diff --git a/app/abstractcontextmanageritem.cpp b/app/abstractcontextmanageritem.cpp index e58f91bf..07575282 100644 --- a/app/abstractcontextmanageritem.cpp +++ b/app/abstractcontextmanageritem.cpp @@ -1,64 +1,62 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractcontextmanageritem.h" -#include "abstractcontextmanageritem.h" - // Local #include namespace Gwenview { struct AbstractContextManagerItemPrivate { ContextManager* mContextManager; QWidget* mWidget; }; AbstractContextManagerItem::AbstractContextManagerItem(ContextManager* manager) : QObject(manager) , d(new AbstractContextManagerItemPrivate) { d->mContextManager = manager; d->mWidget = nullptr; } AbstractContextManagerItem::~AbstractContextManagerItem() { delete d; } ContextManager* AbstractContextManagerItem::contextManager() const { return d->mContextManager; } QWidget* AbstractContextManagerItem::widget() const { return d->mWidget; } void AbstractContextManagerItem::setWidget(QWidget* widget) { d->mWidget = widget; } } // namespace diff --git a/app/fileopscontextmanageritem.cpp b/app/fileopscontextmanageritem.cpp index d32ce95c..2e47b419 100644 --- a/app/fileopscontextmanageritem.cpp +++ b/app/fileopscontextmanageritem.cpp @@ -1,432 +1,431 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "fileopscontextmanageritem.h" #include "dialogguard.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include // Local #include #include #include #include #include "fileoperations.h" #include "sidebar.h" namespace Gwenview { QList FileOpsContextManagerItem::urlList() const { return contextManager()->selectedFileItemList().urlList(); } void FileOpsContextManagerItem::updateServiceList() { // This code is inspired from // kdebase/apps/lib/konq/konq_menuactions.cpp // Get list of all distinct mimetypes in selection QStringList mimeTypes; Q_FOREACH(const KFileItem & item, contextManager()->selectedFileItemList()) { const QString mimeType = item.mimetype(); if (!mimeTypes.contains(mimeType)) { mimeTypes << mimeType; } } // Query trader mServiceList = KFileItemActions::associatedApplications(mimeTypes, QString()); } QMimeData* FileOpsContextManagerItem::selectionMimeData() { KFileItemList selectedFiles; // In Compare mode, restrict the returned mimedata to the focused image if (!mThumbnailView->isVisible()) { selectedFiles << KFileItem(contextManager()->currentUrl()); } else { selectedFiles = contextManager()->selectedFileItemList(); } return MimeTypeUtils::selectionMimeData(selectedFiles, MimeTypeUtils::ClipboardTarget); } QUrl FileOpsContextManagerItem::pasteTargetUrl() const { // If only one folder is selected, paste inside it, otherwise paste in // current KFileItemList list = contextManager()->selectedFileItemList(); if (list.count() == 1 && list.first().isDir()) { return list.first().url(); } else { return contextManager()->currentDirUrl(); } } static QAction* createSeparator(QObject* parent) { QAction* action = new QAction(parent); action->setSeparator(true); return action; } FileOpsContextManagerItem::FileOpsContextManagerItem(ContextManager* manager, QListView* thumbnailView, KActionCollection* actionCollection, KXMLGUIClient* client) : AbstractContextManagerItem(manager) { mThumbnailView = thumbnailView; mXMLGUIClient = client; mGroup = new SideBarGroup(i18n("File Operations")); setWidget(mGroup); EventWatcher::install(mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); mInTrash = false; mNewFileMenu = new KNewFileMenu(Q_NULLPTR, QString(), this); connect(contextManager(), SIGNAL(selectionChanged()), SLOT(updateActions())); connect(contextManager(), SIGNAL(currentDirUrlChanged(QUrl)), SLOT(updateActions())); KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Edit"), actionCollection); mCutAction = edit->addAction(KStandardAction::Cut, this, SLOT(cut())); mCopyAction = edit->addAction(KStandardAction::Copy, this, SLOT(copy())); mPasteAction = edit->addAction(KStandardAction::Paste, this, SLOT(paste())); mCopyToAction = file->addAction("file_copy_to", this, SLOT(copyTo())); mCopyToAction->setText(i18nc("Verb", "Copy To...")); mCopyToAction->setIcon(QIcon::fromTheme("edit-copy")); actionCollection->setDefaultShortcut(mCopyToAction, Qt::Key_F7); mMoveToAction = file->addAction("file_move_to", this, SLOT(moveTo())); mMoveToAction->setText(i18nc("Verb", "Move To...")); mMoveToAction->setIcon(QIcon::fromTheme("go-jump")); actionCollection->setDefaultShortcut(mMoveToAction, Qt::Key_F8); mLinkToAction = file->addAction("file_link_to", this, SLOT(linkTo())); mLinkToAction->setText(i18nc("Verb: create link to the file where user wants", "Link To...")); mLinkToAction->setIcon(QIcon::fromTheme("link")); actionCollection->setDefaultShortcut(mLinkToAction, Qt::Key_F9); mRenameAction = file->addAction("file_rename", this, SLOT(rename())); mRenameAction->setText(i18nc("Verb", "Rename...")); mRenameAction->setIcon(QIcon::fromTheme("edit-rename")); actionCollection->setDefaultShortcut(mRenameAction, Qt::Key_F2); mTrashAction = file->addAction("file_trash", this, SLOT(trash())); mTrashAction->setText(i18nc("Verb", "Trash")); // TODO: Remove trash-empty from the below line when a new user-trash icon // is released by Breeze that adheres to monochrome on HiDPI scaled displays. // See Bug #391078 mTrashAction->setIcon(QIcon::fromTheme("trash-empty", QIcon::fromTheme("user-trash"))); actionCollection->setDefaultShortcut(mTrashAction, Qt::Key_Delete); mDelAction = file->addAction(KStandardAction::DeleteFile, this, SLOT(del())); mRestoreAction = file->addAction("file_restore", this, SLOT(restore())); mRestoreAction->setText(i18n("Restore")); mRestoreAction->setIcon(QIcon::fromTheme("edit-undo")); mShowPropertiesAction = file->addAction("file_show_properties", this, SLOT(showProperties())); mShowPropertiesAction->setText(i18n("Properties")); mShowPropertiesAction->setIcon(QIcon::fromTheme("document-properties")); mCreateFolderAction = file->addAction("file_create_folder", this, SLOT(createFolder())); mCreateFolderAction->setText(i18n("Create Folder...")); mCreateFolderAction->setIcon(QIcon::fromTheme("folder-new")); mOpenWithAction = file->addAction("file_open_with"); mOpenWithAction->setText(i18n("Open With")); QMenu* menu = new QMenu; mOpenWithAction->setMenu(menu); connect(menu, &QMenu::aboutToShow, this, &FileOpsContextManagerItem::populateOpenMenu); connect(menu, &QMenu::triggered, this, &FileOpsContextManagerItem::openWith); mOpenContainingFolderAction = file->addAction("file_open_containing_folder", this, SLOT(openContainingFolder())); mOpenContainingFolderAction->setText(i18n("Open Containing Folder")); mOpenContainingFolderAction->setIcon(QIcon::fromTheme("document-open-folder")); mRegularFileActionList << mRenameAction << mTrashAction << mDelAction << createSeparator(this) << mCopyToAction << mMoveToAction << mLinkToAction << createSeparator(this) << mOpenWithAction << mOpenContainingFolderAction << mShowPropertiesAction << createSeparator(this) << mCreateFolderAction ; mTrashFileActionList << mRestoreAction << mDelAction << createSeparator(this) << mShowPropertiesAction ; connect(QApplication::clipboard(), SIGNAL(dataChanged()), SLOT(updatePasteAction())); // Delay action update because it must happen *after* main window has called // createGUI(), otherwise calling mXMLGUIClient->plugActionList() will // fail. QMetaObject::invokeMethod(this, "updateActions", Qt::QueuedConnection); } FileOpsContextManagerItem::~FileOpsContextManagerItem() { delete mOpenWithAction->menu(); } void FileOpsContextManagerItem::updateActions() { const int count = contextManager()->selectedFileItemList().count(); const bool selectionNotEmpty = count > 0; const bool urlIsValid = contextManager()->currentUrl().isValid(); const bool dirUrlIsValid = contextManager()->currentDirUrl().isValid(); mInTrash = contextManager()->currentDirUrl().scheme() == "trash"; mCutAction->setEnabled(selectionNotEmpty); mCopyAction->setEnabled(selectionNotEmpty); mCopyToAction->setEnabled(selectionNotEmpty); mMoveToAction->setEnabled(selectionNotEmpty); mLinkToAction->setEnabled(selectionNotEmpty); mTrashAction->setEnabled(selectionNotEmpty); mRestoreAction->setEnabled(selectionNotEmpty); mDelAction->setEnabled(selectionNotEmpty); mOpenWithAction->setEnabled(selectionNotEmpty); mRenameAction->setEnabled(count == 1); mOpenContainingFolderAction->setEnabled(selectionNotEmpty); mCreateFolderAction->setEnabled(dirUrlIsValid); mShowPropertiesAction->setEnabled(dirUrlIsValid || urlIsValid); mXMLGUIClient->unplugActionList("file_action_list"); QList& list = mInTrash ? mTrashFileActionList : mRegularFileActionList; mXMLGUIClient->plugActionList("file_action_list", list); updateSideBarContent(); updatePasteAction(); } void FileOpsContextManagerItem::updatePasteAction() { const QMimeData *mimeData = QApplication::clipboard()->mimeData(); bool enable; KFileItem destItem(pasteTargetUrl()); const QString text = KIO::pasteActionText(mimeData, &enable, destItem); mPasteAction->setEnabled(enable); mPasteAction->setText(text); } void FileOpsContextManagerItem::updateSideBarContent() { if (!mGroup->isVisible()) { return; } mGroup->clear(); QList& list = mInTrash ? mTrashFileActionList : mRegularFileActionList; Q_FOREACH(QAction * action, list) { if (action->isEnabled() && !action->isSeparator()) { mGroup->addAction(action); } } } void FileOpsContextManagerItem::showProperties() { KFileItemList list = contextManager()->selectedFileItemList(); if (list.count() > 0) { KPropertiesDialog::showDialog(list, mGroup); } else { QUrl url = contextManager()->currentDirUrl(); KPropertiesDialog::showDialog(url, mGroup); } } void FileOpsContextManagerItem::cut() { QMimeData* mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void FileOpsContextManagerItem::copy() { QMimeData* mimeData = selectionMimeData(); KIO::setClipboardDataCut(mimeData, false); QApplication::clipboard()->setMimeData(mimeData); } void FileOpsContextManagerItem::paste() { KIO::Job *job = KIO::paste(QApplication::clipboard()->mimeData(), pasteTargetUrl()); KJobWidgets::setWindow(job, mGroup); } void FileOpsContextManagerItem::trash() { FileOperations::trash(urlList(), mGroup); } void FileOpsContextManagerItem::del() { FileOperations::del(urlList(), mGroup); } void FileOpsContextManagerItem::restore() { KIO::RestoreJob *job = KIO::restoreFromTrash(urlList()); KJobWidgets::setWindow(job, mGroup); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } void FileOpsContextManagerItem::copyTo() { FileOperations::copyTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::moveTo() { FileOperations::moveTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::linkTo() { FileOperations::linkTo(urlList(), widget(), contextManager()); } void FileOpsContextManagerItem::rename() { if (mThumbnailView->isVisible()) { QModelIndex index = mThumbnailView->currentIndex(); mThumbnailView->edit(index); } else { FileOperations::rename(urlList().first(), mGroup, contextManager()); contextManager()->slotSelectionChanged(); } } void FileOpsContextManagerItem::createFolder() { QUrl url = contextManager()->currentDirUrl(); mNewFileMenu->setParentWidget(mGroup); mNewFileMenu->setPopupFiles(QList() << url); mNewFileMenu->createDirectory(); } void FileOpsContextManagerItem::populateOpenMenu() { QMenu* openMenu = mOpenWithAction->menu(); qDeleteAll(openMenu->actions()); updateServiceList(); int idx = 0; Q_FOREACH(const KService::Ptr & service, mServiceList) { QString text = service->name().replace('&', "&&"); QAction* action = openMenu->addAction(text); action->setIcon(QIcon::fromTheme(service->icon())); action->setData(idx); ++idx; } openMenu->addSeparator(); QAction* action = openMenu->addAction(i18n("Other Application...")); action->setData(-1); } void FileOpsContextManagerItem::openWith(QAction* action) { Q_ASSERT(action); KService::Ptr service; QList list = urlList(); bool ok; int idx = action->data().toInt(&ok); GV_RETURN_IF_FAIL(ok); if (idx == -1) { // Other Application... DialogGuard dlg(list, mGroup); if (!dlg->exec()) { return; } service = dlg->service(); if (!service) { // User entered a custom command Q_ASSERT(!dlg->text().isEmpty()); KRun::run(dlg->text(), list, mGroup); return; } } else { service = mServiceList.at(idx); } Q_ASSERT(service); KRun::runService(*service, list, mGroup); } void FileOpsContextManagerItem::openContainingFolder() { KIO::highlightInFileManager(urlList()); } } // namespace diff --git a/app/filtercontroller.cpp b/app/filtercontroller.cpp index c3bfb47c..898a8b00 100644 --- a/app/filtercontroller.cpp +++ b/app/filtercontroller.cpp @@ -1,359 +1,358 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "filtercontroller.h" #include // Qt #include #include #include #include #include #include #include #include #include // KDE #include #include -#include #include // Local #include #include #include #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE // KDE #include // Local #include #include #endif namespace Gwenview { NameFilterWidget::NameFilterWidget(SortedDirModel* model) { mFilter = new NameFilter(model); mModeComboBox = new KComboBox; mModeComboBox->addItem(i18n("Name contains"), QVariant(NameFilter::Contains)); mModeComboBox->addItem(i18n("Name does not contain"), QVariant(NameFilter::DoesNotContain)); mLineEdit = new QLineEdit; QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(2); layout->addWidget(mModeComboBox); layout->addWidget(mLineEdit); QTimer* timer = new QTimer(this); timer->setInterval(350); timer->setSingleShot(true); connect(timer, SIGNAL(timeout()), SLOT(applyNameFilter())); connect(mLineEdit, SIGNAL(textChanged(QString)), timer, SLOT(start())); connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(applyNameFilter())); QTimer::singleShot(0, mLineEdit, SLOT(setFocus())); } NameFilterWidget::~NameFilterWidget() { delete mFilter; } void NameFilterWidget::applyNameFilter() { QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex()); mFilter->setMode(NameFilter::Mode(data.toInt())); mFilter->setText(mLineEdit->text()); } DateFilterWidget::DateFilterWidget(SortedDirModel* model) { mFilter = new DateFilter(model); mModeComboBox = new KComboBox; mModeComboBox->addItem(i18n("Date >="), DateFilter::GreaterOrEqual); mModeComboBox->addItem(i18n("Date ="), DateFilter::Equal); mModeComboBox->addItem(i18n("Date <="), DateFilter::LessOrEqual); mDateWidget = new DateWidget; QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->addWidget(mModeComboBox); layout->addWidget(mDateWidget); connect(mDateWidget, SIGNAL(dateChanged(QDate)), SLOT(applyDateFilter())); connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(applyDateFilter())); applyDateFilter(); } DateFilterWidget::~DateFilterWidget() { delete mFilter; } void DateFilterWidget::applyDateFilter() { QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex()); mFilter->setMode(DateFilter::Mode(data.toInt())); mFilter->setDate(mDateWidget->date()); } #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE RatingFilterWidget::RatingFilterWidget(SortedDirModel* model) { mModeComboBox = new KComboBox; mModeComboBox->addItem(i18n("Rating >="), RatingFilter::GreaterOrEqual); mModeComboBox->addItem(i18n("Rating =") , RatingFilter::Equal); mModeComboBox->addItem(i18n("Rating <="), RatingFilter::LessOrEqual); mRatingWidget = new KRatingWidget; mRatingWidget->setHalfStepsEnabled(true); mRatingWidget->setMaxRating(10); QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->addWidget(mModeComboBox); layout->addWidget(mRatingWidget); mFilter = new RatingFilter(model); QObject::connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateFilterMode())); QObject::connect(mRatingWidget, SIGNAL(ratingChanged(int)), SLOT(slotRatingChanged(int))); updateFilterMode(); } RatingFilterWidget::~RatingFilterWidget() { delete mFilter; } void RatingFilterWidget::slotRatingChanged(int value) { mFilter->setRating(value); } void RatingFilterWidget::updateFilterMode() { QVariant data = mModeComboBox->itemData(mModeComboBox->currentIndex()); mFilter->setMode(RatingFilter::Mode(data.toInt())); } TagFilterWidget::TagFilterWidget(SortedDirModel* model) { mFilter = new TagFilter(model); mModeComboBox = new KComboBox; mModeComboBox->addItem(i18n("Tagged"), QVariant(true)); mModeComboBox->addItem(i18n("Not Tagged"), QVariant(false)); mTagComboBox = new QComboBox; QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->addWidget(mModeComboBox); layout->addWidget(mTagComboBox); AbstractSemanticInfoBackEnd* backEnd = model->semanticInfoBackEnd(); backEnd->refreshAllTags(); TagModel* tagModel = TagModel::createAllTagsModel(this, backEnd); QCompleter* completer = new QCompleter(mTagComboBox); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModel(tagModel); mTagComboBox->setCompleter(completer); mTagComboBox->setInsertPolicy(QComboBox::NoInsert); mTagComboBox->setEditable(true); mTagComboBox->setModel(tagModel); mTagComboBox->setCurrentIndex(-1); connect(mTagComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter())); connect(mModeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(updateTagSetFilter())); QTimer::singleShot(0, mTagComboBox, SLOT(setFocus())); } TagFilterWidget::~TagFilterWidget() { delete mFilter; } void TagFilterWidget::updateTagSetFilter() { QModelIndex index = mTagComboBox->model()->index(mTagComboBox->currentIndex(), 0); if (!index.isValid()) { qWarning() << "Invalid index"; return; } SemanticInfoTag tag = index.data(TagModel::TagRole).toString(); mFilter->setTag(tag); bool wantMatchingTag = mModeComboBox->itemData(mModeComboBox->currentIndex()).toBool(); mFilter->setWantMatchingTag(wantMatchingTag); } #endif /** * A container for all filter widgets. It features a close button on the right. */ class FilterWidgetContainer : public QFrame { public: FilterWidgetContainer() { QPalette pal = palette(); pal.setColor(QPalette::Window, pal.color(QPalette::Highlight)); setPalette(pal); } void setFilterWidget(QWidget* widget) { QToolButton* closeButton = new QToolButton; closeButton->setIcon(QIcon::fromTheme("window-close")); closeButton->setAutoRaise(true); closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); int size = IconSize(KIconLoader::Small); closeButton->setIconSize(QSize(size, size)); connect(closeButton, SIGNAL(clicked()), SLOT(deleteLater())); QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(2); layout->setSpacing(2); layout->addWidget(widget); layout->addWidget(closeButton); } protected: void paintEvent(QPaintEvent*) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QPainterPath path = PaintUtils::roundedRectangle(QRectF(rect()).adjusted(0.5, 0.5, -0.5, -0.5), 6); QColor color = palette().color(QPalette::Highlight); painter.fillPath(path, PaintUtils::alphaAdjustedF(color, 0.5)); painter.setPen(color); painter.drawPath(path); } }; FilterController::FilterController(QFrame* frame, SortedDirModel* dirModel) : QObject(frame) { q = this; mFrame = frame; mDirModel = dirModel; mFilterWidgetCount = 0; mFrame->hide(); FlowLayout* layout = new FlowLayout(mFrame); layout->setSpacing(2); addAction(i18nc("@action:inmenu", "Filter by Name"), SLOT(addFilterByName())); addAction(i18nc("@action:inmenu", "Filter by Date"), SLOT(addFilterByDate())); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE addAction(i18nc("@action:inmenu", "Filter by Rating"), SLOT(addFilterByRating())); addAction(i18nc("@action:inmenu", "Filter by Tag"), SLOT(addFilterByTag())); #endif } QList FilterController::actionList() const { return mActionList; } void FilterController::addFilterByName() { addFilter(new NameFilterWidget(mDirModel)); } void FilterController::addFilterByDate() { addFilter(new DateFilterWidget(mDirModel)); } #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE void FilterController::addFilterByRating() { addFilter(new RatingFilterWidget(mDirModel)); } void FilterController::addFilterByTag() { addFilter(new TagFilterWidget(mDirModel)); } #endif void FilterController::slotFilterWidgetClosed() { mFilterWidgetCount--; if (mFilterWidgetCount == 0) { mFrame->hide(); } } void FilterController::addAction(const QString& text, const char* slot) { QAction* action = new QAction(text, q); QObject::connect(action, SIGNAL(triggered()), q, slot); mActionList << action; } void FilterController::addFilter(QWidget* widget) { if (mFrame->isHidden()) { mFrame->show(); } FilterWidgetContainer* container = new FilterWidgetContainer; container->setFilterWidget(widget); mFrame->layout()->addWidget(container); mFilterWidgetCount++; QObject::connect(container, SIGNAL(destroyed()), q, SLOT(slotFilterWidgetClosed())); } } // namespace diff --git a/importer/importdialog.cpp b/importer/importdialog.cpp index 53838008..627853d9 100644 --- a/importer/importdialog.cpp +++ b/importer/importdialog.cpp @@ -1,267 +1,267 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "importdialog.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include // Local #include "dialogpage.h" #include "importer.h" #include "importerconfig.h" #include "progresspage.h" #include "thumbnailpage.h" namespace Gwenview { class ImportDialogPrivate { public: ImportDialog* q; QStackedWidget* mCentralWidget; ThumbnailPage* mThumbnailPage; ProgressPage* mProgressPage; DialogPage* mDialogPage; Importer* mImporter; void deleteImportedUrls() { QList importedUrls = mImporter->importedUrlList(); QList skippedUrls = mImporter->skippedUrlList(); int importedCount = importedUrls.count(); int skippedCount = skippedUrls.count(); if (importedCount == 0 && skippedCount == 0) { return; } QStringList message; message << i18np( "One document has been imported.", "%1 documents have been imported.", importedCount); if (skippedCount > 0) { message << i18np( "One document has been skipped because it had already been imported.", "%1 documents have been skipped because they had already been imported.", skippedCount); } if (mImporter->renamedCount() > 0) { message[0].append("*"); message << "* " + i18np( "One of them has been renamed because another document with the same name had already been imported.", "%1 of them have been renamed because other documents with the same name had already been imported.", mImporter->renamedCount()) + ""; } message << QString(); if (skippedCount == 0) { message << i18np( "Delete the imported document from the device?", "Delete the %1 imported documents from the device?", importedCount); } else if (importedCount == 0) { message << i18np( "Delete the skipped document from the device?", "Delete the %1 skipped documents from the device?", skippedCount); } else { message << i18ncp( "Singular sentence is actually never used.", "Delete the imported or skipped document from the device?", "Delete the %1 imported and skipped documents from the device?", importedCount + skippedCount); } int answer = KMessageBox::questionYesNo(mCentralWidget, "" + message.join("
") + "
", i18nc("@title:window", "Import Finished"), KStandardGuiItem::del(), KGuiItem(i18n("Keep")) ); if (answer != KMessageBox::Yes) { return; } QList urls = importedUrls + skippedUrls; while (true) { KIO::Job* job = KIO::del(urls); if (job->exec()) { break; } // Deleting failed int answer = KMessageBox::warningYesNo(mCentralWidget, i18np("Failed to delete the document:\n%2", "Failed to delete documents:\n%2", urls.count(), job->errorString()), QString(), KGuiItem(i18n("Retry")), KGuiItem(i18n("Ignore")) ); if (answer != KMessageBox::Yes) { // Ignore break; } } } void startGwenview() { KService::Ptr service = KService::serviceByDesktopName("org.kde.gwenview"); if (!service) { qCritical() << "Could not find gwenview"; } else { KRun::runService(*service, {mThumbnailPage->destinationUrl()}, nullptr /* window */); } } void showWhatNext() { mCentralWidget->setCurrentWidget(mDialogPage); mDialogPage->setText(i18n("What do you want to do now?")); mDialogPage->removeButtons(); int gwenview = mDialogPage->addButton(KGuiItem(i18n("View Imported Documents with Gwenview"), "gwenview")); int importMore = mDialogPage->addButton(KGuiItem(i18n("Import more Documents"))); mDialogPage->addButton(KGuiItem(i18n("Quit"), "dialog-cancel")); int answer = mDialogPage->exec(); if (answer == gwenview) { startGwenview(); qApp->quit(); } else if (answer == importMore) { mCentralWidget->setCurrentWidget(mThumbnailPage); } else { /* quit */ qApp->quit(); } } }; ImportDialog::ImportDialog() : d(new ImportDialogPrivate) { d->q = this; d->mImporter = new Importer(this); connect(d->mImporter, SIGNAL(error(QString)), SLOT(showImportError(QString))); d->mThumbnailPage = new ThumbnailPage; QUrl url = ImporterConfig::destinationUrl(); if (!url.isValid()) { url = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); int year = QDate::currentDate().year(); url.setPath(url.path() + '/' + QString::number(year)); } d->mThumbnailPage->setDestinationUrl(url); d->mProgressPage = new ProgressPage(d->mImporter); d->mDialogPage = new DialogPage; d->mCentralWidget = new QStackedWidget; setCentralWidget(d->mCentralWidget); d->mCentralWidget->addWidget(d->mThumbnailPage); d->mCentralWidget->addWidget(d->mProgressPage); d->mCentralWidget->addWidget(d->mDialogPage); connect(d->mThumbnailPage, SIGNAL(importRequested()), SLOT(startImport())); connect(d->mThumbnailPage, SIGNAL(rejected()), SLOT(close())); connect(d->mImporter, SIGNAL(importFinished()), SLOT(slotImportFinished())); d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); setWindowIcon(QIcon::fromTheme("gwenview")); setAutoSaveSettings(); } ImportDialog::~ImportDialog() { delete d; } QSize ImportDialog::sizeHint() const { return QSize(700, 500); } void ImportDialog::setSourceUrl(const QUrl& url, const QString& deviceUdi) { QString name, iconName; if (deviceUdi.isEmpty()) { name = url.url(QUrl::PreferLocalFile); iconName = KProtocolInfo::icon(url.scheme()); if (iconName.isEmpty()) { iconName = "folder"; } } else { Solid::Device device(deviceUdi); - name = device.vendor() + " " + device.product(); + name = device.vendor() + ' ' + device.product(); iconName = device.icon(); } d->mThumbnailPage->setSourceUrl(url, iconName, name); } void ImportDialog::startImport() { QUrl url = d->mThumbnailPage->destinationUrl(); ImporterConfig::setDestinationUrl(url); ImporterConfig::self()->save(); d->mCentralWidget->setCurrentWidget(d->mProgressPage); d->mImporter->setAutoRenameFormat( ImporterConfig::autoRename() ? ImporterConfig::autoRenameFormat() : QString()); d->mImporter->start(d->mThumbnailPage->urlList(), url); } void ImportDialog::slotImportFinished() { d->deleteImportedUrls(); d->showWhatNext(); } void ImportDialog::showImportError(const QString& message) { KMessageBox::sorry(this, message); d->mCentralWidget->setCurrentWidget(d->mThumbnailPage); } } // namespace diff --git a/importer/main.cpp b/importer/main.cpp index 0603bacc..4497cc6b 100644 --- a/importer/main.cpp +++ b/importer/main.cpp @@ -1,83 +1,82 @@ /* Gwenview: an image viewer Copyright 2000-2009 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt #include #include #include #include #include #include // KDE #include -#include #include #include // Local #include #include #include "importdialog.h" int main(int argc, char *argv[]) { KLocalizedString::setApplicationDomain("gwenview"); QApplication app(argc, argv); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); QScopedPointer aboutData( Gwenview::createAboutData( QStringLiteral("org.kde.gwenview"), /* component name */ i18n("Gwenview Importer") /* programName */ )); aboutData->setShortDescription(i18n("Photo Importer")); KAboutData::setApplicationData(*aboutData); QCommandLineParser parser; aboutData.data()->setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringLiteral("udi"), i18n("The device UDI, used to retrieve information about the device (name, icon...)"), i18n("Device UDI"))); parser.addPositionalArgument("folder", i18n("Source folder")); parser.process(app); aboutData.data()->processCommandLine(&parser); if (parser.positionalArguments().count() == 0) { qWarning() << i18n("Missing required source folder argument."); parser.showHelp(); } if (parser.positionalArguments().count() > 1) { qWarning() << i18n("Too many arguments."); parser.showHelp(); } QString urlString = parser.positionalArguments().first(); QUrl url = QUrl::fromUserInput(urlString, QDir::currentPath(), QUrl::AssumeLocalFile); if (!url.isValid()) { qCritical() << i18n("Invalid source folder."); return 1; } QString deviceUdi = parser.isSet("udi") ? parser.value("udi") : QString(); Gwenview::ImageFormats::registerPlugins(); Gwenview::ImportDialog* dialog = new Gwenview::ImportDialog(); dialog->show(); QMetaObject::invokeMethod(dialog, "setSourceUrl", Qt::QueuedConnection, Q_ARG(QUrl, url), Q_ARG(QString, deviceUdi)); return app.exec(); } diff --git a/lib/contextmanager.cpp b/lib/contextmanager.cpp index 2243ea99..f28db260 100644 --- a/lib/contextmanager.cpp +++ b/lib/contextmanager.cpp @@ -1,365 +1,364 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextmanager.h" -#include "contextmanager.h" // Qt #include #include #include #include // KDE #include #include #include // Local #include #include #include #include namespace Gwenview { struct ContextManagerPrivate { SortedDirModel* mDirModel; QItemSelectionModel* mSelectionModel; QUrl mCurrentDirUrl; QUrl mCurrentUrl; QUrl mUrlToSelect; QUrl mTargetDirUrl; bool mSelectedFileItemListNeedsUpdate; QSet mQueuedSignals; KFileItemList mSelectedFileItemList; bool mDirListerFinished = false; QTimer* mQueuedSignalsTimer; void queueSignal(const QByteArray& signal) { mQueuedSignals << signal; mQueuedSignalsTimer->start(); } void updateSelectedFileItemList() { if (!mSelectedFileItemListNeedsUpdate) { return; } mSelectedFileItemList.clear(); QItemSelection selection = mSelectionModel->selection(); Q_FOREACH(const QModelIndex & index, selection.indexes()) { mSelectedFileItemList << mDirModel->itemForIndex(index); } // At least add current url if it's valid (it may not be in // the list if we are viewing a non-browsable url, for example // using http protocol) if (mSelectedFileItemList.isEmpty() && mCurrentUrl.isValid()) { KFileItem item(mCurrentUrl); mSelectedFileItemList << item; } mSelectedFileItemListNeedsUpdate = false; } }; ContextManager::ContextManager(SortedDirModel* dirModel, QObject* parent) : QObject(parent) , d(new ContextManagerPrivate) { d->mQueuedSignalsTimer = new QTimer(this); d->mQueuedSignalsTimer->setInterval(100); d->mQueuedSignalsTimer->setSingleShot(true); connect(d->mQueuedSignalsTimer, &QTimer::timeout, this, &ContextManager::emitQueuedSignals); d->mDirModel = dirModel; connect(d->mDirModel, &SortedDirModel::dataChanged, this, &ContextManager::slotDirModelDataChanged); /* HACK! In extended-selection mode, when the current index is removed, * QItemSelectionModel selects the previous index if there is one, if not it * selects the next index. This is not what we want: when the user removes * an image, he expects to go to the next one, not the previous one. * * To overcome this, we must connect to the mDirModel.rowsAboutToBeRemoved() * signal *before* QItemSelectionModel connects to it, so that our slot is * called before QItemSelectionModel slot. This allows us to pick a new * current index ourself, leaving QItemSelectionModel slot with nothing to * do. * * This is the reason ContextManager creates a QItemSelectionModel itself: * doing so ensures QItemSelectionModel cannot be connected to the * mDirModel.rowsAboutToBeRemoved() signal before us. */ connect(d->mDirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &ContextManager::slotRowsAboutToBeRemoved); connect(d->mDirModel, &SortedDirModel::rowsInserted, this, &ContextManager::slotRowsInserted); connect(d->mDirModel->dirLister(), SIGNAL(redirection(QUrl)), SLOT(slotDirListerRedirection(QUrl))); connect(d->mDirModel->dirLister(), static_cast(&KDirLister::completed), this, &ContextManager::slotDirListerCompleted); d->mSelectionModel = new QItemSelectionModel(d->mDirModel); connect(d->mSelectionModel, &QItemSelectionModel::selectionChanged, this, &ContextManager::slotSelectionChanged); connect(d->mSelectionModel, &QItemSelectionModel::currentChanged, this, &ContextManager::slotCurrentChanged); d->mSelectedFileItemListNeedsUpdate = false; } ContextManager::~ContextManager() { delete d; } void ContextManager::loadConfig() { setTargetDirUrl(QUrl(GwenviewConfig::lastTargetDir())); } void ContextManager::saveConfig() const { GwenviewConfig::setLastTargetDir(targetDirUrl().toString()); } QItemSelectionModel* ContextManager::selectionModel() const { return d->mSelectionModel; } void ContextManager::setCurrentUrl(const QUrl ¤tUrl) { if (d->mCurrentUrl == currentUrl) { return; } d->mCurrentUrl = currentUrl; if (!d->mCurrentUrl.isEmpty()) { Document::Ptr doc = DocumentFactory::instance()->load(currentUrl); QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); undoGroup->addStack(doc->undoStack()); undoGroup->setActiveStack(doc->undoStack()); } d->mSelectedFileItemListNeedsUpdate = true; currentUrlChanged(currentUrl); } KFileItemList ContextManager::selectedFileItemList() const { d->updateSelectedFileItemList(); return d->mSelectedFileItemList; } void ContextManager::setCurrentDirUrl(const QUrl &_url) { const QUrl url = _url.adjusted(QUrl::StripTrailingSlash); if (url == d->mCurrentDirUrl) { return; } if (url.isValid() && KProtocolManager::supportsListing(url)) { d->mCurrentDirUrl = url; d->mDirModel->dirLister()->openUrl(url); d->mDirListerFinished = false; } else { d->mCurrentDirUrl.clear(); d->mDirModel->dirLister()->clear(); } currentDirUrlChanged(url); } QUrl ContextManager::currentDirUrl() const { return d->mCurrentDirUrl; } QUrl ContextManager::currentUrl() const { return d->mCurrentUrl; } SortedDirModel* ContextManager::dirModel() const { return d->mDirModel; } void ContextManager::slotDirModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Data change can happen in the following cases: // - items have been renamed // - item bytes have been modified // - item meta info has been retrieved or modified // // If a selected item is affected, schedule emission of a // selectionDataChanged() signal. Don't emit it directly to avoid spamming // the context items in case of a mass change. QModelIndexList selectionList = d->mSelectionModel->selectedIndexes(); if (selectionList.isEmpty()) { return; } QModelIndexList changedList; for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { changedList << d->mDirModel->index(row, 0); } QModelIndexList& shortList = selectionList; QModelIndexList& longList = changedList; if (shortList.length() > longList.length()) { qSwap(shortList, longList); } Q_FOREACH(const QModelIndex & index, shortList) { if (longList.contains(index)) { d->mSelectedFileItemListNeedsUpdate = true; d->queueSignal("selectionDataChanged"); return; } } } void ContextManager::slotSelectionChanged() { d->mSelectedFileItemListNeedsUpdate = true; if (!d->mSelectionModel->hasSelection()) { setCurrentUrl(QUrl()); } d->queueSignal("selectionChanged"); } void Gwenview::ContextManager::slotCurrentChanged(const QModelIndex& index) { QUrl url = d->mDirModel->urlForIndex(index); setCurrentUrl(url); } void ContextManager::emitQueuedSignals() { Q_FOREACH(const QByteArray & signal, d->mQueuedSignals) { QMetaObject::invokeMethod(this, signal.data()); } d->mQueuedSignals.clear(); } void Gwenview::ContextManager::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) { QModelIndex oldCurrent = d->mSelectionModel->currentIndex(); if (oldCurrent.row() < start || oldCurrent.row() > end) { // currentIndex has not been removed return; } QModelIndex newCurrent; if (end + 1 < d->mDirModel->rowCount()) { newCurrent = d->mDirModel->index(end + 1, 0); } else if (start > 0) { newCurrent = d->mDirModel->index(start - 1, 0); } else { // No index we can select, nothing to do return; } d->mSelectionModel->select(oldCurrent, QItemSelectionModel::Deselect); d->mSelectionModel->setCurrentIndex(newCurrent, QItemSelectionModel::Select); } bool ContextManager::currentUrlIsRasterImage() const { return MimeTypeUtils::urlKind(currentUrl()) == MimeTypeUtils::KIND_RASTER_IMAGE; } QUrl ContextManager::urlToSelect() const { return d->mUrlToSelect; } void ContextManager::setUrlToSelect(const QUrl &url) { GV_RETURN_IF_FAIL(url.isValid()); d->mUrlToSelect = url; setCurrentDirUrl(url.adjusted(QUrl::RemoveFilename)); setCurrentUrl(url); selectUrlToSelect(); } QUrl ContextManager::targetDirUrl() const { return d->mTargetDirUrl; } void ContextManager::setTargetDirUrl(const QUrl &url) { GV_RETURN_IF_FAIL(url.isEmpty() || url.isValid()); d->mTargetDirUrl = GwenviewConfig::historyEnabled() ? url : QUrl(); } void ContextManager::slotRowsInserted() { // We reach this method when rows have been inserted in the model, but views // may not have been updated yet and thus do not have the matching items. // Delay the selection of mUrlToSelect so that the view items exist. // // Without this, when Gwenview is started with an image as argument and the // thumbnail bar is visible, the image will not be selected in the thumbnail // bar. if (d->mUrlToSelect.isValid()) { QMetaObject::invokeMethod(this, "selectUrlToSelect", Qt::QueuedConnection); } } void ContextManager::selectUrlToSelect() { // Because of the queued connection above we might be called several times in a row // In this case we don't want the warning below if (d->mUrlToSelect.isEmpty()) { return; } GV_RETURN_IF_FAIL(d->mUrlToSelect.isValid()); QModelIndex index = d->mDirModel->indexForUrl(d->mUrlToSelect); if (index.isValid()) { d->mSelectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); d->mUrlToSelect = QUrl(); } else if (d->mDirListerFinished) { // Desired URL cannot be found in the directory // Clear the selection to avoid dragging any local files into context // and manually set current URL d->mSelectionModel->clearSelection(); setCurrentUrl(d->mUrlToSelect); } } void ContextManager::slotDirListerRedirection(const QUrl &newUrl) { setCurrentDirUrl(newUrl); } void ContextManager::slotDirListerCompleted() { d->mDirListerFinished = true; } } // namespace diff --git a/lib/documentview/documentviewcontroller.cpp b/lib/documentview/documentviewcontroller.cpp index 895d6398..5cf2d3f6 100644 --- a/lib/documentview/documentviewcontroller.cpp +++ b/lib/documentview/documentviewcontroller.cpp @@ -1,281 +1,280 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2011 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "documentviewcontroller.h" // Local #include "abstractdocumentviewadapter.h" #include "documentview.h" #include #include #include // KDE #include #include // Qt #include #include #include -#include namespace Gwenview { /** * A simple container which: * - Horizontally center the tool widget * - Provide a darker background */ class ToolContainerContent : public QWidget { public: explicit ToolContainerContent(QWidget* parent = nullptr) : QWidget(parent) , mLayout(new QHBoxLayout(this)) { mLayout->setMargin(0); setAutoFillBackground(true); setBackgroundRole(QPalette::Mid); } void setToolWidget(QWidget* widget) { mLayout->addWidget(widget, 0, Qt::AlignCenter); setFixedHeight(widget->sizeHint().height()); } private: QHBoxLayout* mLayout; }; struct DocumentViewControllerPrivate { DocumentViewController* q; KActionCollection* mActionCollection; DocumentView* mView; ZoomWidget* mZoomWidget; SlideContainer* mToolContainer; ToolContainerContent* mToolContainerContent; QAction * mZoomToFitAction; QAction * mZoomToFillAction; QAction * mActualSizeAction; QAction * mZoomInAction; QAction * mZoomOutAction; QList mActions; void setupActions() { KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), mActionCollection); mZoomToFitAction = view->addAction(QStringLiteral("view_zoom_to_fit")); view->collection()->setDefaultShortcut(mZoomToFitAction, Qt::Key_F); mZoomToFitAction->setCheckable(true); mZoomToFitAction->setChecked(true); mZoomToFitAction->setText(i18n("Zoom to fit")); mZoomToFitAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); mZoomToFitAction->setIconText(i18nc("@action:button Zoom to fit, shown in status bar, keep it short please", "Fit")); mZoomToFillAction = view->addAction(QStringLiteral("view_zoom_to_fill")); view->collection()->setDefaultShortcut(mZoomToFillAction, Qt::SHIFT + Qt::Key_F); mZoomToFillAction->setCheckable(true); mZoomToFillAction->setText(i18n("Zoom to fill window by fitting to width or height")); mZoomToFillAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-fit-best"))); mZoomToFillAction->setIconText(i18nc("@action:button Zoom to fill (fit width or height), shown in status bar, keep it short please", "Fill")); mActualSizeAction = view->addAction(KStandardAction::ActualSize); mActualSizeAction->setCheckable(true); mActualSizeAction->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); mActualSizeAction->setIconText(i18nc("@action:button Zoom to original size, shown in status bar, keep it short please", "100%")); mZoomInAction = view->addAction(KStandardAction::ZoomIn); mZoomOutAction = view->addAction(KStandardAction::ZoomOut); mActions << mZoomToFitAction << mActualSizeAction << mZoomInAction << mZoomOutAction << mZoomToFillAction; } void connectZoomWidget() { if (!mZoomWidget || !mView) { return; } QObject::connect(mZoomWidget, &ZoomWidget::zoomChanged, mView, &DocumentView::setZoom); QObject::connect(mView, &DocumentView::minimumZoomChanged, mZoomWidget, &ZoomWidget::setMinimumZoom); QObject::connect(mView, &DocumentView::zoomChanged, mZoomWidget, &ZoomWidget::setZoom); mZoomWidget->setMinimumZoom(mView->minimumZoom()); mZoomWidget->setZoom(mView->zoom()); } void updateZoomWidgetVisibility() { if (!mZoomWidget) { return; } mZoomWidget->setVisible(mView && mView->canZoom()); } void updateActions() { const bool enabled = mView && mView->isVisible() && mView->canZoom(); Q_FOREACH(QAction * action, mActions) { action->setEnabled(enabled); } } }; DocumentViewController::DocumentViewController(KActionCollection* actionCollection, QObject* parent) : QObject(parent) , d(new DocumentViewControllerPrivate) { d->q = this; d->mActionCollection = actionCollection; d->mView = nullptr; d->mZoomWidget = nullptr; d->mToolContainer = nullptr; d->mToolContainerContent = new ToolContainerContent; d->setupActions(); } DocumentViewController::~DocumentViewController() { delete d; } void DocumentViewController::setView(DocumentView* view) { // Forget old view if (d->mView) { disconnect(d->mView, nullptr, this, nullptr); Q_FOREACH(QAction * action, d->mActions) { disconnect(action, nullptr, d->mView, nullptr); } disconnect(d->mZoomWidget, nullptr, d->mView, nullptr); } // Connect new view d->mView = view; if (!d->mView) { return; } connect(d->mView, &DocumentView::adapterChanged, this, &DocumentViewController::slotAdapterChanged); connect(d->mView, &DocumentView::zoomToFitChanged, this, &DocumentViewController::updateZoomToFitActionFromView); connect(d->mView, &DocumentView::zoomToFillChanged, this, &DocumentViewController::updateZoomToFillActionFromView); connect(d->mView, &DocumentView::currentToolChanged, this, &DocumentViewController::updateTool); connect(d->mZoomToFitAction, &QAction::triggered, d->mView, &DocumentView::toggleZoomToFit); connect(d->mZoomToFillAction, &QAction::triggered, d->mView, &DocumentView::toggleZoomToFill); connect(d->mActualSizeAction, SIGNAL(triggered()), d->mView, SLOT(zoomActualSize())); connect(d->mZoomInAction, SIGNAL(triggered()), d->mView, SLOT(zoomIn())); connect(d->mZoomOutAction, SIGNAL(triggered()), d->mView, SLOT(zoomOut())); d->updateActions(); updateZoomToFitActionFromView(); updateZoomToFillActionFromView(); updateTool(); // Sync zoom widget d->connectZoomWidget(); d->updateZoomWidgetVisibility(); } DocumentView* DocumentViewController::view() const { return d->mView; } void DocumentViewController::setZoomWidget(ZoomWidget* widget) { d->mZoomWidget = widget; d->mZoomWidget->setActions( d->mZoomToFitAction, d->mActualSizeAction, d->mZoomInAction, d->mZoomOutAction, d->mZoomToFillAction ); d->mZoomWidget->setMaximumZoom(qreal(DocumentView::MaximumZoom)); d->connectZoomWidget(); d->updateZoomWidgetVisibility(); } ZoomWidget* DocumentViewController::zoomWidget() const { return d->mZoomWidget; } void DocumentViewController::slotAdapterChanged() { d->updateActions(); d->updateZoomWidgetVisibility(); } void DocumentViewController::updateZoomToFitActionFromView() { d->mZoomToFitAction->setChecked(d->mView->zoomToFit()); } void DocumentViewController::updateZoomToFillActionFromView() { d->mZoomToFillAction->setChecked(d->mView->zoomToFill()); } void DocumentViewController::updateTool() { if (!d->mToolContainer) { return; } AbstractRasterImageViewTool* tool = d->mView->currentTool(); if (tool && tool->widget()) { // Use a QueuedConnection to ensure the size of the view has been // updated by the time the slot is called. connect(d->mToolContainer, &SlideContainer::slidedIn, tool, &AbstractRasterImageViewTool::onWidgetSlidedIn, Qt::QueuedConnection); d->mToolContainerContent->setToolWidget(tool->widget()); d->mToolContainer->slideIn(); } else { d->mToolContainer->slideOut(); } } void DocumentViewController::reset() { setView(nullptr); d->updateActions(); } void DocumentViewController::setToolContainer(SlideContainer* container) { d->mToolContainer = container; container->setContent(d->mToolContainerContent); } } // namespace diff --git a/lib/slidecontainer.h b/lib/slidecontainer.h index a1dea292..f386808e 100644 --- a/lib/slidecontainer.h +++ b/lib/slidecontainer.h @@ -1,101 +1,100 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SLIDECONTAINER_H #define SLIDECONTAINER_H // Qt #include -#include #include #include class QPropertyAnimation; namespace Gwenview { /** * This widget is design to contain one child widget, the "content" widget. * It will start hidden by default. Calling slideIn() will slide in the content * widget from the top border. Calling slideOut() will slide it out. */ class GWENVIEWLIB_EXPORT SlideContainer : public QFrame { Q_OBJECT Q_PROPERTY(int slideHeight READ slideHeight WRITE setSlideHeight) public: explicit SlideContainer(QWidget* parent = nullptr); /** * Returns the content widget */ QWidget* content() const; /** * Defines the content widget */ void setContent(QWidget* content); QSize sizeHint() const override; QSize minimumSizeHint() const override; int slideHeight() const; Q_INVOKABLE void setSlideHeight(int height); public Q_SLOTS: /** * Slides the content widget in. * Calling it multiple times won't cause the animation to be replayed. */ void slideIn(); /** * Slides the content widget out. * Calling it multiple times won't cause the animation to be replayed. */ void slideOut(); Q_SIGNALS: void slidedIn(); void slidedOut(); protected: void resizeEvent(QResizeEvent*) override; bool eventFilter(QObject*, QEvent* event) override; private Q_SLOTS: void slotAnimFinished(); private: QPointer mContent; QPointer mAnim; bool mSlidingOut; void adjustContentGeometry(); void animTo(int height); }; } /* namespace */ #endif /* SLIDECONTAINER_H */ diff --git a/tests/auto/contextmanagertest.cpp b/tests/auto/contextmanagertest.cpp index c24bcac7..50917ec7 100644 --- a/tests/auto/contextmanagertest.cpp +++ b/tests/auto/contextmanagertest.cpp @@ -1,117 +1,117 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2013 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Self #include "contextmanagertest.h" // Local #include #include #include // Qt #include #include // KDE #include #include #include using namespace Gwenview; using namespace TestUtils; QTEST_MAIN(ContextManagerTest) void ContextManagerTest::testRemove() { // When the current image is removed Gwenview must go to the next image if // there is any, otherwise to the previous image. SandBoxDir sandBox; sandBox.fill(QStringList() << "a" << "b" << "c"); QUrl dirUrl = QUrl::fromLocalFile(sandBox.absolutePath()); SortedDirModel dirModel; { QEventLoop loop; connect(dirModel.dirLister(), SIGNAL(completed()), &loop, SLOT(quit())); dirModel.dirLister()->openUrl(dirUrl); loop.exec(); } QCOMPARE(dirModel.rowCount(), 3); ContextManager manager(&dirModel, 0); // Select second row manager.selectionModel()->setCurrentIndex(dirModel.index(1, 0), QItemSelectionModel::Select); // Remove "b", `manager` should select "c" - sandBox.remove("b"); + sandBox.remove('b'); dirModel.dirLister()->updateDirectory(dirUrl); while (dirModel.rowCount() == 3) { QTest::qWait(100); } QModelIndex currentIndex = manager.selectionModel()->currentIndex(); QCOMPARE(currentIndex.row(), 1); QCOMPARE(currentIndex.data(Qt::DisplayRole).toString(), QStringLiteral("c")); // Remove "c", `manager` should select "a" - sandBox.remove("c"); + sandBox.remove('c'); dirModel.dirLister()->updateDirectory(dirUrl); while (dirModel.rowCount() == 2) { QTest::qWait(100); } currentIndex = manager.selectionModel()->currentIndex(); QCOMPARE(currentIndex.row(), 0); QCOMPARE(currentIndex.data(Qt::DisplayRole).toString(), QStringLiteral("a")); } void ContextManagerTest::testInvalidDirUrl() { class DirLister : public KDirLister { public: DirLister() : mOpenUrlCalled(false) { setAutoErrorHandlingEnabled(false, nullptr); } bool openUrl(const QUrl &url, OpenUrlFlags flags = NoFlags) override { mOpenUrlCalled = true; return KDirLister::openUrl(url, flags); } bool mOpenUrlCalled; }; SortedDirModel dirModel; DirLister* dirLister = new DirLister; dirModel.setDirLister(dirLister); ContextManager manager(&dirModel, nullptr); manager.setCurrentDirUrl(QUrl()); QVERIFY(!dirLister->mOpenUrlCalled); } diff --git a/tests/auto/placetreemodeltest.cpp b/tests/auto/placetreemodeltest.cpp index 7fa01b71..b0d850ba 100644 --- a/tests/auto/placetreemodeltest.cpp +++ b/tests/auto/placetreemodeltest.cpp @@ -1,168 +1,167 @@ /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "placetreemodeltest.h" // Qt #include #include // KDE #include #include -#include #include #include // Local #include "../lib/placetreemodel.h" #include "testutils.h" //#define KEEP_TEMP_DIR QTEST_MAIN(PlaceTreeModelTest) using namespace Gwenview; const char* BOOKMARKS_XML = "" "" "" " " " url1" " " " " " " " " " " " 1214343736/0" " true" " " " " " " " " " url2" " " " " " " " " " " " 1214343736/1" " true" " " " " " " ""; void PlaceTreeModelTest::initTestCase() { Q_ASSERT(mTempDir.isValid()); QDir dir(mTempDir.path()); const bool dir1created = dir.mkdir("url1"); Q_ASSERT(dir1created); Q_UNUSED(dir1created); mUrl1 = QUrl::fromLocalFile(dir.filePath("url1")); const bool dir2created = dir.mkdir("url2"); Q_ASSERT(dir2created); Q_UNUSED(dir2created); mUrl2 = QUrl::fromLocalFile(dir.filePath("url2")); mUrl1Dirs << "aaa" << "zzz" << "bbb"; Q_FOREACH(const QString & dirName, mUrl1Dirs) { dir.mkdir("url1/" + dirName); } #ifdef KEEP_TEMP_DIR mTempDir.setAutoRemove(false); //qDebug() << "mTempDir:" << mTempDir.name(); #endif } void PlaceTreeModelTest::init() { QStandardPaths::setTestModeEnabled(true); TestUtils::purgeUserConfiguration(); const QString confDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); QDir().mkpath(confDir); QFile bookmark(confDir + "/user-places.xbel"); const bool bookmarkOpened = bookmark.open(QIODevice::WriteOnly); Q_ASSERT(bookmarkOpened); Q_UNUSED(bookmarkOpened); QString xml = QString(BOOKMARKS_XML) .arg(mUrl1.url()) .arg(mUrl2.url()) ; bookmark.write(xml.toUtf8()); #ifdef KEEP_TEMP_DIR mTempDir.setAutoRemove(false); //qDebug() << "mTempDir:" << mTempDir.name(); #endif } void PlaceTreeModelTest::testListPlaces() { PlaceTreeModel model(nullptr); #if KIO_VERSION >= QT_VERSION_CHECK(5, 45, 0) QCOMPARE(model.rowCount(), 8); #else QCOMPARE(model.rowCount(), 10); #endif QModelIndex index; index = model.index(0, 0); QCOMPARE(model.urlForIndex(index), mUrl1); index = model.index(1, 0); QCOMPARE(model.urlForIndex(index), mUrl2); } void PlaceTreeModelTest::testListUrl1() { PlaceTreeModel model(nullptr); QModelIndex index = model.index(0, 0); QCOMPARE(model.urlForIndex(index), mUrl1); // We should not have fetched content yet QCOMPARE(model.rowCount(index), 0); QVERIFY(model.canFetchMore(index)); while (model.canFetchMore(index)) { model.fetchMore(index); } QTest::qWait(1000); QCOMPARE(model.rowCount(index), mUrl1Dirs.length()); QStringList dirs = mUrl1Dirs; dirs.sort(); for (int row = 0; row < dirs.count(); ++row) { QModelIndex subIndex = model.index(row, 0, index); QVERIFY(subIndex.isValid()); QString dirName = model.data(subIndex).toString(); QCOMPARE(dirName, dirs.value(row)); } } diff --git a/tests/manual/thumbnailgen.cpp b/tests/manual/thumbnailgen.cpp index f0c7f994..4a5d3270 100644 --- a/tests/manual/thumbnailgen.cpp +++ b/tests/manual/thumbnailgen.cpp @@ -1,128 +1,128 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2012 Aurélien Gâteau This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Local #include #include <../auto/testutils.h> #include // KDE #include #include // Qt #include #include #include #include using namespace Gwenview; int main(int argc, char** argv) { KLocalizedString::setApplicationDomain("thumbnailgen"); QScopedPointer aboutData( Gwenview::createAboutData( QStringLiteral("thumbnailgen"), /* component name */ i18n("thumbnailgen") /* display name */ )); QApplication app(argc, argv); QCommandLineParser parser; aboutData->setupCommandLine(&parser); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("image-dir", i18n("Image dir to open")); parser.addPositionalArgument("size", i18n("What size of thumbnails to generate. Can be either 'normal' or 'large'")); parser.addOption(QCommandLineOption(QStringList() << QStringLiteral("t") << QStringLiteral("thumbnail-dir"), i18n("Use instead of ~/.thumbnails to store thumbnails"), "thumbnail-dir")); parser.process(app); aboutData->processCommandLine(&parser); // Read cmdline options QStringList args = parser.positionalArguments(); if (args.count() != 2) { qFatal("Wrong number of arguments"); return 1; } QString imageDirName = args.first(); ThumbnailGroup::Enum group = ThumbnailGroup::Normal; if (args.last() == "large") { group = ThumbnailGroup::Large; } else if (args.last() == "normal") { // group is already set to the right value } else { qFatal("Invalid thumbnail size: %s", qPrintable(args.last())); } QString thumbnailBaseDirName = parser.value("thumbnail-dir"); // Set up thumbnail base dir if (!thumbnailBaseDirName.isEmpty()) { QDir dir = QDir(thumbnailBaseDirName); thumbnailBaseDirName = dir.absolutePath(); if (!dir.exists()) { bool ok = QDir::root().mkpath(thumbnailBaseDirName); if (!ok) { qFatal("Could not create %s", qPrintable(thumbnailBaseDirName)); return 1; } } - if (!thumbnailBaseDirName.endsWith("/")) { - thumbnailBaseDirName += "/"; + if (!thumbnailBaseDirName.endsWith('/')) { + thumbnailBaseDirName += '/'; } ThumbnailProvider::setThumbnailBaseDir(thumbnailBaseDirName); } // List dir QDir dir(imageDirName); KFileItemList list; Q_FOREACH(const QString &name, dir.entryList()) { QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(name)); KFileItem item(url); list << item; } qWarning() << "Generating thumbnails for" << list.count() << "files"; // Start the job QTime chrono; ThumbnailProvider job; job.setThumbnailGroup(group); job.appendItems(list); chrono.start(); QEventLoop loop; QObject::connect(&job, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); qWarning() << "Time to generate thumbnails:" << chrono.restart(); waitForDeferredDeletes(); while (!ThumbnailProvider::isThumbnailWriterEmpty()) { QCoreApplication::processEvents(); } qWarning() << "Time to save pending thumbnails:" << chrono.restart(); return 0; }