/* * Copyright 2015 Luca Beltrame * * This file is part of Danbooru Client. * * Danbooru Client is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Danbooru Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Danbooru Client. If not, see . */ // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include // KF5 #include #include #include #include #include #include #include #include #include #ifdef WITH_KFILEMETADATA #include #endif // Own #include "libdanbooru/danbooruservice.h" #include "libdanbooru/danboorupost.h" #include "libdanbooru/danboorupool.h" #include "model/danboorupostmodel.h" #include "model/danboorupoolmodel.h" #include "model/danboorutagmodel.h" #include "mainwindow.h" #include "danbooruconnectwidget.h" #include "danboorusearchwidget.h" #include "danboorutagwidget.h" #include "danboorusettings.h" #include "generalpage.h" #include "blacklistpage.h" namespace Danbooru { QHash DanbooruMainWindow::ratingMap = { {0, DanbooruPost::Safe}, {1, DanbooruPost::Questionable}, {2, DanbooruPost::Explicit} }; DanbooruMainWindow::DanbooruMainWindow(QWidget *parent) : KXmlGuiWindow(parent), m_view(new QQuickWidget(this)), m_model(new DanbooruPostModel(this)), m_poolModel(new DanbooruPoolModel(this)), m_tagModel(new DanbooruTagModel(this)), m_service(new DanbooruService()), m_connectWidget(Q_NULLPTR), m_searchWidget(new DanbooruSearchWidget(this)), m_tagWidget(new DanbooruTagWidget(this)), m_proxyModel(new QSortFilterProxyModel(this)), m_tableView(new QTableView(this)), m_cache(Q_NULLPTR) { m_service->setParent(this); setCentralWidget(m_view); qmlRegisterType("DanbooruClient", 1, 0, "DanbooruPost"); qmlRegisterType("DanbooruClient", 1, 0, "DanbooruService"); qRegisterMetaType(); loadSettings(); m_cache = new KImageCache(qApp->applicationName(), DanbooruSettings::self()->cacheSize()); m_service->setImageCache(m_cache); m_proxyModel->setSortRole(DanbooruTagModel::TagCountRole); m_proxyModel->setSourceModel(m_tagModel); m_proxyModel->setDynamicSortFilter(true); m_tableView->setModel(m_poolModel); m_tagWidget->setModel(m_proxyModel); // Set up declarative bindings for the QQuickWidget m_view->setResizeMode(QQuickWidget::SizeRootObjectToView); KDeclarative::KDeclarative declarative; declarative.setDeclarativeEngine(m_view->engine()); declarative.setupBindings(); auto qmlViewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, qApp->applicationName() + QLatin1String("/danbooruimageview.qml")); QQmlContext *ctxt = m_view->rootContext(); ctxt->setContextProperty("danbooruModel", m_model); ctxt->setContextProperty("danbooruService", m_service); m_view->setSource(QUrl::fromLocalFile(qmlViewPath)); m_view->rootObject()->setProperty("poolMode", QVariant(false)); ctxt->setContextProperty("infiniteScroll", DanbooruSettings::self()->infiniteScrolling()); auto rootObj = m_view->rootObject(); connect(m_service, SIGNAL(postDownloadFinished()), rootObj, SIGNAL(downloadFinished())); statusBar()->addPermanentWidget(m_connectWidget); statusBar()->hide(); setupDockWidgets(); // then, setup our actions setupActions(); setupGUI(KXmlGuiWindow::ToolBar|Keys|Save|Create, "danbooru-clientui.rc"); // connections connect(m_connectWidget, &DanbooruConnectWidget::accepted, [this]() { m_service->setBoardUrl(m_connectWidget->boardUrl()); if (!m_connectWidget->isAnonymous() && !m_connectWidget->username().isEmpty() && !m_connectWidget->password().isEmpty()) { m_service->setUserName(m_connectWidget->username()); m_service->setPassword(m_connectWidget->password()); } actionCollection()->action(QLatin1String("fetch"))->setEnabled(true); actionCollection()->action(QLatin1String("find"))->setEnabled(true); actionCollection()->action(QLatin1String("poolDownload"))->setEnabled(true); actionCollection()->action(QLatin1String("tags"))->setEnabled(true); actionCollection()->action(QLatin1String("morePosts"))->setEnabled(true); if (DanbooruSettings::self()->autoDownload()) { handlePostDownload(QStringList(), false); } m_connectWidget->hide(); statusBar()->hide(); }); connect(m_connectWidget, &DanbooruConnectWidget::rejected, [this]() { m_connectWidget->hide(); }); connect(m_service, &Danbooru::DanbooruService::postDownloaded, m_model, &Danbooru::DanbooruPostModel::addPost); connect(m_service, &Danbooru::DanbooruService::poolDownloaded, m_poolModel, &DanbooruPoolModel::addPool); connect(m_service, &Danbooru::DanbooruService::tagDownloaded, m_tagModel, &DanbooruTagModel::addTag); connect(m_service, &Danbooru::DanbooruService::poolDownloadFinished, [this]() { m_tableView->resizeColumnsToContents(); } ); connect(m_tableView, &QTableView::doubleClicked, [this](QModelIndex index) { auto pool = m_poolModel->poolAt(index.row()); clearModels(); m_view->rootObject()->setProperty("poolMode", QVariant(true)); QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted"); m_service->getPool(pool->id()); }); connect(m_searchWidget, &DanbooruSearchWidget::accepted, [this]() { QDockWidget* searchDockWidget = findChild(QLatin1String("SearchView")); searchDockWidget->hide(); handlePostDownload(m_searchWidget->selectedTags(), true /* relatedTags */); }); connect(m_searchWidget, &DanbooruSearchWidget::rejected, [this]() { QDockWidget* searchDockWidget = findChild(QLatin1String("SearchView")); searchDockWidget->hide(); }); connect(m_service, &DanbooruService::postDownloadFinished, [this]() { if (m_tagModel->rowCount() == 0) { // Only get tags if we don't have any already for (auto tag: m_model->postTags()) { m_service->getTagList(1, tag); } } m_proxyModel->sort(0, Qt::DescendingOrder); }); connect(m_view->rootObject(), SIGNAL(downloadRequested(const QUrl&, QVariant)), this, SLOT(slotHandleDownload(const QUrl&, QVariant))); connect(m_tagWidget, &QListView::doubleClicked, this, &DanbooruMainWindow::searchTag); } DanbooruMainWindow::~DanbooruMainWindow() { } void DanbooruMainWindow::loadSettings() { m_service->setBlacklist(DanbooruSettings::self()->tagBlacklist()); m_service->setMaxPosts(DanbooruSettings::self()->maxPosts()); m_service->setMaximumAllowedRating(ratingMap.value(DanbooruSettings::self()->maxRating())); m_service->setMaxPosts(DanbooruSettings::self()->maxPosts()); QVector boardsList; QStringList::const_iterator it; for(it = DanbooruSettings::self()->boards().constBegin(); it != DanbooruSettings::self()->boards().constEnd(); ++it) { boardsList.append(QUrl::fromUserInput(*it)); } if (!m_connectWidget) { m_connectWidget = new DanbooruConnectWidget(boardsList, this); } else { m_connectWidget->setBoards(boardsList); } m_tagWidget->setBlackList(DanbooruSettings::self()->tagBlacklist()); m_view->rootContext()->setContextProperty("infiniteScroll", DanbooruSettings::self()->infiniteScrolling()); } void DanbooruMainWindow::setupActions() { QAction *connectAction = new QAction( QIcon::fromTheme(QLatin1String("document-open-remote")), i18n("Connect..."), this); QAction *fetchAction = new QAction(QIcon::fromTheme(QLatin1String("download")), i18n("Download"), this); KToggleAction *findAction = new KToggleAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Search"), this); KToggleAction *poolAction = new KToggleAction(QIcon::fromTheme(QLatin1String("image-x-generic")), i18n("Pools"), this); QAction *nextPageAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("More posts"), this); QAction *nextPoolAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("More pools"), this); KDualAction* tagAction = new KDualAction(i18n("Show tags"), i18n("Hide tags"), this); tagAction->setIconForStates(QIcon::fromTheme(QLatin1String("tag"))); fetchAction->setEnabled(false); findAction->setEnabled(false); poolAction->setEnabled(false); nextPageAction->setEnabled(false); nextPoolAction->setEnabled(false); poolAction->setChecked(false); findAction->setChecked(false); tagAction->setEnabled(false); actionCollection()->addAction(QLatin1String("connect"), connectAction); actionCollection()->addAction(QLatin1String("fetch"), fetchAction); actionCollection()->addAction(QLatin1String("find"), findAction); actionCollection()->addAction(QLatin1String("poolDownload"), poolAction); actionCollection()->addAction(QLatin1String("tags"), tagAction); actionCollection()->addAction(QLatin1String("morePosts"), nextPageAction); actionCollection()->addAction(QLatin1String("morePools"), nextPoolAction); actionCollection()->setDefaultShortcut(connectAction, KStandardShortcut::Open); actionCollection()->setDefaultShortcut(findAction, KStandardShortcut::Find); KStandardAction::quit(qApp, SLOT(quit()), actionCollection()); KStandardAction::preferences(this, SLOT(optionsPreferences()), actionCollection()); connect(connectAction, &QAction::triggered, this, &DanbooruMainWindow::connectToBoard); connect(fetchAction, &QAction::triggered, this, &DanbooruMainWindow::downloadPosts); connect(poolAction, &KToggleAction::toggled, [this](bool checked) { if (!m_service) { return; } QDockWidget* poolDockWidget = findChild(QLatin1String("PoolView")); if (checked) { if (m_poolModel->rowCount() == 0) { m_service->getPoolList(); } poolDockWidget->show(); actionCollection()->action(QLatin1String("morePools"))->setEnabled(true); m_tableView->show(); } else { poolDockWidget->hide(); actionCollection()->action(QLatin1String("morePools"))->setEnabled(false); m_tableView->hide(); } }); connect(findAction, &KToggleAction::toggled, [this](bool checked) { QDockWidget* searchDockWidget = findChild(QLatin1String("SearchView")); if (checked) { searchDockWidget->show(); m_searchWidget->show(); } else { searchDockWidget->hide(); m_searchWidget->hide(); } }); connect(tagAction, &KDualAction::activeChanged, [this](bool checked) { QDockWidget* tagDockWidget = findChild(QLatin1String("TagView")); if (checked) { tagDockWidget->show(); m_tagWidget->show(); } else { tagDockWidget->hide(); m_tagWidget->hide(); } }); connect(nextPageAction, &QAction::triggered, [this]() { if (m_model->rowCount() == 0) { return; } QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted"); m_service->nextPostPage(); } ); connect(nextPoolAction, &QAction::triggered, [this]() { m_service->nextPoolPage(); } ); } void DanbooruMainWindow::setupDockWidgets() { // Set up PoolWidget QDockWidget* poolDockWidget = new QDockWidget(i18n("Pools"), this); poolDockWidget->setAllowedAreas(Qt::BottomDockWidgetArea); poolDockWidget->setWidget(m_tableView); poolDockWidget->setObjectName("PoolView"); // Prevent the use of winId() when detached, leads to QQuickWidget bugs poolDockWidget->setFeatures(QDockWidget::DockWidgetClosable); addDockWidget(Qt::BottomDockWidgetArea, poolDockWidget); m_tableView->hide(); poolDockWidget->hide(); // Find widget QDockWidget *searchDockWidget = new QDockWidget(QLatin1String(""), this); searchDockWidget->setAllowedAreas(Qt::TopDockWidgetArea); searchDockWidget->setWidget(m_searchWidget); searchDockWidget->setObjectName("SearchView"); searchDockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); addDockWidget(Qt::TopDockWidgetArea, searchDockWidget); // No title wanted searchDockWidget->setTitleBarWidget(new QWidget(this)); searchDockWidget->hide(); m_searchWidget->hide(); QDockWidget *tagDockWidget = new QDockWidget(QLatin1String("Tags"), this); tagDockWidget->setAllowedAreas(Qt::RightDockWidgetArea); tagDockWidget->setWidget(m_tagWidget); tagDockWidget->setObjectName("TagView"); tagDockWidget->setFeatures(QDockWidget::DockWidgetClosable); addDockWidget(Qt::RightDockWidgetArea, tagDockWidget); // tagDockWidget->hide(); // m_tagWidget->hide(); // Connections connect(poolDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) { actionCollection()->action(QLatin1String("poolDownload"))->setChecked(visible); }); connect(searchDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) { actionCollection()->action(QLatin1String("find"))->setChecked(visible); }); connect(tagDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) { qobject_cast(actionCollection()->action(QLatin1String("tags")))->setActive(visible); }); } void DanbooruMainWindow::connectToBoard() { if (!m_view) { return; } clearModels(); m_poolModel->clear(); m_service->reset(); statusBar()->show(); m_connectWidget->show(); } void DanbooruMainWindow::downloadPosts() { if (!m_service) { return; } handlePostDownload(QStringList(), false); } void DanbooruMainWindow::optionsPreferences() { KConfigDialog* dialog = new KConfigDialog(this, "danboorusettings", DanbooruSettings::self()); dialog->addPage(new GeneralPage(DanbooruSettings::self(), this), i18n("General"), "table"); dialog->addPage(new BlacklistPage(DanbooruSettings::self(), this), i18n("Tag blacklist"), "configure"); connect(dialog, &KConfigDialog::settingsChanged, this, &DanbooruMainWindow::loadSettings); dialog->show(); } void DanbooruMainWindow::slotHandleDownload(const QUrl &url, const QVariant tags) { QStringList tagList = tags.toStringList(); KIO::MimetypeJob* mimeJob = KIO::mimetype(url, KIO::HideProgressInfo); QString filters; QString remoteFile = url.fileName(); if (mimeJob->exec()) { filters = mimeJob->mimetype(); } else { filters = "Images (*.png *.gif *.jpg);;All files (*.*)"; } // Prevent invalid characters (":" can be a tag in Danbooru) if (remoteFile.contains(":")) { remoteFile.replace(":", "_"); } QUrl localFile = QFileDialog::getSaveFileUrl( this, i18n("Save file"), QDir::homePath() + QLatin1Char('/') + remoteFile, filters ); // TODO: Remember last user directory - settings? if (!localFile.isEmpty()) { KIO::FileCopyJob *job = KIO::file_copy(url, localFile, -1, KIO::DefaultFlags); connect(job, &KIO::Job::result, [this, localFile, tagList](KJob *job) { if (job->error()) { return; } #ifdef WITH_KFILEMETADATA qDebug() << "Local file" << localFile.toLocalFile(); KFileMetaData::UserMetaData meta(localFile.toLocalFile()); meta.setTags(tagList); #endif }); } } void DanbooruMainWindow::searchTag(const QModelIndex &index) { if (!index.isValid()) { return; } QString tagName = m_tagModel->itemAt(index.row())->name(); QStringList tags = {tagName}; handlePostDownload(tags, true /* relatedTags */); } void DanbooruMainWindow::clearModels() { m_model->clear(); m_tagModel->clear(); // m_poolModel->clear(); m_service->reset(); } void DanbooruMainWindow::handlePostDownload(const QStringList &tags, bool relatedTags) { clearModels(); m_view->rootObject()->setProperty("poolMode", QVariant(false)); m_service->setPostTags(tags); if(relatedTags) { m_service->getRelatedTags(tags); } // Call the downloadStarted signal from QML QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted"); m_service->getPostList(); } } // namespace Danbooru