diff --git a/app/browsemainpage.cpp b/app/browsemainpage.cpp index a12febb7..33ffecd1 100644 --- a/app/browsemainpage.cpp +++ b/app/browsemainpage.cpp @@ -1,574 +1,574 @@ // 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 "browsemainpage.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include #include // Local #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include "lib/semanticinfo/semanticinfodirmodel.h" #endif namespace Gwenview { inline Sorting::Enum sortingFromSortAction(const QAction* action) { Q_ASSERT(action); return Sorting::Enum(action->data().toInt()); } struct BrowseMainPagePrivate : public Ui_BrowseMainPage { BrowseMainPage* q; GvCore* mGvCore; KFilePlacesModel* mFilePlacesModel; KUrlNavigator* mUrlNavigator; SortedDirModel* mDirModel; int mDocumentCountImages; int mDocumentCountVideos; KFileItemList* mSelectedMediaItems; KActionCollection* mActionCollection; FilterController* mFilterController; QActionGroup* mSortAction; KToggleAction* mSortDescendingAction; QActionGroup* mThumbnailDetailsActionGroup; PreviewItemDelegate* mDelegate; void setupWidgets() { setupUi(q); - q->layout()->setMargin(0); + q->layout()->setContentsMargins(0, 0, 0, 0); // mThumbnailView mThumbnailView->setModel(mDirModel); mDelegate = new PreviewItemDelegate(mThumbnailView); mThumbnailView->setItemDelegate(mDelegate); mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); // mUrlNavigator (use stupid layouting code because KUrlNavigator ctor // can't be used directly from Designer) mFilePlacesModel = new KFilePlacesModel(q); mUrlNavigator = new KUrlNavigator(mFilePlacesModel, QUrl(), mUrlNavigatorContainer); mUrlNavigatorContainer->setAutoFillBackground(true); mUrlNavigatorContainer->setBackgroundRole(QPalette::Mid); QVBoxLayout* layout = new QVBoxLayout(mUrlNavigatorContainer); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(mUrlNavigator); QObject::connect(mUrlNavigator, SIGNAL(urlsDropped(QUrl,QDropEvent*)), q, SLOT(slotUrlsDropped(QUrl,QDropEvent*))); // FullScreen Toolbar mFullScreenToolBar->setVisible(false); mFullScreenToolBar2->setVisible(false); mFullScreenToolBar->setAutoFillBackground(true); mFullScreenToolBar2->setAutoFillBackground(true); mFullScreenToolBar->setBackgroundRole(QPalette::Mid); mFullScreenToolBar2->setBackgroundRole(QPalette::Mid); // Thumbnail slider QObject::connect(mThumbnailSlider, &ZoomSlider::valueChanged, mThumbnailView, &ThumbnailView::setThumbnailWidth); QObject::connect(mThumbnailView, &ThumbnailView::thumbnailWidthChanged, mThumbnailSlider, &ZoomSlider::setValue); // Document count label QMargins labelMargins = mDocumentCountLabel->contentsMargins(); labelMargins.setLeft(15); labelMargins.setRight(15); mDocumentCountLabel->setContentsMargins(labelMargins); } QAction *thumbnailDetailAction(const QString &text, PreviewItemDelegate::ThumbnailDetail detail) { QAction *action = new QAction(q); action->setText(text); action->setCheckable(true); action->setChecked(GwenviewConfig::thumbnailDetails() & detail); action->setData(QVariant(detail)); mThumbnailDetailsActionGroup->addAction(action); QObject::connect(action, SIGNAL(triggered(bool)), q, SLOT(updateThumbnailDetails())); return action; } void setupActions(KActionCollection* actionCollection) { KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); QAction * action = view->addAction("edit_location", q, SLOT(editLocation())); action->setText(i18nc("@action:inmenu Navigation Bar", "Edit Location")); actionCollection->setDefaultShortcut(action, Qt::Key_F6); KActionMenu* sortActionMenu = view->add("sort_by"); sortActionMenu->setText(i18nc("@action:inmenu", "Sort By")); mSortAction = new QActionGroup(actionCollection); action = new QAction(i18nc("@addAction:inmenu", "Name"), mSortAction); action->setCheckable(true); action->setData(QVariant(Sorting::Name)); action = new QAction(i18nc("@addAction:inmenu", "Date"), mSortAction); action->setCheckable(true); action->setData(QVariant(Sorting::Date)); action = new QAction(i18nc("@addAction:inmenu", "Size"), mSortAction); action->setCheckable(true); action->setData(QVariant(Sorting::Size)); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE action = new QAction(i18nc("@addAction:inmenu", "Rating"), mSortAction); action->setCheckable(true); action->setData(QVariant(Sorting::Rating)); #endif QObject::connect(mSortAction, SIGNAL(triggered(QAction*)), q, SLOT(updateSortOrder())); mSortDescendingAction = view->add("sort_desc"); mSortDescendingAction->setText(i18nc("@action:inmenu Sort", "Descending")); QObject::connect(mSortDescendingAction, SIGNAL(toggled(bool)), q, SLOT(updateSortOrder())); for(auto action : mSortAction->actions()) { sortActionMenu->addAction(action); } sortActionMenu->addSeparator(); sortActionMenu->addAction(mSortDescendingAction); mThumbnailDetailsActionGroup = new QActionGroup(q); mThumbnailDetailsActionGroup->setExclusive(false); KActionMenu* thumbnailDetailsAction = view->add("thumbnail_details"); thumbnailDetailsAction->setText(i18nc("@action:inmenu", "Thumbnail Details")); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Filename"), PreviewItemDelegate::FileNameDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Date"), PreviewItemDelegate::DateDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Image Size"), PreviewItemDelegate::ImageSizeDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "File Size"), PreviewItemDelegate::FileSizeDetail)); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Rating"), PreviewItemDelegate::RatingDetail)); #endif KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); action = file->addAction("add_folder_to_places", q, SLOT(addFolderToPlaces())); action->setText(i18nc("@action:inmenu", "Add Folder to Places")); } void setupFilterController() { QMenu* menu = new QMenu(mAddFilterButton); mFilterController = new FilterController(mFilterFrame, mDirModel); Q_FOREACH(QAction * action, mFilterController->actionList()) { menu->addAction(action); } mAddFilterButton->setMenu(menu); } void setupFullScreenToolBar() { mFullScreenToolBar->setIconDimensions(KIconLoader::SizeMedium); mFullScreenToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); mFullScreenToolBar->addAction(mActionCollection->action("browse")); mFullScreenToolBar->addAction(mActionCollection->action("view")); mFullScreenToolBar2->setIconDimensions(KIconLoader::SizeMedium); mFullScreenToolBar2->setToolButtonStyle(Qt::ToolButtonIconOnly); mFullScreenToolBar2->addAction(mActionCollection->action("leave_fullscreen")); } void updateSelectedMediaItems(const QItemSelection& selected, const QItemSelection& deselected) { for (auto index : selected.indexes()) { KFileItem item = mDirModel->itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { mSelectedMediaItems->append(item); } } for (auto index : deselected.indexes()) { KFileItem item = mDirModel->itemForIndex(index); mSelectedMediaItems->removeOne(item); } } void updateDocumentCountLabel() { if (mSelectedMediaItems->count() > 1) { KIO::filesize_t totalSize = 0; for (const auto &item : *mSelectedMediaItems) { totalSize += item.size(); } const QString text = i18nc("@info:status %1 number of selected documents, %2 total number of documents, %3 total filesize of selected documents", "Selected %1 of %2 (%3)", mSelectedMediaItems->count(), mDocumentCountImages + mDocumentCountVideos, KFormat().formatByteSize(totalSize)); mDocumentCountLabel->setText(text); } else { const QString imageText = i18ncp("@info:status Image files", "%1 image", "%1 images", mDocumentCountImages); const QString videoText = i18ncp("@info:status Video files", "%1 video", "%1 videos", mDocumentCountVideos); QString labelText; if (mDocumentCountImages > 0 && mDocumentCountVideos == 0) { labelText = imageText; } else if (mDocumentCountImages == 0 && mDocumentCountVideos > 0) { labelText = videoText; } else { labelText = i18nc("@info:status images, videos", "%1, %2", imageText, videoText); } mDocumentCountLabel->setText(labelText); } } void documentCountsForIndexRange(const QModelIndex& parent, int start, int end, int& imageCountOut, int& videoCountOut) { imageCountOut = 0; videoCountOut = 0; for (int row = start; row <= end; ++row) { QModelIndex index = mDirModel->index(row, 0, parent); KFileItem item = mDirModel->itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { MimeTypeUtils::Kind kind = MimeTypeUtils::mimeTypeKind(item.mimetype()); if (kind == MimeTypeUtils::KIND_RASTER_IMAGE || kind == MimeTypeUtils::KIND_SVG_IMAGE) { imageCountOut++; } else if (kind == MimeTypeUtils::KIND_VIDEO) { videoCountOut++; } } } } void updateContextBarActions() { PreviewItemDelegate::ContextBarActions actions; switch (GwenviewConfig::thumbnailActions()) { case ThumbnailActions::None: actions = PreviewItemDelegate::NoAction; break; case ThumbnailActions::ShowSelectionButtonOnly: actions = PreviewItemDelegate::SelectionAction; break; case ThumbnailActions::AllButtons: default: actions = PreviewItemDelegate::SelectionAction | PreviewItemDelegate::RotateAction; if (!q->window()->isFullScreen()) { actions |= PreviewItemDelegate::FullScreenAction; } break; } mDelegate->setContextBarActions(actions); } void applyPalette(bool fullScreenmode) { q->setPalette(mGvCore->palette(fullScreenmode ? GvCore::FullScreenPalette : GvCore::NormalPalette)); mThumbnailView->setPalette(mGvCore->palette(fullScreenmode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); } }; BrowseMainPage::BrowseMainPage(QWidget* parent, KActionCollection* actionCollection, GvCore* gvCore) : QWidget(parent) , d(new BrowseMainPagePrivate) { d->q = this; d->mGvCore = gvCore; d->mDirModel = gvCore->sortedDirModel(); d->mDocumentCountImages = 0; d->mDocumentCountVideos = 0; d->mSelectedMediaItems = new KFileItemList; d->mActionCollection = actionCollection; d->setupWidgets(); d->setupActions(actionCollection); d->setupFilterController(); loadConfig(); updateSortOrder(); updateThumbnailDetails(); // Set up connections for document count connect(d->mDirModel, &SortedDirModel::rowsInserted, this, &BrowseMainPage::slotDirModelRowsInserted); connect(d->mDirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &BrowseMainPage::slotDirModelRowsAboutToBeRemoved); connect(d->mDirModel, &SortedDirModel::modelReset, this, &BrowseMainPage::slotDirModelReset); connect(thumbnailView(), &ThumbnailView::selectionChangedSignal, this, &BrowseMainPage::slotSelectionChanged); installEventFilter(this); } BrowseMainPage::~BrowseMainPage() { d->mSelectedMediaItems->clear(); delete d->mSelectedMediaItems; delete d; } void BrowseMainPage::loadConfig() { d->applyPalette(window()->isFullScreen()); d->mUrlNavigator->setUrlEditable(GwenviewConfig::urlNavigatorIsEditable()); d->mUrlNavigator->setShowFullPath(GwenviewConfig::urlNavigatorShowFullPath()); d->mThumbnailSlider->setValue(GwenviewConfig::thumbnailSize()); d->mThumbnailSlider->updateToolTip(); // If GwenviewConfig::thumbnailSize() returns the current value of // mThumbnailSlider, it won't emit valueChanged() and the thumbnail view // won't be updated. That's why we do it ourself. d->mThumbnailView->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); d->mThumbnailView->setThumbnailWidth(GwenviewConfig::thumbnailSize()); Q_FOREACH(QAction * action, d->mSortAction->actions()) { if (sortingFromSortAction(action) == GwenviewConfig::sorting()) { action->setChecked(true); break; } } d->mSortDescendingAction->setChecked(GwenviewConfig::sortDescending()); d->updateContextBarActions(); } void BrowseMainPage::saveConfig() const { GwenviewConfig::setUrlNavigatorIsEditable(d->mUrlNavigator->isUrlEditable()); GwenviewConfig::setUrlNavigatorShowFullPath(d->mUrlNavigator->showFullPath()); GwenviewConfig::setThumbnailSize(d->mThumbnailSlider->value()); GwenviewConfig::setSorting(sortingFromSortAction(d->mSortAction->checkedAction())); GwenviewConfig::setSortDescending(d->mSortDescendingAction->isChecked()); GwenviewConfig::setThumbnailDetails(d->mDelegate->thumbnailDetails()); } bool BrowseMainPage::eventFilter(QObject* watched, QEvent* event) { if (window()->isFullScreen() && event->type() == QEvent::ShortcutOverride) { const QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Escape) { d->mActionCollection->action("leave_fullscreen")->trigger(); event->accept(); } } return QWidget::eventFilter(watched, event); } void BrowseMainPage::mousePressEvent(QMouseEvent* event) { switch (event->button()) { case Qt::ForwardButton: case Qt::BackButton: return; default: QWidget::mousePressEvent(event); } } ThumbnailView* BrowseMainPage::thumbnailView() const { return d->mThumbnailView; } KUrlNavigator* BrowseMainPage::urlNavigator() const { return d->mUrlNavigator; } void BrowseMainPage::reload() { QModelIndexList list = d->mThumbnailView->selectionModel()->selectedIndexes(); Q_FOREACH(const QModelIndex & index, list) { d->mThumbnailView->reloadThumbnail(index); } d->mDirModel->reload(); } void BrowseMainPage::editLocation() { d->mUrlNavigator->setUrlEditable(true); d->mUrlNavigator->setFocus(); } void BrowseMainPage::addFolderToPlaces() { QUrl url = d->mUrlNavigator->locationUrl(); QString text = url.adjusted(QUrl::StripTrailingSlash).fileName(); if (text.isEmpty()) { text = url.toDisplayString(); } d->mFilePlacesModel->addPlace(text, url); } void BrowseMainPage::slotDirModelRowsInserted(const QModelIndex& parent, int start, int end) { int imageCount; int videoCount; d->documentCountsForIndexRange(parent, start, end, imageCount, videoCount); if (imageCount > 0 || videoCount > 0) { d->mDocumentCountImages += imageCount; d->mDocumentCountVideos += videoCount; d->updateDocumentCountLabel(); } } void BrowseMainPage::slotDirModelRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { int imageCount; int videoCount; d->documentCountsForIndexRange(parent, start, end, imageCount, videoCount); if (imageCount > 0 || videoCount > 0) { d->mDocumentCountImages -= imageCount; d->mDocumentCountVideos -= videoCount; d->updateDocumentCountLabel(); } } void BrowseMainPage::slotDirModelReset() { d->mDocumentCountImages = 0; d->mDocumentCountVideos = 0; d->mSelectedMediaItems->clear(); d->updateDocumentCountLabel(); } void BrowseMainPage::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { d->updateSelectedMediaItems(selected, deselected); d->updateDocumentCountLabel(); } void BrowseMainPage::updateSortOrder() { const QAction* action = d->mSortAction->checkedAction(); GV_RETURN_IF_FAIL(action); const Qt::SortOrder order = d->mSortDescendingAction->isChecked() ? Qt::DescendingOrder : Qt::AscendingOrder; KDirModel::ModelColumns column; int sortRole; // Map Sorting::Enum to model columns and sorting roles switch (sortingFromSortAction(action)) { case Sorting::Name: column = KDirModel::Name; sortRole = Qt::DisplayRole; break; case Sorting::Size: column = KDirModel::Size; sortRole = Qt::DisplayRole; break; case Sorting::Date: column = KDirModel::ModifiedTime; sortRole = Qt::DisplayRole; break; #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE case Sorting::Rating: column = KDirModel::Name; sortRole = SemanticInfoDirModel::RatingRole; break; #endif } d->mDirModel->setSortRole(sortRole); d->mDirModel->sort(column, order); } void BrowseMainPage::updateThumbnailDetails() { PreviewItemDelegate::ThumbnailDetails details = 0; Q_FOREACH(const QAction * action, d->mThumbnailDetailsActionGroup->actions()) { if (action->isChecked()) { details |= PreviewItemDelegate::ThumbnailDetail(action->data().toInt()); } } d->mDelegate->setThumbnailDetails(details); } void BrowseMainPage::setFullScreenMode(bool fullScreen) { d->applyPalette(fullScreen); d->mUrlNavigatorContainer->setContentsMargins( fullScreen ? 6 : 0, 0, 0, 0); d->updateContextBarActions(); d->mFullScreenToolBar->setVisible(fullScreen); d->mFullScreenToolBar2->setVisible(fullScreen); if (fullScreen && d->mFullScreenToolBar->actions().isEmpty()) { d->setupFullScreenToolBar(); } } void BrowseMainPage::setStatusBarVisible(bool visible) { d->mStatusBarContainer->setVisible(visible); } void BrowseMainPage::slotUrlsDropped(const QUrl &destUrl, QDropEvent* event) { const QList urlList = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (urlList.isEmpty()) { return; } event->acceptProposedAction(); // We can't call FileOperations::showMenuForDroppedUrls() directly because // we need the slot to return so that the drop event is accepted. Otherwise // the drop cursor is still visible when the menu is shown. QMetaObject::invokeMethod(this, "showMenuForDroppedUrls", Qt::QueuedConnection, Q_ARG(QList, urlList), Q_ARG(QUrl, destUrl)); } void BrowseMainPage::showMenuForDroppedUrls(const QList& urlList, const QUrl &destUrl) { FileOperations::showMenuForDroppedUrls(d->mUrlNavigator, urlList, destUrl); } QToolButton* BrowseMainPage::toggleSideBarButton() const { return d->mToggleSideBarButton; } } // namespace diff --git a/app/configdialog.cpp b/app/configdialog.cpp index 1dc250b1..79dc59dc 100644 --- a/app/configdialog.cpp +++ b/app/configdialog.cpp @@ -1,133 +1,133 @@ // 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 "configdialog.h" // Qt // KDE #include // Local #include #include namespace Gwenview { template QWidget* setupPage(Ui& ui) { QWidget* widget = new QWidget; ui.setupUi(widget); - widget->layout()->setMargin(0); + widget->layout()->setContentsMargins(0, 0, 0, 0); return widget; } ConfigDialog::ConfigDialog(QWidget* parent) : KConfigDialog(parent, "Settings", GwenviewConfig::self()) { setFaceType(KPageDialog::List); QWidget* widget; KPageWidgetItem* pageItem; // General widget = setupPage(mGeneralConfigPage); mThumbnailActionsGroup = new InvisibleButtonGroup(widget); mThumbnailActionsGroup->setObjectName(QLatin1String("kcfg_ThumbnailActions")); mThumbnailActionsGroup->addButton(mGeneralConfigPage.allButtonsThumbnailActionsRadioButton, int(ThumbnailActions::AllButtons)); mThumbnailActionsGroup->addButton(mGeneralConfigPage.selectionOnlyThumbnailActionsRadioButton, int(ThumbnailActions::ShowSelectionButtonOnly)); mThumbnailActionsGroup->addButton(mGeneralConfigPage.noneThumbnailActionsRadioButton, int(ThumbnailActions::None)); pageItem = addPage(widget, i18n("General")); pageItem->setIcon(QIcon::fromTheme("gwenview")); connect(mGeneralConfigPage.kcfg_ViewBackgroundValue, &QAbstractSlider::valueChanged, this, &ConfigDialog::updateViewBackgroundFrame); // Image View widget = setupPage(mImageViewConfigPage); mAlphaBackgroundModeGroup = new InvisibleButtonGroup(widget); mAlphaBackgroundModeGroup->setObjectName(QLatin1String("kcfg_AlphaBackgroundMode")); mAlphaBackgroundModeGroup->addButton(mImageViewConfigPage.surroundingRadioButton, int(AbstractImageView::AlphaBackgroundNone)); mAlphaBackgroundModeGroup->addButton(mImageViewConfigPage.checkBoardRadioButton, int(AbstractImageView::AlphaBackgroundCheckBoard)); mAlphaBackgroundModeGroup->addButton(mImageViewConfigPage.solidColorRadioButton, int(AbstractImageView::AlphaBackgroundSolid)); mWheelBehaviorGroup = new InvisibleButtonGroup(widget); mWheelBehaviorGroup->setObjectName(QLatin1String("kcfg_MouseWheelBehavior")); mWheelBehaviorGroup->addButton(mImageViewConfigPage.mouseWheelScrollRadioButton, int(MouseWheelBehavior::Scroll)); mWheelBehaviorGroup->addButton(mImageViewConfigPage.mouseWheelBrowseRadioButton, int(MouseWheelBehavior::Browse)); mWheelBehaviorGroup->addButton(mImageViewConfigPage.mouseWheelZoomRadioButton, int(MouseWheelBehavior::Zoom)); mAnimationMethodGroup = new InvisibleButtonGroup(widget); mAnimationMethodGroup->setObjectName(QLatin1String("kcfg_AnimationMethod")); mAnimationMethodGroup->addButton(mImageViewConfigPage.glAnimationRadioButton, int(DocumentView::GLAnimation)); mAnimationMethodGroup->addButton(mImageViewConfigPage.softwareAnimationRadioButton, int(DocumentView::SoftwareAnimation)); mAnimationMethodGroup->addButton(mImageViewConfigPage.noAnimationRadioButton, int(DocumentView::NoAnimation)); mZoomModeGroup = new InvisibleButtonGroup(widget); mZoomModeGroup->setObjectName(QLatin1String("kcfg_ZoomMode")); mZoomModeGroup->addButton(mImageViewConfigPage.autofitZoomModeRadioButton, int(ZoomMode::Autofit)); mZoomModeGroup->addButton(mImageViewConfigPage.keepSameZoomModeRadioButton, int(ZoomMode::KeepSame)); mZoomModeGroup->addButton(mImageViewConfigPage.individualZoomModeRadioButton, int(ZoomMode::Individual)); mThumbnailBarOrientationGroup = new InvisibleButtonGroup(widget); mThumbnailBarOrientationGroup->setObjectName(QLatin1String("kcfg_ThumbnailBarOrientation")); mThumbnailBarOrientationGroup->addButton(mImageViewConfigPage.horizontalRadioButton, int(Qt::Horizontal)); mThumbnailBarOrientationGroup->addButton(mImageViewConfigPage.verticalRadioButton, int(Qt::Vertical)); pageItem = addPage(widget, i18n("Image View")); pageItem->setIcon(QIcon::fromTheme("view-preview")); // Advanced widget = setupPage(mAdvancedConfigPage); mRenderingIntentGroup = new InvisibleButtonGroup(widget); mRenderingIntentGroup->setObjectName(QLatin1String("kcfg_RenderingIntent")); mRenderingIntentGroup->addButton(mAdvancedConfigPage.relativeRenderingIntentRadioButton, int(RenderingIntent::Relative)); mRenderingIntentGroup->addButton(mAdvancedConfigPage.perceptualRenderingIntentRadioButton, int(RenderingIntent::Perceptual)); pageItem = addPage(widget, i18n("Advanced")); pageItem->setIcon(QIcon::fromTheme("preferences-other")); mAdvancedConfigPage.cacheHelpLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); mAdvancedConfigPage.perceptualHelpLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); mAdvancedConfigPage.relativeHelpLabel->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); updateViewBackgroundFrame(); } void ConfigDialog::updateViewBackgroundFrame() { QColor color = QColor::fromHsv(0, 0, mGeneralConfigPage.kcfg_ViewBackgroundValue->value()); QString css = QString( "background-color: %1;" "border-radius: 5px;" "border: 1px solid %1;") .arg(color.name()); // When using Oxygen, setting the background color via palette causes the // pixels outside the frame to be painted with the new background color as // well. Using CSS works more like expected. mGeneralConfigPage.backgroundValueFrame->setStyleSheet(css); } } // namespace diff --git a/app/filtercontroller.cpp b/app/filtercontroller.cpp index 8320055b..c2b249ee 100644 --- a/app/filtercontroller.cpp +++ b/app/filtercontroller.cpp @@ -1,370 +1,370 @@ // 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 // 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->setContentsMargins(0, 0, 0, 0); layout->setSpacing(2); layout->addWidget(mModeComboBox); layout->addWidget(mLineEdit); QTimer* timer = new QTimer(this); timer->setInterval(350); timer->setSingleShot(true); connect(timer, &QTimer::timeout, this, &NameFilterWidget::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->setContentsMargins(0, 0, 0, 0); layout->addWidget(mModeComboBox); layout->addWidget(mDateWidget); connect(mDateWidget, &DateWidget::dateChanged, this, &DateFilterWidget::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->setContentsMargins(0, 0, 0, 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->setContentsMargins(0, 0, 0, 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, &QAbstractButton::clicked, this, &QObject::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()), QKeySequence(Qt::CTRL + Qt::Key_I)); addAction(i18nc("@action:inmenu", "Filter by Date"), SLOT(addFilterByDate()), QKeySequence()); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE addAction(i18nc("@action:inmenu", "Filter by Rating"), SLOT(addFilterByRating()), QKeySequence()); addAction(i18nc("@action:inmenu", "Filter by Tag"), SLOT(addFilterByTag()), QKeySequence()); #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, const QKeySequence shortcut) { QAction* action = new QAction(text, q); action->setShortcut(shortcut); 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, &QObject::destroyed, q, &FilterController::slotFilterWidgetClosed); } } // namespace diff --git a/app/fullscreencontent.cpp b/app/fullscreencontent.cpp index 4aa848e5..c7c6a270 100644 --- a/app/fullscreencontent.cpp +++ b/app/fullscreencontent.cpp @@ -1,572 +1,572 @@ // 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 "fullscreencontent.h" // Qt #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include // Local #include #include #include #include #include #include #include #include #include #include namespace Gwenview { /** * A widget which behaves more or less like a QToolBar, but which uses real * widgets for the toolbar items. We need a real widget to be able to position * the option menu. */ class FullScreenToolBar : public QWidget { public: explicit FullScreenToolBar(QWidget* parent = nullptr) : QWidget(parent) , mLayout(new QHBoxLayout(this)) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); mLayout->setSpacing(0); - mLayout->setMargin(0); + mLayout->setContentsMargins(0, 0, 0, 0); } void addAction(QAction* action) { QToolButton* button = new QToolButton; button->setDefaultAction(action); button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setAutoRaise(true); const int extent = KIconLoader::SizeMedium; button->setIconSize(QSize(extent, extent)); mLayout->addWidget(button); } void addSeparator() { mLayout->addSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); } void addStretch() { mLayout->addStretch(); } void setDirection(QBoxLayout::Direction direction) { mLayout->setDirection(direction); } private: QBoxLayout* mLayout; }; FullScreenContent::FullScreenContent(QObject* parent, GvCore* gvCore) : QObject(parent) { mGvCore = gvCore; mViewPageVisible = false; } void FullScreenContent::init(KActionCollection* actionCollection, QWidget* autoHideParentWidget, SlideShow* slideShow) { mSlideShow = slideShow; mActionCollection = actionCollection; connect(actionCollection->action("view"), &QAction::toggled, this, &FullScreenContent::slotViewModeActionToggled); // mAutoHideContainer mAutoHideContainer = new FullScreenBar(autoHideParentWidget); mAutoHideContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(mAutoHideContainer); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); EventWatcher::install(autoHideParentWidget, QEvent::Resize, this, SLOT(adjustSize())); // mContent mContent = new QWidget; mContent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); mContent->setAutoFillBackground(true); EventWatcher::install(mContent, QEvent::Show, this, SLOT(updateCurrentUrlWidgets())); layout->addWidget(mContent); createOptionsAction(); // mToolBar mToolBar = new FullScreenToolBar(mContent); #define addAction(name) mToolBar->addAction(actionCollection->action(name)) addAction("browse"); addAction("view"); mToolBar->addSeparator(); addAction("go_previous"); addAction("toggle_slideshow"); addAction("go_next"); mToolBar->addSeparator(); addAction("rotate_left"); addAction("rotate_right"); #undef addAction mToolBarShadow = new ShadowFilter(mToolBar); // mInformationLabel mInformationLabel = new QLabel; mInformationLabel->setWordWrap(true); mInformationLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); // mDocumentCountLabel mDocumentCountLabel = new QLabel; mInformationContainer = new QWidget; const int infoContainerTopMargin = 6; const int infoContainerBottomMargin = 2; mInformationContainer->setContentsMargins(6, infoContainerTopMargin, 6, infoContainerBottomMargin); mInformationContainer->setAutoFillBackground(true); mInformationContainer->setBackgroundRole(QPalette::Mid); mInformationContainerShadow = new ShadowFilter(mInformationContainer); QHBoxLayout* hLayout = new QHBoxLayout(mInformationContainer); - hLayout->setMargin(0); + hLayout->setContentsMargins(0, 0, 0, 0); hLayout->addWidget(mInformationLabel); hLayout->addWidget(mDocumentCountLabel); // Thumbnail bar mThumbnailBar = new ThumbnailBarView(mContent); mThumbnailBar->setThumbnailScaleMode(ThumbnailView::ScaleToSquare); ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar); mThumbnailBar->setItemDelegate(delegate); mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); // Calculate minimum bar height to give mInformationLabel exactly two lines height const int lineHeight = mInformationLabel->fontMetrics().lineSpacing(); mMinimumThumbnailBarHeight = mToolBar->sizeHint().height() + lineHeight * 2 + infoContainerTopMargin + infoContainerBottomMargin; // Ensure document count is updated when items added/removed from folder connect(mThumbnailBar, &ThumbnailBarView::rowsInsertedSignal, this, &FullScreenContent::updateDocumentCountLabel); connect(mThumbnailBar, &ThumbnailBarView::rowsRemovedSignal, this, &FullScreenContent::updateDocumentCountLabel); connect(mThumbnailBar, &ThumbnailBarView::indexActivated, this, &FullScreenContent::updateDocumentCountLabel); // Right bar mRightToolBar = new FullScreenToolBar(mContent); mRightToolBar->addAction(mActionCollection->action("leave_fullscreen")); mRightToolBar->addAction(mOptionsAction); mRightToolBar->addStretch(); mRightToolBar->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); mRightToolBarShadow = new ShadowFilter(mRightToolBar); updateLayout(); updateContainerAppearance(); } ThumbnailBarView* FullScreenContent::thumbnailBar() const { return mThumbnailBar; } void FullScreenContent::setCurrentUrl(const QUrl &url) { if (url.isEmpty()) { mCurrentDocument = Document::Ptr(); } else { mCurrentDocument = DocumentFactory::instance()->load(url); connect(mCurrentDocument.data(), &Document::metaInfoUpdated, this, &FullScreenContent::updateCurrentUrlWidgets); } updateCurrentUrlWidgets(); // Give the thumbnail view time to update its "current index" QTimer::singleShot(0, this, &FullScreenContent::updateDocumentCountLabel); } void FullScreenContent::updateInformationLabel() { if (!mCurrentDocument) { return; } if (!mInformationLabel->isVisible()) { return; } ImageMetaInfoModel* model = mCurrentDocument->metaInfo(); QStringList valueList; Q_FOREACH(const QString & key, GwenviewConfig::fullScreenPreferredMetaInfoKeyList()) { const QString value = model->getValueForKey(key); if (value.length() > 0) { valueList << value; } } QString text = valueList.join(i18nc("@item:intext fullscreen meta info separator", ", ")); mInformationLabel->setText(text); } void FullScreenContent::updateCurrentUrlWidgets() { updateInformationLabel(); updateMetaInfoDialog(); } void FullScreenContent::showImageMetaInfoDialog() { if (!mImageMetaInfoDialog) { mImageMetaInfoDialog = new ImageMetaInfoDialog(mInformationLabel); // Do not let the fullscreen theme propagate to this dialog for now, // it's already quite complicated to create a theme mImageMetaInfoDialog->setStyle(QApplication::style()); mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); connect(mImageMetaInfoDialog.data(), &ImageMetaInfoDialog::preferredMetaInfoKeyListChanged, this, &FullScreenContent::slotPreferredMetaInfoKeyListChanged); connect(mImageMetaInfoDialog.data(), &QObject::destroyed, this, &FullScreenContent::slotImageMetaInfoDialogClosed); } if (mCurrentDocument) { mImageMetaInfoDialog->setMetaInfo(mCurrentDocument->metaInfo(), GwenviewConfig::fullScreenPreferredMetaInfoKeyList()); } mAutoHideContainer->setAutoHidingEnabled(false); mImageMetaInfoDialog->show(); } void FullScreenContent::slotPreferredMetaInfoKeyListChanged(const QStringList& list) { GwenviewConfig::setFullScreenPreferredMetaInfoKeyList(list); GwenviewConfig::self()->save(); updateInformationLabel(); } void FullScreenContent::updateMetaInfoDialog() { if (!mImageMetaInfoDialog) { return; } ImageMetaInfoModel* model = mCurrentDocument ? mCurrentDocument->metaInfo() : nullptr; mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::fullScreenPreferredMetaInfoKeyList()); } static QString formatSlideShowIntervalText(int value) { return i18ncp("Slideshow interval in seconds", "%1 sec", "%1 secs", value); } void FullScreenContent::updateLayout() { delete mContent->layout(); if (GwenviewConfig::showFullScreenThumbnails()) { mRightToolBar->setDirection(QBoxLayout::TopToBottom); QHBoxLayout* layout = new QHBoxLayout(mContent); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); QVBoxLayout* vLayout; // First column vLayout = new QVBoxLayout; vLayout->addWidget(mToolBar); vLayout->addWidget(mInformationContainer); layout->addLayout(vLayout); // Second column layout->addSpacing(2); layout->addWidget(mThumbnailBar); layout->addSpacing(2); // Third column vLayout = new QVBoxLayout; vLayout->addWidget(mRightToolBar); layout->addLayout(vLayout); mInformationContainer->setMaximumWidth(mToolBar->sizeHint().width()); const int barHeight = qMax(GwenviewConfig::fullScreenBarHeight(), mMinimumThumbnailBarHeight); mThumbnailBar->setFixedHeight(barHeight); mAutoHideContainer->setFixedHeight(barHeight); mInformationLabel->setAlignment(Qt::AlignTop); mDocumentCountLabel->setAlignment(Qt::AlignBottom | Qt::AlignRight); // Shadows mToolBarShadow->reset(); mToolBarShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 64)); mToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); mInformationContainerShadow->reset(); mInformationContainerShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); mInformationContainerShadow->setShadow(ShadowFilter::TopEdge, QColor(0, 0, 0, 64)); mInformationContainerShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 128)); mInformationContainerShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); mRightToolBarShadow->reset(); mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); mRightToolBarShadow->setShadow(ShadowFilter::BottomEdge, QColor(255, 255, 255, 8)); } else { mRightToolBar->setDirection(QBoxLayout::RightToLeft); QHBoxLayout* layout = new QHBoxLayout(mContent); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(mToolBar); layout->addWidget(mInformationContainer); layout->addWidget(mRightToolBar); mInformationContainer->setMaximumWidth(QWIDGETSIZE_MAX); mAutoHideContainer->setFixedHeight(mToolBar->sizeHint().height()); mInformationLabel->setAlignment(Qt::AlignVCenter); mDocumentCountLabel->setAlignment(Qt::AlignVCenter | Qt::AlignRight); // Shadows mToolBarShadow->reset(); mInformationContainerShadow->reset(); mInformationContainerShadow->setShadow(ShadowFilter::LeftEdge, QColor(0, 0, 0, 64)); mInformationContainerShadow->setShadow(ShadowFilter::RightEdge, QColor(0, 0, 0, 32)); mRightToolBarShadow->reset(); mRightToolBarShadow->setShadow(ShadowFilter::LeftEdge, QColor(255, 255, 255, 8)); } } void FullScreenContent::updateContainerAppearance() { if (!mContent->window()->isFullScreen() || !mViewPageVisible) { mAutoHideContainer->setActivated(false); return; } mThumbnailBar->setVisible(GwenviewConfig::showFullScreenThumbnails()); mAutoHideContainer->adjustSize(); mAutoHideContainer->setActivated(true); } void FullScreenContent::adjustSize() { if (mContent->window()->isFullScreen() && mViewPageVisible) { mAutoHideContainer->adjustSize(); } } void FullScreenContent::createOptionsAction() { // We do not use a KActionMenu because: // // - It causes the button to show a small down arrow on its right, // which makes it wider // // - We can't control where the menu shows: in no-thumbnail-mode, the // menu should not be aligned to the right edge of the screen because // if the mode is changed to thumbnail-mode, we want the option button // to remain visible mOptionsAction = new QAction(this); mOptionsAction->setPriority(QAction::LowPriority); mOptionsAction->setIcon(QIcon::fromTheme("configure")); mOptionsAction->setToolTip(i18nc("@info:tooltip", "Configure full screen mode")); QObject::connect(mOptionsAction, &QAction::triggered, this, &FullScreenContent::showOptionsMenu); } void FullScreenContent::updateSlideShowIntervalLabel() { Q_ASSERT(mConfigWidget); int value = mConfigWidget->mSlideShowIntervalSlider->value(); QString text = formatSlideShowIntervalText(value); mConfigWidget->mSlideShowIntervalLabel->setText(text); } void FullScreenContent::setFullScreenBarHeight(int value) { mThumbnailBar->setFixedHeight(value); mAutoHideContainer->setFixedHeight(value); GwenviewConfig::setFullScreenBarHeight(value); } void FullScreenContent::showOptionsMenu() { Q_ASSERT(!mConfigWidget); mConfigWidget = new FullScreenConfigWidget; FullScreenConfigWidget* widget = mConfigWidget; // Put widget in a menu QMenu menu; QWidgetAction* action = new QWidgetAction(&menu); action->setDefaultWidget(widget); menu.addAction(action); // Slideshow checkboxes widget->mSlideShowLoopCheckBox->setChecked(mSlideShow->loopAction()->isChecked()); connect(widget->mSlideShowLoopCheckBox, &QAbstractButton::toggled, mSlideShow->loopAction(), &QAction::trigger); widget->mSlideShowRandomCheckBox->setChecked(mSlideShow->randomAction()->isChecked()); connect(widget->mSlideShowRandomCheckBox, &QAbstractButton::toggled, mSlideShow->randomAction(), &QAction::trigger); // Interval slider widget->mSlideShowIntervalSlider->setValue(int(GwenviewConfig::interval())); connect(widget->mSlideShowIntervalSlider, &QAbstractSlider::valueChanged, mSlideShow, &SlideShow::setInterval); connect(widget->mSlideShowIntervalSlider, &QAbstractSlider::valueChanged, this, &FullScreenContent::updateSlideShowIntervalLabel); // Interval label QString text = formatSlideShowIntervalText(88); int width = widget->mSlideShowIntervalLabel->fontMetrics().width(text); widget->mSlideShowIntervalLabel->setFixedWidth(width); updateSlideShowIntervalLabel(); // Image information connect(widget->mConfigureDisplayedInformationButton, &QAbstractButton::clicked, this, &FullScreenContent::showImageMetaInfoDialog); // Thumbnails widget->mThumbnailGroupBox->setVisible(mViewPageVisible); if (mViewPageVisible) { widget->mShowThumbnailsCheckBox->setChecked(GwenviewConfig::showFullScreenThumbnails()); widget->mHeightSlider->setMinimum(mMinimumThumbnailBarHeight); widget->mHeightSlider->setValue(mThumbnailBar->height()); connect(widget->mShowThumbnailsCheckBox, &QAbstractButton::toggled, this, &FullScreenContent::slotShowThumbnailsToggled); connect(widget->mHeightSlider, &QAbstractSlider::valueChanged, this, &FullScreenContent::setFullScreenBarHeight); } // Show menu below its button QPoint pos; QWidget* button = mOptionsAction->associatedWidgets().constFirst(); Q_ASSERT(button); qWarning() << button << button->geometry(); if (QApplication::isRightToLeft()) { pos = button->mapToGlobal(button->rect().bottomLeft()); } else { pos = button->mapToGlobal(button->rect().bottomRight()); pos.rx() -= menu.sizeHint().width(); } qWarning() << pos; menu.exec(pos); } void FullScreenContent::setFullScreenMode(bool fullScreenMode) { Q_UNUSED(fullScreenMode); updateContainerAppearance(); setupThumbnailBarStyleSheet(); } void FullScreenContent::setDistractionFreeMode(bool distractionFreeMode) { mAutoHideContainer->setEdgeTriggerEnabled(!distractionFreeMode); } void FullScreenContent::slotImageMetaInfoDialogClosed() { mAutoHideContainer->setAutoHidingEnabled(true); } void FullScreenContent::slotShowThumbnailsToggled(bool value) { GwenviewConfig::setShowFullScreenThumbnails(value); GwenviewConfig::self()->save(); mThumbnailBar->setVisible(value); updateLayout(); mContent->adjustSize(); mAutoHideContainer->adjustSize(); } void FullScreenContent::slotViewModeActionToggled(bool value) { mViewPageVisible = value; updateContainerAppearance(); } void FullScreenContent::setupThumbnailBarStyleSheet() { const QPalette pal = mGvCore->palette(GvCore::NormalPalette); const QPalette fsPal = mGvCore->palette(GvCore::FullScreenPalette); QColor bgColor = fsPal.color(QPalette::Normal, QPalette::Base); QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight); QColor bgHovColor = pal.color(QPalette::Normal, QPalette::Highlight); // Darken the select color a little to suit dark theme of fullscreen mode bgSelColor.setHsv(bgSelColor.hue(), bgSelColor.saturation(), (bgSelColor.value() * 0.8)); // Calculate hover color based on background color in case it changes (matches ViewMainPage thumbnail bar) bgHovColor.setHsv(bgHovColor.hue(), (bgHovColor.saturation() / 2), ((bgHovColor.value() + bgColor.value()) / 2)); QString genCss = "QListView {" " background-color: %1;" "}"; genCss = genCss.arg(StyleSheetUtils::rgba(bgColor)); QString itemSelCss = "QListView::item:selected {" " background-color: %1;" "}"; itemSelCss = itemSelCss.arg(StyleSheetUtils::rgba(bgSelColor)); QString itemHovCss = "QListView::item:hover:!selected {" " background-color: %1;" "}"; itemHovCss = itemHovCss.arg(StyleSheetUtils::rgba(bgHovColor)); QString css = genCss + itemSelCss + itemHovCss; mThumbnailBar->setStyleSheet(css); } void FullScreenContent::updateDocumentCountLabel() { const int current = mThumbnailBar->currentIndex().row() + 1; const int total = mThumbnailBar->model()->rowCount(); const QString text = i18nc("@info:status %1 current document index, %2 total documents", "%1 of %2", current, total); mDocumentCountLabel->setText(text); } } // namespace diff --git a/app/kipiimagecollectionselector.cpp b/app/kipiimagecollectionselector.cpp index a35e36e3..d3029abe 100644 --- a/app/kipiimagecollectionselector.cpp +++ b/app/kipiimagecollectionselector.cpp @@ -1,90 +1,90 @@ // 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 "kipiimagecollectionselector.h" // Qt #include #include // KDE #include // Local namespace Gwenview { struct KIPIImageCollectionSelectorPrivate { KIPIInterface* mInterface; QListWidget* mListWidget; }; KIPIImageCollectionSelector::KIPIImageCollectionSelector(KIPIInterface* interface, QWidget* parent) : KIPI::ImageCollectionSelector(parent) , d(new KIPIImageCollectionSelectorPrivate) { d->mInterface = interface; d->mListWidget = new QListWidget; QList list = interface->allAlbums(); Q_FOREACH(const KIPI::ImageCollection & collection, list) { QListWidgetItem* item = new QListWidgetItem(d->mListWidget); QString name = collection.name(); int imageCount = collection.images().size(); QString title = i18ncp("%1 is collection name, %2 is image count in collection", "%1 (%2 image)", "%1 (%2 images)", name, imageCount); item->setText(title); item->setData(Qt::UserRole, name); } connect(d->mListWidget, &QListWidget::currentRowChanged, this, &KIPIImageCollectionSelector::selectionChanged); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(d->mListWidget); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); } KIPIImageCollectionSelector::~KIPIImageCollectionSelector() { delete d; } QList KIPIImageCollectionSelector::selectedImageCollections() const { QListWidgetItem* item = d->mListWidget->currentItem(); QList selectedList; if (item) { QString name = item->data(Qt::UserRole).toString(); QList list = d->mInterface->allAlbums(); Q_FOREACH(const KIPI::ImageCollection & collection, list) { if (collection.name() == name) { selectedList << collection; break; } } } return selectedList; } } // namespace diff --git a/app/kipiuploadwidget.cpp b/app/kipiuploadwidget.cpp index 5d1e905d..84ac30e2 100644 --- a/app/kipiuploadwidget.cpp +++ b/app/kipiuploadwidget.cpp @@ -1,56 +1,56 @@ // 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 "kipiuploadwidget.h" // Qt #include #include // KDE #include // Local #include "kipiinterface.h" namespace Gwenview { KIPIUploadWidget::KIPIUploadWidget(KIPIInterface* interface, QWidget* parent) : KIPI::UploadWidget(parent) , mInterface(interface) { QLabel* label = new QLabel(this); QUrl url = mInterface->currentAlbum().uploadUrl(); label->setText(i18n("Images will be uploaded here:\n%1", url.toDisplayString())); label->setWordWrap(true); QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(label); } KIPI::ImageCollection KIPIUploadWidget::selectedImageCollection() const { return mInterface->currentAlbum(); } } // namespace diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 0c405053..2eae3530 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,1790 +1,1790 @@ /* 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 "mainwindow.h" #include #include "dialogguard.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_OSX #include #endif // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "configdialog.h" #include "documentinfoprovider.h" #include "viewmainpage.h" #include "fileopscontextmanageritem.h" #include "folderviewcontextmanageritem.h" #include "fullscreencontent.h" #include "gvcore.h" #include "imageopscontextmanageritem.h" #include "infocontextmanageritem.h" #ifdef KIPI_FOUND #include "kipiexportaction.h" #include "kipiinterface.h" #endif #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include "semanticinfocontextmanageritem.h" #endif #include "preloader.h" #include "savebar.h" #include "sidebar.h" #include "splitter.h" #include "startmainpage.h" #include "thumbnailviewhelper.h" #include "browsemainpage.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_QTDBUS #include #endif #include #include #include #include #include #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) qDebug() << x #else #define LOG(x) ; #endif static const int BROWSE_PRELOAD_DELAY = 1000; static const int VIEW_PRELOAD_DELAY = 100; static const char* SESSION_CURRENT_PAGE_KEY = "Page"; static const char* SESSION_URL_KEY = "Url"; enum MainPageId { StartMainPageId, BrowseMainPageId, ViewMainPageId }; struct MainWindowState { bool mToolBarVisible; }; /* Layout of the main window looks like this: .-mCentralSplitter-----------------------------. |.-mSideBar--. .-mContentWidget---------------.| || | |.-mSaveBar-------------------.|| || | || ||| || | |'----------------------------'|| || | |.-mViewStackedWidget---------.|| || | || ||| || | || ||| || | || ||| || | || ||| || | |'----------------------------'|| |'-----------' '------------------------------'| '----------------------------------------------' */ struct MainWindow::Private { GvCore* mGvCore; MainWindow* q; QSplitter* mCentralSplitter; QWidget* mContentWidget; ViewMainPage* mViewMainPage; KUrlNavigator* mUrlNavigator; ThumbnailView* mThumbnailView; ThumbnailView* mActiveThumbnailView; DocumentInfoProvider* mDocumentInfoProvider; ThumbnailViewHelper* mThumbnailViewHelper; QPointer mThumbnailProvider; BrowseMainPage* mBrowseMainPage; StartMainPage* mStartMainPage; SideBar* mSideBar; QStackedWidget* mViewStackedWidget; FullScreenContent* mFullScreenContent; SaveBar* mSaveBar; bool mStartSlideShowWhenDirListerCompleted; SlideShow* mSlideShow; #ifdef HAVE_QTDBUS Mpris2Service* mMpris2Service; #endif Preloader* mPreloader; bool mPreloadDirectionIsForward; #ifdef KIPI_FOUND KIPIInterface* mKIPIInterface; #endif QActionGroup* mViewModeActionGroup; KRecentFilesAction* mFileOpenRecentAction; QAction * mBrowseAction; QAction * mViewAction; QAction * mGoUpAction; QAction * mGoToPreviousAction; QAction * mGoToNextAction; QAction * mGoToFirstAction; QAction * mGoToLastAction; KToggleAction* mToggleSideBarAction; QAction* mFullScreenAction; QAction * mToggleSlideShowAction; KToggleAction* mShowMenuBarAction; KToggleAction* mShowStatusBarAction; #ifdef KIPI_FOUND KIPIExportAction* mKIPIExportAction; #endif SortedDirModel* mDirModel; DocumentOnlyProxyModel* mThumbnailBarModel; KLinkItemSelectionModel* mThumbnailBarSelectionModel; ContextManager* mContextManager; MainWindowState mStateBeforeFullScreen; QString mCaption; MainPageId mCurrentMainPageId; QDateTime mFullScreenLeftAt; KNotificationRestrictions* mNotificationRestrictions; void setupContextManager() { mContextManager = new ContextManager(mDirModel, q); connect(mContextManager, &ContextManager::selectionChanged, q, &MainWindow::slotSelectionChanged); connect(mContextManager, &ContextManager::currentDirUrlChanged, q, &MainWindow::slotCurrentDirUrlChanged); } void setupWidgets() { mFullScreenContent = new FullScreenContent(q, mGvCore); connect(mContextManager, &ContextManager::currentUrlChanged, mFullScreenContent, &FullScreenContent::setCurrentUrl); mCentralSplitter = new Splitter(Qt::Horizontal, q); q->setCentralWidget(mCentralSplitter); // Left side of splitter mSideBar = new SideBar(mCentralSplitter); // Right side of splitter mContentWidget = new QWidget(mCentralSplitter); mSaveBar = new SaveBar(mContentWidget, q->actionCollection()); connect(mContextManager, &ContextManager::currentUrlChanged, mSaveBar, &SaveBar::setCurrentUrl); mViewStackedWidget = new QStackedWidget(mContentWidget); QVBoxLayout* layout = new QVBoxLayout(mContentWidget); layout->addWidget(mSaveBar); layout->addWidget(mViewStackedWidget); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); //// mStartSlideShowWhenDirListerCompleted = false; mSlideShow = new SlideShow(q); connect(mContextManager, &ContextManager::currentUrlChanged, mSlideShow, &SlideShow::setCurrentUrl); setupThumbnailView(mViewStackedWidget); setupViewMainPage(mViewStackedWidget); setupStartMainPage(mViewStackedWidget); mViewStackedWidget->addWidget(mBrowseMainPage); mViewStackedWidget->addWidget(mViewMainPage); mViewStackedWidget->addWidget(mStartMainPage); mViewStackedWidget->setCurrentWidget(mBrowseMainPage); mCentralSplitter->setStretchFactor(0, 0); mCentralSplitter->setStretchFactor(1, 1); mCentralSplitter->setChildrenCollapsible(false); mThumbnailView->setFocus(); connect(mSaveBar, &SaveBar::requestSaveAll, mGvCore, &GvCore::saveAll); connect(mSaveBar, &SaveBar::goToUrl, q, &MainWindow::goToUrl); connect(mSlideShow, &SlideShow::goToUrl, q, &MainWindow::goToUrl); } void setupThumbnailView(QWidget* parent) { Q_ASSERT(mContextManager); mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore); mThumbnailView = mBrowseMainPage->thumbnailView(); mThumbnailView->viewport()->installEventFilter(q); mThumbnailView->setSelectionModel(mContextManager->selectionModel()); mUrlNavigator = mBrowseMainPage->urlNavigator(); mDocumentInfoProvider = new DocumentInfoProvider(mDirModel); mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider); mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection()); connect(mContextManager, &ContextManager::currentDirUrlChanged, mThumbnailViewHelper, &ThumbnailViewHelper::setCurrentDirUrl); mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper); mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q); // Connect thumbnail view connect(mThumbnailView, &ThumbnailView::indexActivated, q, &MainWindow::slotThumbnailViewIndexActivated); // Connect delegate QAbstractItemDelegate* delegate = mThumbnailView->itemDelegate(); connect(delegate, SIGNAL(saveDocumentRequested(QUrl)), mGvCore, SLOT(save(QUrl))); connect(delegate, SIGNAL(rotateDocumentLeftRequested(QUrl)), mGvCore, SLOT(rotateLeft(QUrl))); connect(delegate, SIGNAL(rotateDocumentRightRequested(QUrl)), mGvCore, SLOT(rotateRight(QUrl))); connect(delegate, SIGNAL(showDocumentInFullScreenRequested(QUrl)), q, SLOT(showDocumentInFullScreen(QUrl))); connect(delegate, SIGNAL(setDocumentRatingRequested(QUrl,int)), mGvCore, SLOT(setRating(QUrl,int))); // Connect url navigator connect(mUrlNavigator, &KUrlNavigator::urlChanged, q, &MainWindow::openDirUrl); } void setupViewMainPage(QWidget* parent) { mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore); connect(mViewMainPage, &ViewMainPage::captionUpdateRequested, q, &MainWindow::slotUpdateCaption); connect(mViewMainPage, &ViewMainPage::completed, q, &MainWindow::slotPartCompleted); connect(mViewMainPage, &ViewMainPage::previousImageRequested, q, &MainWindow::goToPrevious); connect(mViewMainPage, &ViewMainPage::nextImageRequested, q, &MainWindow::goToNext); connect(mViewMainPage, &ViewMainPage::openUrlRequested, q, &MainWindow::openUrl); connect(mViewMainPage, &ViewMainPage::openDirUrlRequested, q, &MainWindow::openDirUrl); setupThumbnailBar(mViewMainPage->thumbnailBar()); } void setupThumbnailBar(ThumbnailView* bar) { Q_ASSERT(mThumbnailBarModel); Q_ASSERT(mThumbnailBarSelectionModel); Q_ASSERT(mDocumentInfoProvider); Q_ASSERT(mThumbnailViewHelper); bar->setModel(mThumbnailBarModel); bar->setSelectionModel(mThumbnailBarSelectionModel); bar->setDocumentInfoProvider(mDocumentInfoProvider); bar->setThumbnailViewHelper(mThumbnailViewHelper); } void setupStartMainPage(QWidget* parent) { mStartMainPage = new StartMainPage(parent, mGvCore); connect(mStartMainPage, &StartMainPage::urlSelected, q, &MainWindow::slotStartMainPageUrlSelected); connect(mStartMainPage, &StartMainPage::recentFileRemoved, [this](const QUrl& url) { mFileOpenRecentAction->removeUrl(url); }); connect(mStartMainPage, &StartMainPage::recentFilesCleared, [this]() { mFileOpenRecentAction->clear(); }); } void installDisabledActionShortcutMonitor(QAction* action, const char* slot) { DisabledActionShortcutMonitor* monitor = new DisabledActionShortcutMonitor(action, q); connect(monitor, SIGNAL(activated()), q, slot); } void setupActions() { KActionCollection* actionCollection = q->actionCollection(); KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); file->addAction(KStandardAction::Save, q, SLOT(saveCurrent())); file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs())); file->addAction(KStandardAction::Open, q, SLOT(openFile())); mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(QUrl)), q); connect(mFileOpenRecentAction, &KRecentFilesAction::recentListCleared, mGvCore, &GvCore::clearRecentFilesAndFolders); QAction * clearAction = mFileOpenRecentAction->menu()->findChild("clear_action"); if (clearAction) { clearAction->setText(i18nc("@action Open Recent menu", "Forget All Files && Folders")); } file->addAction("file_open_recent", mFileOpenRecentAction); file->addAction(KStandardAction::Print, q, SLOT(print())); file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows())); QAction * action = file->addAction("reload", q, SLOT(reload())); action->setText(i18nc("@action reload the currently viewed image", "Reload")); action->setIcon(QIcon::fromTheme("view-refresh")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::reload()); QAction * replaceLocationAction = actionCollection->addAction(QStringLiteral("replace_location")); replaceLocationAction->setText(i18nc("@action:inmenu Navigation Bar", "Replace Location")); actionCollection->setDefaultShortcut(replaceLocationAction, Qt::CTRL + Qt::Key_L); connect(replaceLocationAction, &QAction::triggered, q, &MainWindow::replaceLocation); mBrowseAction = view->addAction("browse"); mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse")); mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images")); mBrowseAction->setCheckable(true); mBrowseAction->setIcon(QIcon::fromTheme("view-list-icons")); actionCollection->setDefaultShortcut(mBrowseAction, Qt::Key_Escape); connect(mViewMainPage, &ViewMainPage::goToBrowseModeRequested, mBrowseAction, &QAction::trigger); mViewAction = view->addAction("view"); mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View")); mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images")); mViewAction->setIcon(QIcon::fromTheme("view-preview")); mViewAction->setCheckable(true); mViewModeActionGroup = new QActionGroup(q); mViewModeActionGroup->addAction(mBrowseAction); mViewModeActionGroup->addAction(mViewAction); connect(mViewModeActionGroup, &QActionGroup::triggered, q, &MainWindow::setActiveViewModeAction); mFullScreenAction = KStandardAction::fullScreen(q, &MainWindow::toggleFullScreen, q, actionCollection); QList shortcuts = mFullScreenAction->shortcuts(); shortcuts.append(QKeySequence(Qt::Key_F11)); actionCollection->setDefaultShortcuts(mFullScreenAction, shortcuts); connect(mViewMainPage, &ViewMainPage::toggleFullScreenRequested, mFullScreenAction, &QAction::trigger); QAction * leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen())); leaveFullScreenAction->setIcon(QIcon::fromTheme("view-restore")); leaveFullScreenAction->setPriority(QAction::LowPriority); leaveFullScreenAction->setText(i18nc("@action", "Leave Fullscreen Mode")); mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious())); mGoToPreviousAction->setPriority(QAction::LowPriority); mGoToPreviousAction->setIcon(QIcon::fromTheme("go-previous-view")); mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous")); mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image")); actionCollection->setDefaultShortcut(mGoToPreviousAction, Qt::Key_Backspace); installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached())); mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext())); mGoToNextAction->setPriority(QAction::LowPriority); mGoToNextAction->setIcon(QIcon::fromTheme("go-next-view")); mGoToNextAction->setText(i18nc("@action Go to next image", "Next")); mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image")); actionCollection->setDefaultShortcut(mGoToNextAction, Qt::Key_Space); installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached())); mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst())); mGoToFirstAction->setPriority(QAction::LowPriority); mGoToFirstAction->setIcon(QIcon::fromTheme("go-first-view")); mGoToFirstAction->setText(i18nc("@action Go to first image", "First")); mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image")); actionCollection->setDefaultShortcut(mGoToFirstAction, Qt::Key_Home); mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast())); mGoToLastAction->setPriority(QAction::LowPriority); mGoToLastAction->setIcon(QIcon::fromTheme("go-last-view")); mGoToLastAction->setText(i18nc("@action Go to last image", "Last")); mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image")); actionCollection->setDefaultShortcut(mGoToLastAction, Qt::Key_End); mPreloadDirectionIsForward = true; mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp())); action = view->addAction("go_start_page", q, SLOT(showStartMainPage())); action->setPriority(QAction::LowPriority); action->setIcon(QIcon::fromTheme("go-home")); action->setText(i18nc("@action", "Start Page")); action->setToolTip(i18nc("@info:tooltip", "Open the start page")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::home()); mToggleSideBarAction = view->add("toggle_sidebar"); connect(mToggleSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleSideBar); mToggleSideBarAction->setIcon(QIcon::fromTheme("view-sidetree")); actionCollection->setDefaultShortcut(mToggleSideBarAction, Qt::Key_F4); mToggleSideBarAction->setText(i18nc("@action", "Sidebar")); connect(mBrowseMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger); connect(mViewMainPage->toggleSideBarButton(), &QAbstractButton::clicked, mToggleSideBarAction, &QAction::trigger); mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow())); q->updateSlideShowAction(); connect(mSlideShow, &SlideShow::stateChanged, q, &MainWindow::updateSlideShowAction); q->setStandardToolBarMenuEnabled(true); mShowMenuBarAction = static_cast(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar()))); mShowStatusBarAction = static_cast(view->addAction(KStandardAction::ShowStatusbar, q, SLOT(toggleStatusBar(bool)))); actionCollection->setDefaultShortcut(mShowStatusBarAction, Qt::Key_F3); view->addAction(KStandardAction::name(KStandardAction::KeyBindings), KStandardAction::keyBindings(q, &MainWindow::configureShortcuts, actionCollection)); view->addAction(KStandardAction::Preferences, q, SLOT(showConfigDialog())); view->addAction(KStandardAction::ConfigureToolbars, q, SLOT(configureToolbars())); #ifdef KIPI_FOUND mKIPIExportAction = new KIPIExportAction(q); actionCollection->addAction("kipi_export", mKIPIExportAction); #endif } void setupUndoActions() { // There is no KUndoGroup similar to KUndoStack. This code basically // does the same as KUndoStack, but for the KUndoGroup actions. QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); QAction* action; KActionCollection* actionCollection = q->actionCollection(); KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection); action = undoGroup->createRedoAction(actionCollection); action->setObjectName(KStandardAction::name(KStandardAction::Redo)); action->setIcon(QIcon::fromTheme("edit-redo")); action->setIconText(i18n("Redo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo()); edit->addAction(action->objectName(), action); action = undoGroup->createUndoAction(actionCollection); action->setObjectName(KStandardAction::name(KStandardAction::Undo)); action->setIcon(QIcon::fromTheme("edit-undo")); action->setIconText(i18n("Undo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo()); edit->addAction(action->objectName(), action); } void setupContextManagerItems() { Q_ASSERT(mContextManager); KActionCollection* actionCollection = q->actionCollection(); // Create context manager items FolderViewContextManagerItem* folderViewItem = new FolderViewContextManagerItem(mContextManager); connect(folderViewItem, &FolderViewContextManagerItem::urlChanged, q, &MainWindow::folderViewUrlChanged); InfoContextManagerItem* infoItem = new InfoContextManagerItem(mContextManager); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE SemanticInfoContextManagerItem* semanticInfoItem = nullptr; semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage); #endif ImageOpsContextManagerItem* imageOpsItem = new ImageOpsContextManagerItem(mContextManager, q); FileOpsContextManagerItem* fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q); // Fill sidebar SideBarPage* page; page = new SideBarPage(i18n("Folders")); page->setObjectName(QLatin1String("folders")); page->addWidget(folderViewItem->widget()); - page->layout()->setMargin(0); + page->layout()->setContentsMargins(0, 0, 0, 0); mSideBar->addPage(page); page = new SideBarPage(i18n("Information")); page->setObjectName(QLatin1String("information")); page->addWidget(infoItem->widget()); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE if (semanticInfoItem) { page->addWidget(semanticInfoItem->widget()); } #endif mSideBar->addPage(page); page = new SideBarPage(i18n("Operations")); page->setObjectName(QLatin1String("operations")); page->addWidget(imageOpsItem->widget()); page->addWidget(fileOpsItem->widget()); page->addStretch(); mSideBar->addPage(page); } void initDirModel() { mDirModel->setKindFilter( MimeTypeUtils::KIND_DIR | MimeTypeUtils::KIND_ARCHIVE | MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO); connect(mDirModel, &QAbstractItemModel::rowsInserted, q, &MainWindow::slotDirModelNewItems); connect(mDirModel, &QAbstractItemModel::rowsRemoved, q, &MainWindow::updatePreviousNextActions); connect(mDirModel, &QAbstractItemModel::modelReset, q, &MainWindow::updatePreviousNextActions); connect(mDirModel->dirLister(), SIGNAL(completed()), q, SLOT(slotDirListerCompleted())); } void setupThumbnailBarModel() { mThumbnailBarModel = new DocumentOnlyProxyModel(q); mThumbnailBarModel->setSourceModel(mDirModel); } bool indexIsDirOrArchive(const QModelIndex& index) const { Q_ASSERT(index.isValid()); KFileItem item = mDirModel->itemForIndex(index); return ArchiveUtils::fileItemIsDirOrArchive(item); } void goTo(const QModelIndex& index) { if (!index.isValid()) { return; } mThumbnailView->setCurrentIndex(index); mThumbnailView->scrollTo(index); } void goTo(int offset) { mPreloadDirectionIsForward = offset > 0; QModelIndex index = mContextManager->selectionModel()->currentIndex(); index = mDirModel->index(index.row() + offset, 0); if (index.isValid() && !indexIsDirOrArchive(index)) { goTo(index); } } void goToFirstDocument() { QModelIndex index; for (int row = 0;; ++row) { index = mDirModel->index(row, 0); if (!index.isValid()) { return; } if (!indexIsDirOrArchive(index)) { break; } } goTo(index); } void goToLastDocument() { QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0); goTo(index); } void setupFullScreenContent() { mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow); setupThumbnailBar(mFullScreenContent->thumbnailBar()); } inline void setActionEnabled(const char* name, bool enabled) { QAction* action = q->actionCollection()->action(name); if (action) { action->setEnabled(enabled); } else { qWarning() << "Action" << name << "not found"; } } void setActionsDisabledOnStartMainPageEnabled(bool enabled) { mBrowseAction->setEnabled(enabled); mViewAction->setEnabled(enabled); mToggleSideBarAction->setEnabled(enabled); mShowStatusBarAction->setEnabled(enabled); mFullScreenAction->setEnabled(enabled); mToggleSlideShowAction->setEnabled(enabled); setActionEnabled("reload", enabled); setActionEnabled("go_start_page", enabled); setActionEnabled("add_folder_to_places", enabled); } void updateActions() { bool isRasterImage = false; bool canSave = false; bool isModified = false; const QUrl url = mContextManager->currentUrl(); if (url.isValid()) { isRasterImage = mContextManager->currentUrlIsRasterImage(); canSave = isRasterImage; isModified = DocumentFactory::instance()->load(url)->isModified(); if (mCurrentMainPageId != ViewMainPageId && mContextManager->selectedFileItemList().count() != 1) { // Saving only makes sense if exactly one image is selected canSave = false; } } KActionCollection* actionCollection = q->actionCollection(); actionCollection->action("file_save")->setEnabled(canSave && isModified); actionCollection->action("file_save_as")->setEnabled(canSave); actionCollection->action("file_print")->setEnabled(isRasterImage); } bool sideBarVisibility() const { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN_VALUE(false, "Sidebar not implemented on start page"); break; case BrowseMainPageId: return GwenviewConfig::sideBarVisibleBrowseMode(); case ViewMainPageId: return q->isFullScreen() ? GwenviewConfig::sideBarVisibleViewModeFullScreen() : GwenviewConfig::sideBarVisibleViewMode(); } return false; } void saveSideBarVisibility(const bool visible) { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN("Sidebar not implemented on start page"); break; case BrowseMainPageId: GwenviewConfig::setSideBarVisibleBrowseMode(visible); break; case ViewMainPageId: q->isFullScreen() ? GwenviewConfig::setSideBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setSideBarVisibleViewMode(visible); break; } } bool statusBarVisibility() const { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN_VALUE(false, "Statusbar not implemented on start page"); break; case BrowseMainPageId: return GwenviewConfig::statusBarVisibleBrowseMode(); case ViewMainPageId: return q->isFullScreen() ? GwenviewConfig::statusBarVisibleViewModeFullScreen() : GwenviewConfig::statusBarVisibleViewMode(); } return false; } void saveStatusBarVisibility(const bool visible) { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN("Statusbar not implemented on start page"); break; case BrowseMainPageId: GwenviewConfig::setStatusBarVisibleBrowseMode(visible); break; case ViewMainPageId: q->isFullScreen() ? GwenviewConfig::setStatusBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setStatusBarVisibleViewMode(visible); break; } } void loadSplitterConfig() { const QList sizes = GwenviewConfig::sideBarSplitterSizes(); if (!sizes.isEmpty()) { mCentralSplitter->setSizes(sizes); } } void saveSplitterConfig() { if (mSideBar->isVisible()) { GwenviewConfig::setSideBarSplitterSizes(mCentralSplitter->sizes()); } } void setScreenSaverEnabled(bool enabled) { // Always delete mNotificationRestrictions, it does not hurt delete mNotificationRestrictions; if (!enabled) { mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q); } else { mNotificationRestrictions = nullptr; } } void assignThumbnailProviderToThumbnailView(ThumbnailView* thumbnailView) { GV_RETURN_IF_FAIL(thumbnailView); if (mActiveThumbnailView) { mActiveThumbnailView->setThumbnailProvider(nullptr); } thumbnailView->setThumbnailProvider(mThumbnailProvider); mActiveThumbnailView = thumbnailView; if (mActiveThumbnailView->isVisible()) { mThumbnailProvider->stop(); mActiveThumbnailView->generateThumbnailsForItems(); } } void autoAssignThumbnailProvider() { if (mCurrentMainPageId == ViewMainPageId) { if (q->windowState() & Qt::WindowFullScreen) { assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar()); } else { assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar()); } } else if (mCurrentMainPageId == BrowseMainPageId) { assignThumbnailProviderToThumbnailView(mThumbnailView); } else if (mCurrentMainPageId == StartMainPageId) { assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView()); } } }; MainWindow::MainWindow() : KXmlGuiWindow(), d(new MainWindow::Private) { d->q = this; d->mCurrentMainPageId = StartMainPageId; d->mDirModel = new SortedDirModel(this); d->setupContextManager(); d->setupThumbnailBarModel(); d->mGvCore = new GvCore(this, d->mDirModel); d->mPreloader = new Preloader(this); d->mNotificationRestrictions = nullptr; d->mThumbnailProvider = new ThumbnailProvider(); d->mActiveThumbnailView = nullptr; d->initDirModel(); d->setupWidgets(); d->setupActions(); d->setupUndoActions(); d->setupContextManagerItems(); d->setupFullScreenContent(); d->updateActions(); updatePreviousNextActions(); d->mSaveBar->initActionDependentWidgets(); createGUI(); loadConfig(); connect(DocumentFactory::instance(), &DocumentFactory::modifiedDocumentListChanged, this, &MainWindow::slotModifiedDocumentListChanged); #ifdef HAVE_QTDBUS d->mMpris2Service = new Mpris2Service(d->mSlideShow, d->mContextManager, d->mToggleSlideShowAction, d->mFullScreenAction, d->mGoToPreviousAction, d->mGoToNextAction, this); #endif #ifdef GWENVIEW_SEMANTICINFO_BACKEND_NONE auto* ratingMenu = static_cast(guiFactory()->container("rating", this)); if (ratingMenu) { ratingMenu->menuAction()->setVisible(false); } #endif #ifdef KIPI_FOUND d->mKIPIInterface = new KIPIInterface(this); d->mKIPIExportAction->setKIPIInterface(d->mKIPIInterface); #else auto* pluginsMenu = static_cast(guiFactory()->container("plugins", this)); if (pluginsMenu) { pluginsMenu->menuAction()->setVisible(false); } #endif setAutoSaveSettings(); #ifdef Q_OS_OSX qApp->installEventFilter(this); #endif } MainWindow::~MainWindow() { if (GwenviewConfig::deleteThumbnailCacheOnExit()) { QDir dir(ThumbnailProvider::thumbnailBaseDir()); if (dir.exists()) { dir.removeRecursively(); } } delete d->mThumbnailProvider; delete d; } ContextManager* MainWindow::contextManager() const { return d->mContextManager; } ViewMainPage* MainWindow::viewMainPage() const { return d->mViewMainPage; } void MainWindow::setCaption(const QString& caption) { // Keep a trace of caption to use it in slotModifiedDocumentListChanged() d->mCaption = caption; KXmlGuiWindow::setCaption(caption); } void MainWindow::setCaption(const QString& caption, bool modified) { d->mCaption = caption; KXmlGuiWindow::setCaption(caption, modified); } void MainWindow::slotUpdateCaption(const QString& caption) { const QUrl url = d->mContextManager->currentUrl(); const QList list = DocumentFactory::instance()->modifiedDocumentList(); setCaption(caption, list.contains(url)); } void MainWindow::slotModifiedDocumentListChanged() { d->updateActions(); slotUpdateCaption(d->mCaption); } void MainWindow::setInitialUrl(const QUrl &_url) { Q_ASSERT(_url.isValid()); QUrl url = UrlUtils::fixUserEnteredUrl(_url); if (UrlUtils::urlIsDirectory(url)) { d->mBrowseAction->trigger(); openDirUrl(url); } else { openUrl(url); } } void MainWindow::startSlideShow() { d->mViewAction->trigger(); // We need to wait until we have listed all images in the dirlister to // start the slideshow because the SlideShow objects needs an image list to // work. d->mStartSlideShowWhenDirListerCompleted = true; } void MainWindow::setActiveViewModeAction(QAction* action) { if (action == d->mViewAction) { d->mCurrentMainPageId = ViewMainPageId; // Switching to view mode d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage); openSelectedDocuments(); d->mPreloadDirectionIsForward = true; QTimer::singleShot(VIEW_PRELOAD_DELAY, this, &MainWindow::preloadNextUrl); } else { d->mCurrentMainPageId = BrowseMainPageId; // Switching to browse mode d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage); if (!d->mViewMainPage->isEmpty() && KProtocolManager::supportsListing(d->mViewMainPage->url())) { // Reset the view to spare resources, but don't do it if we can't // browse the url, otherwise if the user starts Gwenview this way: // gwenview http://example.com/example.png // and switch to browse mode, switching back to view mode won't bring // his image back. d->mViewMainPage->reset(); } setCaption(QString()); } d->autoAssignThumbnailProvider(); toggleSideBar(d->sideBarVisibility()); toggleStatusBar(d->statusBarVisibility()); emit viewModeChanged(); } void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex& index) { if (!index.isValid()) { return; } KFileItem item = d->mDirModel->itemForIndex(index); if (item.isDir()) { // Item is a dir, open it openDirUrl(item.url()); } else { QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype()); if (!protocol.isEmpty()) { // Item is an archive, tweak url then open it QUrl url = item.url(); url.setScheme(protocol); openDirUrl(url); } else { // Item is a document, switch to view mode d->mViewAction->trigger(); } } } void MainWindow::openSelectedDocuments() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } int count = 0; QList urls; const auto list = d->mContextManager->selectedFileItemList(); for (const auto &item : list) { if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { urls << item.url(); ++count; if (count == ViewMainPage::MaxViewCount) { break; } } } if (urls.isEmpty()) { // Selection contains no fitting items // Switch back to browsing mode d->mBrowseAction->trigger(); d->mViewMainPage->reset(); return; } QUrl currentUrl = d->mContextManager->currentUrl(); if (currentUrl.isEmpty() || !urls.contains(currentUrl)) { // There is no current URL or it doesn't belong to selection // This can happen when user manually selects a group of items currentUrl = urls.first(); } d->mViewMainPage->openUrls(urls, currentUrl); } void MainWindow::goUp() { if (d->mCurrentMainPageId == BrowseMainPageId) { QUrl url = d->mContextManager->currentDirUrl(); url = KIO::upUrl(url); openDirUrl(url); } else { d->mBrowseAction->trigger(); } } void MainWindow::showStartMainPage() { d->mCurrentMainPageId = StartMainPageId; d->setActionsDisabledOnStartMainPageEnabled(false); d->saveSplitterConfig(); d->mSideBar->hide(); d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage); d->updateActions(); updatePreviousNextActions(); d->mContextManager->setCurrentDirUrl(QUrl()); d->mContextManager->setCurrentUrl(QUrl()); d->autoAssignThumbnailProvider(); } void MainWindow::slotStartMainPageUrlSelected(const QUrl &url) { d->setActionsDisabledOnStartMainPageEnabled(true); if (d->mBrowseAction->isChecked()) { // Silently uncheck the action so that setInitialUrl() does the right thing SignalBlocker blocker(d->mBrowseAction); d->mBrowseAction->setChecked(false); } setInitialUrl(url); } void MainWindow::openDirUrl(const QUrl &url) { const QUrl currentUrl = d->mContextManager->currentDirUrl(); if (url == currentUrl) { return; } if (url.isParentOf(currentUrl)) { // Keep first child between url and currentUrl selected // If currentPath is "/home/user/photos/2008/event" // and wantedPath is "/home/user/photos" // pathToSelect should be "/home/user/photos/2008" // To anyone considering using QUrl::toLocalFile() instead of // QUrl::path() here. Please don't, using QUrl::path() is the right // thing to do here. const QString currentPath = QDir::cleanPath(currentUrl.adjusted(QUrl::StripTrailingSlash).path()); const QString wantedPath = QDir::cleanPath(url.adjusted(QUrl::StripTrailingSlash).path()); const QChar separator('/'); const int slashCount = wantedPath.count(separator); const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1); QUrl urlToSelect = url; urlToSelect.setPath(pathToSelect); d->mContextManager->setUrlToSelect(urlToSelect); } d->mThumbnailProvider->stop(); d->mContextManager->setCurrentDirUrl(url); d->mGvCore->addUrlToRecentFolders(url); d->mViewMainPage->reset(); } void MainWindow::folderViewUrlChanged(const QUrl &url) { const QUrl currentUrl = d->mContextManager->currentDirUrl(); if (url == currentUrl) { switch (d->mCurrentMainPageId) { case ViewMainPageId: d->mBrowseAction->trigger(); break; case BrowseMainPageId: d->mViewAction->trigger(); break; case StartMainPageId: break; } } else { openDirUrl(url); } } void MainWindow::toggleSideBar(bool visible) { d->saveSplitterConfig(); d->mToggleSideBarAction->setChecked(visible); d->saveSideBarVisibility(visible); d->mSideBar->setVisible(visible); const QString text = QApplication::isRightToLeft() ? QString::fromUtf8(visible ? "▮→" : "▮←") : QString::fromUtf8(visible ? "▮←" : "▮→"); const QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar"); const QList buttonList { d->mBrowseMainPage->toggleSideBarButton(), d->mViewMainPage->toggleSideBarButton() }; for (auto button : buttonList) { button->setText(text); button->setToolTip(toolTip); } } void MainWindow::toggleStatusBar(bool visible) { d->mShowStatusBarAction->setChecked(visible); d->saveStatusBarVisibility(visible); d->mViewMainPage->setStatusBarVisible(visible); d->mBrowseMainPage->setStatusBarVisible(visible); } void MainWindow::slotPartCompleted() { d->updateActions(); const QUrl url = d->mContextManager->currentUrl(); if (!url.isEmpty() && GwenviewConfig::historyEnabled()) { d->mFileOpenRecentAction->addUrl(url); d->mGvCore->addUrlToRecentFiles(url); } if (KProtocolManager::supportsListing(url)) { const QUrl dirUrl = d->mContextManager->currentDirUrl(); d->mGvCore->addUrlToRecentFolders(dirUrl); } } void MainWindow::slotSelectionChanged() { if (d->mCurrentMainPageId == ViewMainPageId) { // The user selected a new file in the thumbnail view, since the // document view is visible, let's show it openSelectedDocuments(); } else { // No document view, we need to load the document to set the undo group // of document factory to the correct QUndoStack QModelIndex index = d->mThumbnailView->currentIndex(); KFileItem item; if (index.isValid()) { item = d->mDirModel->itemForIndex(index); } QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { QUrl url = item.url(); Document::Ptr doc = DocumentFactory::instance()->load(url); undoGroup->addStack(doc->undoStack()); undoGroup->setActiveStack(doc->undoStack()); } else { undoGroup->setActiveStack(nullptr); } } // Update UI d->updateActions(); updatePreviousNextActions(); // Start preloading int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY; QTimer::singleShot(preloadDelay, this, &MainWindow::preloadNextUrl); } void MainWindow::slotCurrentDirUrlChanged(const QUrl &url) { if (url.isValid()) { d->mUrlNavigator->setLocationUrl(url); d->mGoUpAction->setEnabled(url.path() != "/"); } else { d->mGoUpAction->setEnabled(false); } } void MainWindow::slotDirModelNewItems() { if (d->mContextManager->selectionModel()->hasSelection()) { updatePreviousNextActions(); } } void MainWindow::slotDirListerCompleted() { if (d->mStartSlideShowWhenDirListerCompleted) { d->mStartSlideShowWhenDirListerCompleted = false; QTimer::singleShot(0, d->mToggleSlideShowAction, &QAction::trigger); } if (d->mContextManager->selectionModel()->hasSelection()) { updatePreviousNextActions(); } else { d->goToFirstDocument(); // Try to select the first directory in case there are no images to select if (!d->mContextManager->selectionModel()->hasSelection()) { const QModelIndex index = d->mThumbnailView->model()->index(0, 0); if (index.isValid()) { d->mThumbnailView->setCurrentIndex(index); } } } d->mThumbnailView->scrollToSelectedIndex(); d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex(); d->mFullScreenContent->thumbnailBar()->scrollToSelectedIndex(); } void MainWindow::goToPrevious() { d->goTo(-1); } void MainWindow::goToNext() { d->goTo(1); } void MainWindow::goToFirst() { d->goToFirstDocument(); } void MainWindow::goToLast() { d->goToLastDocument(); } void MainWindow::goToUrl(const QUrl &url) { if (d->mCurrentMainPageId == ViewMainPageId) { d->mViewMainPage->openUrl(url); } QUrl dirUrl = url; dirUrl = dirUrl.adjusted(QUrl::RemoveFilename); if (dirUrl != d->mContextManager->currentDirUrl()) { d->mContextManager->setCurrentDirUrl(dirUrl); d->mGvCore->addUrlToRecentFolders(dirUrl); } d->mContextManager->setUrlToSelect(url); } void MainWindow::updatePreviousNextActions() { bool hasPrevious; bool hasNext; QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex(); if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) { int row = currentIndex.row(); QModelIndex prevIndex = d->mDirModel->index(row - 1, 0); QModelIndex nextIndex = d->mDirModel->index(row + 1, 0); hasPrevious = prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex); hasNext = nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex); } else { hasPrevious = false; hasNext = false; } d->mGoToPreviousAction->setEnabled(hasPrevious); d->mGoToNextAction->setEnabled(hasNext); d->mGoToFirstAction->setEnabled(hasPrevious); d->mGoToLastAction->setEnabled(hasNext); } void MainWindow::leaveFullScreen() { if (d->mFullScreenAction->isChecked()) { d->mFullScreenAction->trigger(); } } void MainWindow::toggleFullScreen(bool checked) { setUpdatesEnabled(false); if (checked) { // Save MainWindow config now, this way if we quit while in // fullscreen, we are sure latest MainWindow changes are remembered. KConfigGroup saveConfigGroup = autoSaveConfigGroup(); if (!isFullScreen()) { // Save state if window manager did not already switch to fullscreen. saveMainWindowSettings(saveConfigGroup); d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible(); } setAutoSaveSettings(saveConfigGroup, false); resetAutoSaveSettings(); // Go full screen KToggleFullScreenAction::setFullScreen(this, true); menuBar()->hide(); toolBar()->hide(); qApp->setProperty("KDE_COLOR_SCHEME_PATH", d->mGvCore->fullScreenPaletteName()); QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette)); d->setScreenSaverEnabled(false); } else { setAutoSaveSettings(); // Back to normal qApp->setProperty("KDE_COLOR_SCHEME_PATH", QVariant()); QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette)); d->mSlideShow->pause(); KToggleFullScreenAction::setFullScreen(this, false); menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible); d->setScreenSaverEnabled(true); // See resizeEvent d->mFullScreenLeftAt = QDateTime::currentDateTime(); } d->mFullScreenContent->setFullScreenMode(checked); d->mBrowseMainPage->setFullScreenMode(checked); d->mViewMainPage->setFullScreenMode(checked); d->mSaveBar->setFullScreenMode(checked); toggleSideBar(d->sideBarVisibility()); toggleStatusBar(d->statusBarVisibility()); setUpdatesEnabled(true); d->autoAssignThumbnailProvider(); } void MainWindow::saveCurrent() { d->mGvCore->save(d->mContextManager->currentUrl()); } void MainWindow::saveCurrentAs() { d->mGvCore->saveAs(d->mContextManager->currentUrl()); } void MainWindow::reload() { if (d->mCurrentMainPageId == ViewMainPageId) { d->mViewMainPage->reload(); } else { d->mBrowseMainPage->reload(); } } void MainWindow::openFile() { QUrl dirUrl = d->mContextManager->currentDirUrl(); DialogGuard dialog(this); dialog->setDirectoryUrl(dirUrl); dialog->setWindowTitle(i18nc("@title:window", "Open Image")); const QStringList mimeFilter = MimeTypeUtils::imageMimeTypes(); dialog->setMimeTypeFilters(mimeFilter); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (!dialog->exec()) { return; } if (!dialog->selectedUrls().isEmpty()) { openUrl(dialog->selectedUrls().first()); } } void MainWindow::openUrl(const QUrl& url) { d->setActionsDisabledOnStartMainPageEnabled(true); d->mContextManager->setUrlToSelect(url); d->mViewAction->trigger(); } void MainWindow::showDocumentInFullScreen(const QUrl &url) { d->mContextManager->setUrlToSelect(url); d->mViewAction->trigger(); d->mFullScreenAction->trigger(); } void MainWindow::toggleSlideShow() { if (d->mSlideShow->isRunning()) { d->mSlideShow->pause(); } else { if (!d->mViewAction->isChecked()) { d->mViewAction->trigger(); } if (!d->mFullScreenAction->isChecked()) { d->mFullScreenAction->trigger(); } QList list; for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) { QModelIndex index = d->mDirModel->index(pos, 0); KFileItem item = d->mDirModel->itemForIndex(index); MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); switch (kind) { case MimeTypeUtils::KIND_SVG_IMAGE: case MimeTypeUtils::KIND_RASTER_IMAGE: case MimeTypeUtils::KIND_VIDEO: list << item.url(); break; default: break; } } d->mSlideShow->start(list); } updateSlideShowAction(); } void MainWindow::updateSlideShowAction() { if (d->mSlideShow->isRunning()) { d->mToggleSlideShowAction->setText(i18n("Pause Slideshow")); d->mToggleSlideShowAction->setIcon(QIcon::fromTheme("media-playback-pause")); } else { d->mToggleSlideShowAction->setText(d->mFullScreenAction->isChecked() ? i18n("Resume Slideshow") : i18n("Start Slideshow")); d->mToggleSlideShowAction->setIcon(QIcon::fromTheme("media-playback-start")); } } bool MainWindow::queryClose() { saveConfig(); QList list = DocumentFactory::instance()->modifiedDocumentList(); if (list.size() == 0) { return true; } KGuiItem yes(i18n("Save All Changes"), "document-save-all"); KGuiItem no(i18n("Discard Changes"), "delete"); QString msg = i18np("One image has been modified.", "%1 images have been modified.", list.size()) + '\n' + i18n("If you quit now, your changes will be lost."); int answer = KMessageBox::warningYesNoCancel( this, msg, QString() /* caption */, yes, no); switch (answer) { case KMessageBox::Yes: d->mGvCore->saveAll(); // We need to wait a bit because the DocumentFactory is notified about // saved documents through a queued connection. qApp->processEvents(); return DocumentFactory::instance()->modifiedDocumentList().isEmpty(); case KMessageBox::No: return true; default: // cancel return false; } } void MainWindow::showConfigDialog() { // Save first so changes like thumbnail zoom level are not lost when reloading config saveConfig(); DialogGuard dialog(this); connect(dialog.data(), &KConfigDialog::settingsChanged, this, &MainWindow::loadConfig); dialog->exec(); } void MainWindow::configureShortcuts() { guiFactory()->configureShortcuts(); guiFactory()->refreshActionProperties(); } void MainWindow::toggleMenuBar() { if (!d->mFullScreenAction->isChecked()) { if (!d->mShowMenuBarAction->isChecked()) { const QString accel = d->mShowMenuBarAction->shortcut().toString(); KMessageBox::information(this, i18n("This will hide the menu bar completely." " You can show it again by typing %1.", accel), i18n("Hide menu bar"), QLatin1String("HideMenuBarWarning")); } menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); } } void MainWindow::loadConfig() { d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions()); d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos()); if (GwenviewConfig::historyEnabled()) { d->mFileOpenRecentAction->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); foreach(const QUrl& url, d->mFileOpenRecentAction->urls()) { d->mGvCore->addUrlToRecentFiles(url); } } else { d->mFileOpenRecentAction->clear(); } d->mFileOpenRecentAction->setVisible(GwenviewConfig::historyEnabled()); d->mStartMainPage->loadConfig(); d->mViewMainPage->loadConfig(); d->mBrowseMainPage->loadConfig(); d->mContextManager->loadConfig(); d->mSideBar->loadConfig(); d->loadSplitterConfig(); } void MainWindow::saveConfig() { d->mFileOpenRecentAction->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); d->mViewMainPage->saveConfig(); d->mBrowseMainPage->saveConfig(); d->mContextManager->saveConfig(); d->saveSplitterConfig(); GwenviewConfig::setFullScreenModeActive(isFullScreen()); } void MainWindow::print() { if (!d->mContextManager->currentUrlIsRasterImage()) { return; } Document::Ptr doc = DocumentFactory::instance()->load(d->mContextManager->currentUrl()); PrintHelper printHelper(this); printHelper.print(doc); } void MainWindow::preloadNextUrl() { static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0"; if (disablePreload) { qDebug() << "Preloading disabled"; return; } QItemSelection selection = d->mContextManager->selectionModel()->selection(); if (selection.size() != 1) { return; } QModelIndexList indexList = selection.indexes(); if (indexList.isEmpty()) { return; } QModelIndex index = indexList.at(0); if (!index.isValid()) { return; } if (d->mCurrentMainPageId == ViewMainPageId) { // If we are in view mode, preload the next url, otherwise preload the // selected one int offset = d->mPreloadDirectionIsForward ? 1 : -1; index = d->mDirModel->sibling(index.row() + offset, index.column(), index); if (!index.isValid()) { return; } } KFileItem item = d->mDirModel->itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { QUrl url = item.url(); if (url.isLocalFile()) { QSize size = d->mViewStackedWidget->size(); d->mPreloader->preload(url, size); } } } QSize MainWindow::sizeHint() const { return KXmlGuiWindow::sizeHint().expandedTo(QSize(750, 500)); } void MainWindow::showEvent(QShowEvent *event) { // We need to delay initializing the action state until the menu bar has // been initialized, that's why it's done only in the showEvent() d->mShowMenuBarAction->setChecked(menuBar()->isVisible()); KXmlGuiWindow::showEvent(event); } void MainWindow::resizeEvent(QResizeEvent* event) { KXmlGuiWindow::resizeEvent(event); // This is a small hack to execute code after leaving fullscreen, and after // the window has been resized back to its former size. if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) { if (d->mCurrentMainPageId == BrowseMainPageId) { d->mThumbnailView->scrollToSelectedIndex(); } d->mFullScreenLeftAt = QDateTime(); } } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); Q_UNUSED(event); #ifdef Q_OS_OSX /** * handle Mac OS X file open events (only exist on OS X) */ if (event->type() == QEvent::FileOpen) { QFileOpenEvent *fileOpenEvent = static_cast(event); openUrl(fileOpenEvent->url()); return true; } #endif if (obj == d->mThumbnailView->viewport()) { switch(event->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonDblClick: mouseButtonNavigate(static_cast(event)); break; default: ; } } return false; } void MainWindow::mousePressEvent(QMouseEvent *event) { mouseButtonNavigate(event); KXmlGuiWindow::mousePressEvent(event); } void MainWindow::mouseDoubleClickEvent(QMouseEvent *event) { mouseButtonNavigate(event); KXmlGuiWindow::mouseDoubleClickEvent(event); } void MainWindow::mouseButtonNavigate(QMouseEvent *event) { switch(event->button()) { case Qt::ForwardButton: if (d->mGoToNextAction->isEnabled()) { d->mGoToNextAction->trigger(); return; } break; case Qt::BackButton: if (d->mGoToPreviousAction->isEnabled()) { d->mGoToPreviousAction->trigger(); return; } break; default: ; } } void MainWindow::setDistractionFreeMode(bool value) { d->mFullScreenContent->setDistractionFreeMode(value); } void MainWindow::saveProperties(KConfigGroup& group) { group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId)); group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl().toString()); } void MainWindow::readProperties(const KConfigGroup& group) { const QUrl url = group.readEntry(SESSION_URL_KEY, QUrl()); if (url.isValid()) { goToUrl(url); } MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId))); if (pageId == StartMainPageId) { showStartMainPage(); } else if (pageId == BrowseMainPageId) { d->mBrowseAction->trigger(); } else { d->mViewAction->trigger(); } } void MainWindow::showFirstDocumentReached() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } HudButtonBox* dlg = new HudButtonBox; dlg->setText(i18n("You reached the first document, what do you want to do?")); dlg->addButton(i18n("Stay There")); dlg->addAction(d->mGoToLastAction, i18n("Go to the Last Document")); dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); dlg->addCountDown(15000); d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); } void MainWindow::showLastDocumentReached() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } HudButtonBox* dlg = new HudButtonBox; dlg->setText(i18n("You reached the last document, what do you want to do?")); dlg->addButton(i18n("Stay There")); dlg->addAction(d->mGoToFirstAction, i18n("Go to the First Document")); dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); dlg->addCountDown(15000); d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); } void MainWindow::replaceLocation() { QLineEdit* lineEdit = d->mUrlNavigator->editor()->lineEdit(); // If the text field currently has focus and everything is selected, // pressing the keyboard shortcut returns the whole thing to breadcrumb mode if (d->mUrlNavigator->isUrlEditable() && lineEdit->hasFocus() && lineEdit->selectedText() == lineEdit->text() ) { d->mUrlNavigator->setUrlEditable(false); } else { d->mUrlNavigator->setUrlEditable(true); d->mUrlNavigator->setFocus(); lineEdit->selectAll(); } } } // namespace diff --git a/app/savebar.cpp b/app/savebar.cpp index 2758890d..d3f23b40 100644 --- a/app/savebar.cpp +++ b/app/savebar.cpp @@ -1,367 +1,367 @@ // 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 "savebar.h" // Qt #include #include #include #include #include #include #include // KDE #include #include #include #include // Local #include "lib/document/documentfactory.h" #include "lib/gwenviewconfig.h" #include "lib/memoryutils.h" #include "lib/paintutils.h" namespace Gwenview { QToolButton* createToolButton() { QToolButton* button = new QToolButton; button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->hide(); return button; } struct SaveBarPrivate { SaveBar* q; KActionCollection* mActionCollection; QWidget* mSaveBarWidget; QWidget* mTopRowWidget; QToolButton* mUndoButton; QToolButton* mRedoButton; QToolButton* mSaveCurrentUrlButton; QToolButton* mSaveAsButton; QToolButton* mSaveAllButton; QToolButton* mSaveAllFullScreenButton; QLabel* mMessageLabel; QLabel* mActionsLabel; QFrame* mTooManyChangesFrame; QUrl mCurrentUrl; void createTooManyChangesFrame() { mTooManyChangesFrame = new QFrame; // Icon QLabel* iconLabel = new QLabel; QPixmap pix = KIconLoader::global()->loadIcon( "dialog-warning", KIconLoader::Dialog, KIconLoader::SizeSmall); iconLabel->setPixmap(pix); // Text label QLabel* textLabel = new QLabel; textLabel->setText( i18n("You have modified many images. To avoid memory problems, you should save your changes.") ); mSaveAllFullScreenButton = createToolButton(); // Layout QHBoxLayout* layout = new QHBoxLayout(mTooManyChangesFrame); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(iconLabel); layout->addWidget(textLabel); layout->addWidget(mSaveAllFullScreenButton); mTooManyChangesFrame->hide(); // CSS KColorScheme scheme(mSaveBarWidget->palette().currentColorGroup(), KColorScheme::Window); QColor warningBackgroundColor = scheme.background(KColorScheme::NegativeBackground).color(); QColor warningBorderColor = PaintUtils::adjustedHsv(warningBackgroundColor, 0, 150, 0); QColor warningColor = scheme.foreground(KColorScheme::NegativeText).color(); QString css = ".QFrame {" " background-color: %1;" " border: 1px solid %2;" " border-radius: 4px;" " padding: 3px;" "}" ".QFrame QLabel {" " color: %3;" "}" ; css = css.arg(warningBackgroundColor.name(), warningBorderColor.name(), warningColor.name()); mTooManyChangesFrame->setStyleSheet(css); } void applyNormalStyleSheet() { QColor bgColor = QToolTip::palette().base().color(); QColor borderColor = PaintUtils::adjustedHsv(bgColor, 0, 150, 0); QColor fgColor = QToolTip::palette().text().color(); QString css = "#saveBarWidget {" " background-color: %1;" " border-top: 1px solid %2;" " border-bottom: 1px solid %2;" " color: %3;" "}" ; css = css.arg(bgColor.name(), borderColor.name(), fgColor.name()); mSaveBarWidget->setStyleSheet(css); } void applyFullScreenStyleSheet() { QString css = "#saveBarWidget {" " background-color: #333;" "}"; mSaveBarWidget->setStyleSheet(css); } void updateTooManyChangesFrame(const QList& list) { qreal maxPercentageOfMemoryUsage = GwenviewConfig::percentageOfMemoryUsageWarning(); qulonglong maxMemoryUsage = MemoryUtils::getTotalMemory() * maxPercentageOfMemoryUsage; qulonglong memoryUsage = 0; Q_FOREACH(const QUrl &url, list) { Document::Ptr doc = DocumentFactory::instance()->load(url); memoryUsage += doc->memoryUsage(); } mTooManyChangesFrame->setVisible(memoryUsage > maxMemoryUsage); } void updateTopRowWidget(const QList& lst) { QStringList links; QString message; if (lst.contains(mCurrentUrl)) { message = i18n("Current image modified"); mUndoButton->show(); mRedoButton->show(); if (lst.size() > 1) { QString previous = i18n("Previous modified image"); QString next = i18n("Next modified image"); if (mCurrentUrl == lst[0]) { links << previous; } else { links << QStringLiteral("%1").arg(previous); } if (mCurrentUrl == lst[lst.size() - 1]) { links << next; } else { links << QStringLiteral("%1").arg(next); } } } else { mUndoButton->hide(); mRedoButton->hide(); message = i18np("One image modified", "%1 images modified", lst.size()); if (lst.size() > 1) { links << QStringLiteral("%1").arg(i18n("Go to first modified image")); } else { links << QStringLiteral("%1").arg(i18n("Go to it")); } } mSaveCurrentUrlButton->setVisible(lst.contains(mCurrentUrl)); mSaveAsButton->setVisible(lst.contains(mCurrentUrl)); mSaveAllButton->setVisible(lst.size() >= 1); mMessageLabel->setText(message); mMessageLabel->setMaximumWidth(mMessageLabel->minimumSizeHint().width()); mActionsLabel->setText(links.join(" | ")); } void updateWidgetSizes() { QVBoxLayout* layout = static_cast(mSaveBarWidget->layout()); int topRowHeight = q->window()->isFullScreen() ? 0 : mTopRowWidget->height(); int bottomRowHeight = mTooManyChangesFrame->isVisibleTo(mSaveBarWidget) ? mTooManyChangesFrame->sizeHint().height() : 0; int height = 2 * layout->margin() + topRowHeight + bottomRowHeight; if (topRowHeight > 0 && bottomRowHeight > 0) { height += layout->spacing(); } mSaveBarWidget->setFixedHeight(height); } }; SaveBar::SaveBar(QWidget* parent, KActionCollection* actionCollection) : SlideContainer(parent) , d(new SaveBarPrivate) { d->q = this; d->mActionCollection = actionCollection; d->mSaveBarWidget = new QWidget(); d->mSaveBarWidget->setObjectName(QLatin1String("saveBarWidget")); d->applyNormalStyleSheet(); d->mMessageLabel = new QLabel; d->mMessageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); d->mUndoButton = createToolButton(); d->mRedoButton = createToolButton(); d->mSaveCurrentUrlButton = createToolButton(); d->mSaveAsButton = createToolButton(); d->mSaveAllButton = createToolButton(); d->mActionsLabel = new QLabel; d->mActionsLabel->setAlignment(Qt::AlignCenter); d->mActionsLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); d->createTooManyChangesFrame(); // Setup top row d->mTopRowWidget = new QWidget; QHBoxLayout* rowLayout = new QHBoxLayout(d->mTopRowWidget); rowLayout->addWidget(d->mMessageLabel); rowLayout->setStretchFactor(d->mMessageLabel, 1); rowLayout->addWidget(d->mUndoButton); rowLayout->addWidget(d->mRedoButton); rowLayout->addWidget(d->mActionsLabel); rowLayout->addWidget(d->mSaveCurrentUrlButton); rowLayout->addWidget(d->mSaveAsButton); rowLayout->addWidget(d->mSaveAllButton); - rowLayout->setMargin(0); + rowLayout->setContentsMargins(0, 0, 0, 0); // Setup bottom row QHBoxLayout* bottomRowLayout = new QHBoxLayout; bottomRowLayout->addStretch(); bottomRowLayout->addWidget(d->mTooManyChangesFrame); bottomRowLayout->addStretch(); // Gather everything together QVBoxLayout* layout = new QVBoxLayout(d->mSaveBarWidget); layout->addWidget(d->mTopRowWidget); layout->addLayout(bottomRowLayout); layout->setMargin(3); layout->setSpacing(3); setContent(d->mSaveBarWidget); connect(DocumentFactory::instance(), &DocumentFactory::modifiedDocumentListChanged, this, &SaveBar::updateContent); connect(d->mActionsLabel, &QLabel::linkActivated, this, &SaveBar::triggerAction); } SaveBar::~SaveBar() { delete d; } void SaveBar::initActionDependentWidgets() { d->mUndoButton->setDefaultAction(d->mActionCollection->action("edit_undo")); d->mRedoButton->setDefaultAction(d->mActionCollection->action("edit_redo")); d->mSaveCurrentUrlButton->setDefaultAction(d->mActionCollection->action("file_save")); d->mSaveAsButton->setDefaultAction(d->mActionCollection->action("file_save_as")); // FIXME: Not using an action for now d->mSaveAllButton->setText(i18n("Save All")); d->mSaveAllButton->setIcon(QIcon::fromTheme("document-save-all")); connect(d->mSaveAllButton, &QToolButton::clicked, this, &SaveBar::requestSaveAll); d->mSaveAllFullScreenButton->setText(i18n("Save All")); connect(d->mSaveAllFullScreenButton, &QToolButton::clicked, this, &SaveBar::requestSaveAll); int height = d->mUndoButton->sizeHint().height(); d->mTopRowWidget->setFixedHeight(height); d->updateWidgetSizes(); } void SaveBar::setFullScreenMode(bool isFullScreen) { d->mSaveAllFullScreenButton->setVisible(isFullScreen); if (isFullScreen) { d->applyFullScreenStyleSheet(); } else { d->applyNormalStyleSheet(); } updateContent(); } void SaveBar::updateContent() { QList lst = DocumentFactory::instance()->modifiedDocumentList(); if (window()->isFullScreen()) { d->mTopRowWidget->hide(); } else { d->mTopRowWidget->show(); d->updateTopRowWidget(lst); } d->updateTooManyChangesFrame(lst); d->updateWidgetSizes(); if (lst.isEmpty() || (window()->isFullScreen() && !d->mTooManyChangesFrame->isVisibleTo(d->mSaveBarWidget))) { slideOut(); } else { slideIn(); } } void SaveBar::triggerAction(const QString& action) { QList lst = DocumentFactory::instance()->modifiedDocumentList(); if (action == "first") { emit goToUrl(lst[0]); } else if (action == "previous") { int pos = lst.indexOf(d->mCurrentUrl); --pos; Q_ASSERT(pos >= 0); emit goToUrl(lst[pos]); } else if (action == "next") { int pos = lst.indexOf(d->mCurrentUrl); ++pos; Q_ASSERT(pos < lst.size()); emit goToUrl(lst[pos]); } else { qWarning() << "Unknown action: " << action ; } } void SaveBar::setCurrentUrl(const QUrl &url) { d->mCurrentUrl = url; updateContent(); } } // namespace diff --git a/app/semanticinfocontextmanageritem.cpp b/app/semanticinfocontextmanageritem.cpp index 66e563ba..2e03b0b9 100644 --- a/app/semanticinfocontextmanageritem.cpp +++ b/app/semanticinfocontextmanageritem.cpp @@ -1,467 +1,467 @@ // 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 "semanticinfocontextmanageritem.h" // Qt #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include // Local #include "viewmainpage.h" #include "sidebar.h" #include "ui_semanticinfosidebaritem.h" #include "ui_semanticinfodialog.h" #include #include #include #include #include #include #include #include #include namespace Gwenview { static const int RATING_INDICATOR_HIDE_DELAY = 2000; struct SemanticInfoDialog : public QDialog, public Ui_SemanticInfoDialog { SemanticInfoDialog(QWidget* parent) : QDialog(parent) { setLayout(new QVBoxLayout); QWidget* mainWidget = new QWidget; layout()->addWidget(mainWidget); setupUi(mainWidget); - mainWidget->layout()->setMargin(0); + mainWidget->layout()->setContentsMargins(0, 0, 0, 0); setWindowTitle(mainWidget->windowTitle()); KWindowConfig::restoreWindowSize(windowHandle(), configGroup()); } ~SemanticInfoDialog() override { KConfigGroup group = configGroup(); KWindowConfig::saveWindowSize(windowHandle(), group); } KConfigGroup configGroup() const { KSharedConfigPtr config = KSharedConfig::openConfig(); return KConfigGroup(config, "SemanticInfoDialog"); } }; /** * A QGraphicsPixmapItem-like class, but which inherits from QGraphicsWidget */ class GraphicsPixmapWidget : public QGraphicsWidget { public: void setPixmap(const QPixmap& pix) { mPix = pix; setMinimumSize(pix.size()); } void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) override { painter->drawPixmap( (size().width() - mPix.width()) / 2, (size().height() - mPix.height()) / 2, mPix); } private: QPixmap mPix; }; class RatingIndicator : public HudWidget { public: RatingIndicator() : HudWidget() , mPixmapWidget(new GraphicsPixmapWidget) , mDeleteTimer(new QTimer(this)) { updatePixmap(0); setOpacity(0); init(mPixmapWidget, OptionNone); mDeleteTimer->setInterval(RATING_INDICATOR_HIDE_DELAY); mDeleteTimer->setSingleShot(true); connect(mDeleteTimer, &QTimer::timeout, this, &HudWidget::fadeOut); connect(this, &HudWidget::fadedOut, this, &QObject::deleteLater); } void setRating(int rating) { updatePixmap(rating); update(); mDeleteTimer->start(); fadeIn(); } private: GraphicsPixmapWidget* mPixmapWidget; QTimer* mDeleteTimer; void updatePixmap(int rating) { KRatingPainter ratingPainter; const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); QPixmap pix(iconSize * 5 + ratingPainter.spacing() * 4, iconSize); pix.fill(Qt::transparent); { QPainter painter(&pix); ratingPainter.paint(&painter, pix.rect(), rating); } mPixmapWidget->setPixmap(pix); } }; struct SemanticInfoContextManagerItemPrivate : public Ui_SemanticInfoSideBarItem { SemanticInfoContextManagerItem* q; SideBarGroup* mGroup; KActionCollection* mActionCollection; ViewMainPage* mViewMainPage; QPointer mSemanticInfoDialog; TagInfo mTagInfo; QAction * mEditTagsAction; /** A list of all actions, so that we can disable them when necessary */ QList mActions; QPointer mRatingIndicator; void setupGroup() { mGroup = new SideBarGroup(i18n("Semantic Information"), false); q->setWidget(mGroup); EventWatcher::install(mGroup, QEvent::Show, q, SLOT(update())); QWidget* container = new QWidget; setupUi(container); - container->layout()->setMargin(0); + container->layout()->setContentsMargins(0, 0, 0, 0); mGroup->addWidget(container); formLayout->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); QObject::connect(mRatingWidget, SIGNAL(ratingChanged(int)), q, SLOT(slotRatingChanged(int))); mDescriptionTextEdit->installEventFilter(q); QObject::connect(mTagLabel, &QLabel::linkActivated, mEditTagsAction, &QAction::trigger); } void setupActions() { KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Edit"), mActionCollection); mEditTagsAction = edit->addAction("edit_tags"); mEditTagsAction->setText(i18nc("@action", "Edit Tags")); mEditTagsAction->setIcon(QIcon::fromTheme("tag")); mActionCollection->setDefaultShortcut(mEditTagsAction, Qt::CTRL + Qt::Key_T); QObject::connect(mEditTagsAction, &QAction::triggered, q, &SemanticInfoContextManagerItem::showSemanticInfoDialog); mActions << mEditTagsAction; for (int rating = 0; rating <= 5; ++rating) { QAction * action = edit->addAction(QStringLiteral("rate_%1").arg(rating)); if (rating == 0) { action->setText(i18nc("@action Rating value of zero", "Zero")); } else { action->setText(QString(rating, QChar(0x22C6))); /* 0x22C6 is the 'star' character */ } mActionCollection->setDefaultShortcut(action, Qt::Key_0 + rating); QObject::connect(action, &QAction::triggered, q, [this, rating]() { mRatingWidget->setRating(rating * 2); }); mActions << action; } } void updateTagLabel() { if (q->contextManager()->selectedFileItemList().isEmpty()) { mTagLabel->clear(); return; } AbstractSemanticInfoBackEnd* backEnd = q->contextManager()->dirModel()->semanticInfoBackEnd(); TagInfo::ConstIterator it = mTagInfo.constBegin(), end = mTagInfo.constEnd(); QMap labelMap; for (; it != end; ++it) { SemanticInfoTag tag = it.key(); QString label = backEnd->labelForTag(tag); if (!it.value()) { // Tag is not present for all urls label += '*'; } labelMap[label.toLower()] = label; } QStringList labels(labelMap.values()); QString editLink = i18n("Edit"); QString text = labels.join(", ") + QStringLiteral(" %1").arg(editLink); mTagLabel->setText(text); } void updateSemanticInfoDialog() { mSemanticInfoDialog->mTagWidget->setEnabled(!q->contextManager()->selectedFileItemList().isEmpty()); mSemanticInfoDialog->mTagWidget->setTagInfo(mTagInfo); } }; SemanticInfoContextManagerItem::SemanticInfoContextManagerItem(ContextManager* manager, KActionCollection* actionCollection, ViewMainPage* viewMainPage) : AbstractContextManagerItem(manager) , d(new SemanticInfoContextManagerItemPrivate) { d->q = this; d->mActionCollection = actionCollection; d->mViewMainPage = viewMainPage; connect(contextManager(), &ContextManager::selectionChanged, this, &SemanticInfoContextManagerItem::slotSelectionChanged); connect(contextManager(), &ContextManager::selectionDataChanged, this, &SemanticInfoContextManagerItem::update); connect(contextManager(), &ContextManager::currentDirUrlChanged, this, &SemanticInfoContextManagerItem::update); d->setupActions(); d->setupGroup(); } SemanticInfoContextManagerItem::~SemanticInfoContextManagerItem() { delete d; } inline int ratingForVariant(const QVariant& variant) { if (variant.isValid()) { return variant.toInt(); } else { return 0; } } void SemanticInfoContextManagerItem::slotSelectionChanged() { update(); } void SemanticInfoContextManagerItem::update() { KFileItemList itemList = contextManager()->selectedFileItemList(); bool first = true; int rating = 0; QString description; SortedDirModel* dirModel = contextManager()->dirModel(); // This hash stores for how many items the tag is present // If you have 3 items, and only 2 have the "Holiday" tag, // then tagHash["Holiday"] will be 2 at the end of the loop. typedef QHash TagHash; TagHash tagHash; Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); QVariant value = dirModel->data(index, SemanticInfoDirModel::RatingRole); if (first) { rating = ratingForVariant(value); } else if (rating != ratingForVariant(value)) { // Ratings aren't the same, reset rating = 0; } QString indexDescription = index.data(SemanticInfoDirModel::DescriptionRole).toString(); if (first) { description = indexDescription; } else if (description != indexDescription) { description.clear(); } // Fill tagHash, incrementing the tag count if it's already there TagSet tagSet = TagSet::fromVariant(index.data(SemanticInfoDirModel::TagsRole)); Q_FOREACH(const QString & tag, tagSet) { TagHash::Iterator it = tagHash.find(tag); if (it == tagHash.end()) { tagHash[tag] = 1; } else { ++it.value(); } } first = false; } { SignalBlocker blocker(d->mRatingWidget); d->mRatingWidget->setRating(rating); } d->mDescriptionTextEdit->setText(description); // Init tagInfo from tagHash d->mTagInfo.clear(); int itemCount = itemList.count(); TagHash::ConstIterator it = tagHash.constBegin(), end = tagHash.constEnd(); for (; it != end; ++it) { QString tag = it.key(); int count = it.value(); d->mTagInfo[tag] = count == itemCount; } bool enabled = !contextManager()->selectedFileItemList().isEmpty(); Q_FOREACH(QAction * action, d->mActions) { action->setEnabled(enabled); } d->updateTagLabel(); if (d->mSemanticInfoDialog) { d->updateSemanticInfoDialog(); } } void SemanticInfoContextManagerItem::slotRatingChanged(int rating) { KFileItemList itemList = contextManager()->selectedFileItemList(); // Show rating indicator in view mode, and only if sidebar is not visible if (d->mViewMainPage->isVisible() && !d->mRatingWidget->isVisible()) { if (!d->mRatingIndicator.data()) { d->mRatingIndicator = new RatingIndicator; d->mViewMainPage->showMessageWidget(d->mRatingIndicator, Qt::AlignBottom | Qt::AlignHCenter); } d->mRatingIndicator->setRating(rating); } SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); dirModel->setData(index, rating, SemanticInfoDirModel::RatingRole); } } void SemanticInfoContextManagerItem::storeDescription() { if (!d->mDescriptionTextEdit->document()->isModified()) { return; } d->mDescriptionTextEdit->document()->setModified(false); QString description = d->mDescriptionTextEdit->toPlainText(); KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); dirModel->setData(index, description, SemanticInfoDirModel::DescriptionRole); } } void SemanticInfoContextManagerItem::assignTag(const SemanticInfoTag& tag) { KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); if (!tags.contains(tag)) { tags << tag; dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); } } } void SemanticInfoContextManagerItem::removeTag(const SemanticInfoTag& tag) { KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); if (tags.contains(tag)) { tags.remove(tag); dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); } } } void SemanticInfoContextManagerItem::showSemanticInfoDialog() { if (!d->mSemanticInfoDialog) { d->mSemanticInfoDialog = new SemanticInfoDialog(d->mGroup); d->mSemanticInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); connect(d->mSemanticInfoDialog->mPreviousButton, &QAbstractButton::clicked, d->mActionCollection->action("go_previous"), &QAction::trigger); connect(d->mSemanticInfoDialog->mNextButton, &QAbstractButton::clicked, d->mActionCollection->action("go_next"), &QAction::trigger); connect(d->mSemanticInfoDialog->mButtonBox, &QDialogButtonBox::rejected, d->mSemanticInfoDialog.data(), &QWidget::close); AbstractSemanticInfoBackEnd* backEnd = contextManager()->dirModel()->semanticInfoBackEnd(); d->mSemanticInfoDialog->mTagWidget->setSemanticInfoBackEnd(backEnd); connect(d->mSemanticInfoDialog->mTagWidget, &TagWidget::tagAssigned, this, &SemanticInfoContextManagerItem::assignTag); connect(d->mSemanticInfoDialog->mTagWidget, &TagWidget::tagRemoved, this, &SemanticInfoContextManagerItem::removeTag); } d->updateSemanticInfoDialog(); d->mSemanticInfoDialog->show(); } bool SemanticInfoContextManagerItem::eventFilter(QObject*, QEvent* event) { if (event->type() == QEvent::FocusOut) { storeDescription(); } return false; } } // namespace diff --git a/app/sidebar.cpp b/app/sidebar.cpp index 73950e85..cf2a64cf 100644 --- a/app/sidebar.cpp +++ b/app/sidebar.cpp @@ -1,265 +1,265 @@ /* 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 "sidebar.h" // Qt #include #include #include #include #include #include #include #include // KDE #include #include // Local #include #include namespace Gwenview { /** * A button which always leave room for an icon, even if there is none, so that * all button texts are correctly aligned. */ class SideBarButton : public QToolButton { protected: void paintEvent(QPaintEvent* event) override { forceIcon(); QToolButton::paintEvent(event); } QSize sizeHint() const override { const_cast(this)->forceIcon(); return QToolButton::sizeHint(); } private: void forceIcon() { if (!icon().isNull()) { return; } // Assign an empty icon to the button if there is no icon associated // with the action so that all button texts are correctly aligned. QSize wantedSize = iconSize(); if (mEmptyIcon.isNull() || mEmptyIcon.actualSize(wantedSize) != wantedSize) { QPixmap pix(wantedSize); pix.fill(Qt::transparent); mEmptyIcon.addPixmap(pix); } setIcon(mEmptyIcon); } QIcon mEmptyIcon; }; //- SideBarGroup --------------------------------------------------------------- struct SideBarGroupPrivate { QFrame* mContainer; QLabel* mTitleLabel; bool mDefaultContainerMarginEnabled; }; SideBarGroup::SideBarGroup(const QString& title, bool defaultContainerMarginEnabled) : QFrame() , d(new SideBarGroupPrivate) { d->mContainer = nullptr; d->mTitleLabel = new QLabel(this); d->mDefaultContainerMarginEnabled = defaultContainerMarginEnabled; d->mTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->mTitleLabel->setFixedHeight(d->mTitleLabel->sizeHint().height() * 3 / 2); QFont font(d->mTitleLabel->font()); font.setBold(true); d->mTitleLabel->setFont(font); d->mTitleLabel->setText(title); QVBoxLayout* layout = new QVBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(d->mTitleLabel); d->mTitleLabel->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); clear(); } SideBarGroup::~SideBarGroup() { delete d; } void SideBarGroup::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); if (parentWidget()->layout()->indexOf(this) != 0) { // Draw a separator, but only if we are not the first group QPainter painter(this); QPen pen(palette().mid().color()); painter.setPen(pen); painter.drawLine(rect().topLeft(), rect().topRight()); } } void SideBarGroup::addWidget(QWidget* widget) { widget->setParent(d->mContainer); d->mContainer->layout()->addWidget(widget); } void SideBarGroup::clear() { if (d->mContainer) { d->mContainer->deleteLater(); } d->mContainer = new QFrame(this); QVBoxLayout* containerLayout = new QVBoxLayout(d->mContainer); - containerLayout->setMargin(0); + containerLayout->setContentsMargins(0, 0, 0, 0); containerLayout->setSpacing(0); layout()->addWidget(d->mContainer); if(d->mDefaultContainerMarginEnabled) { d->mContainer->layout()->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); } } void SideBarGroup::addAction(QAction* action) { int size = KIconLoader::global()->currentSize(KIconLoader::Small); QToolButton* button = new SideBarButton(); button->setFocusPolicy(Qt::NoFocus); button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); button->setAutoRaise(true); button->setDefaultAction(action); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->setIconSize(QSize(size, size)); if (action->menu()) { button->setPopupMode(QToolButton::InstantPopup); } addWidget(button); } //- SideBarPage ---------------------------------------------------------------- struct SideBarPagePrivate { QString mTitle; QVBoxLayout* mLayout; }; SideBarPage::SideBarPage(const QString& title) : QWidget() , d(new SideBarPagePrivate) { d->mTitle = title; d->mLayout = new QVBoxLayout(this); - d->mLayout->setMargin(0); + d->mLayout->setContentsMargins(0, 0, 0, 0); } SideBarPage::~SideBarPage() { delete d; } const QString& SideBarPage::title() const { return d->mTitle; } void SideBarPage::addWidget(QWidget* widget) { d->mLayout->addWidget(widget); } void SideBarPage::addStretch() { d->mLayout->addStretch(); } //- SideBar -------------------------------------------------------------------- struct SideBarPrivate { }; SideBar::SideBar(QWidget* parent) : QTabWidget(parent) , d(new SideBarPrivate) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); tabBar()->setDocumentMode(true); tabBar()->setUsesScrollButtons(false); tabBar()->setFocusPolicy(Qt::NoFocus); setTabPosition(QTabWidget::South); setElideMode(Qt::ElideRight); connect(tabBar(), &QTabBar::currentChanged, [=]() { GwenviewConfig::setSideBarPage(currentPage()); }); } SideBar::~SideBar() { delete d; } QSize SideBar::sizeHint() const { return QSize(200, 200); } void SideBar::addPage(SideBarPage* page) { // Prevent emitting currentChanged() while populating pages SignalBlocker blocker(tabBar()); addTab(page, page->title()); } QString SideBar::currentPage() const { return currentWidget()->objectName(); } void SideBar::setCurrentPage(const QString& name) { for (int index = 0; index < count(); ++index) { if (widget(index)->objectName() == name) { setCurrentIndex(index); } } } void SideBar::loadConfig() { setCurrentPage(GwenviewConfig::sideBarPage()); } } // namespace diff --git a/app/viewmainpage.cpp b/app/viewmainpage.cpp index 8969ed94..d33bd29b 100644 --- a/app/viewmainpage.cpp +++ b/app/viewmainpage.cpp @@ -1,874 +1,874 @@ /* 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 "viewmainpage.h" #include "config-gwenview.h" // Qt #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif // Local #include "fileoperations.h" #include #include "splitter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) qDebug() << x #else #define LOG(x) ; #endif const int ViewMainPage::MaxViewCount = 6; /* * Layout of mThumbnailSplitter is: * * +-mThumbnailSplitter------------------------------------------------+ * |+-mAdapterContainer-----------------------------------------------+| * ||+-mDocumentViewContainer----------------------------------------+|| * |||+-DocumentView----------------++-DocumentView-----------------+||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||+-----------------------------++------------------------------+||| * ||+---------------------------------------------------------------+|| * ||+-mToolContainer------------------------------------------------+|| * ||| ||| * ||+---------------------------------------------------------------+|| * ||+-mStatusBarContainer-------------------------------------------+|| * |||[mToggleSideBarButton][mToggleThumbnailBarButton] [mZoomWidget]||| * ||+---------------------------------------------------------------+|| * |+-----------------------------------------------------------------+| * |===================================================================| * |+-mThumbnailBar---------------------------------------------------+| * || || * || || * |+-----------------------------------------------------------------+| * +-------------------------------------------------------------------+ */ struct ViewMainPagePrivate { ViewMainPage* q; SlideShow* mSlideShow; KActionCollection* mActionCollection; GvCore* mGvCore; KModelIndexProxyMapper* mDirModelToBarModelProxyMapper; QSplitter *mThumbnailSplitter; QWidget* mAdapterContainer; DocumentViewController* mDocumentViewController; QList mDocumentViews; DocumentViewSynchronizer* mSynchronizer; QToolButton* mToggleSideBarButton; QToolButton* mToggleThumbnailBarButton; ZoomWidget* mZoomWidget; DocumentViewContainer* mDocumentViewContainer; SlideContainer* mToolContainer; QWidget* mStatusBarContainer; ThumbnailBarView* mThumbnailBar; KToggleAction* mToggleThumbnailBarAction; KToggleAction* mSynchronizeAction; QCheckBox* mSynchronizeCheckBox; KSqueezedTextLabel* mDocumentCountLabel; // Activity Resource events reporting needs to be above KPart, // in the shell itself, to avoid problems with other MDI applications // that use this KPart #ifdef KF5Activities_FOUND QHash mActivityResources; #endif bool mCompareMode; ZoomMode::Enum mZoomMode; void setupThumbnailBar() { mThumbnailBar = new ThumbnailBarView; ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar); mThumbnailBar->setItemDelegate(delegate); mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); } void setupThumbnailBarStyleSheet() { QPalette pal = mGvCore->palette(GvCore::NormalViewPalette); mThumbnailBar->setPalette(pal); Qt::Orientation orientation = mThumbnailBar->orientation(); QColor bgColor = pal.color(QPalette::Normal, QPalette::Base); QColor bgSelColor = pal.color(QPalette::Normal, QPalette::Highlight); QColor bgHovColor = pal.color(QPalette::Normal, QPalette::Highlight); // Avoid dark and bright colors bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4); // Hover uses lighter/faded version of select color. Combine with bgColor to adapt to different backgrounds bgHovColor.setHsv(bgHovColor.hue(), (bgHovColor.saturation() / 2), ((bgHovColor.value() + bgColor.value()) / 2)); QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value())); QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value())); QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value())); QString itemCss = "QListView::item {" " background-color: %1;" " border-left: 1px solid %2;" " border-right: 1px solid %3;" "}"; itemCss = itemCss.arg( StyleSheetUtils::gradient(orientation, bgColor, 46), StyleSheetUtils::gradient(orientation, leftBorderColor, 36), StyleSheetUtils::gradient(orientation, rightBorderColor, 26)); QString itemSelCss = "QListView::item:selected {" " background-color: %1;" " border-left: 1px solid %2;" " border-right: 1px solid %2;" "}"; itemSelCss = itemSelCss.arg( StyleSheetUtils::gradient(orientation, bgSelColor, 56), StyleSheetUtils::rgba(borderSelColor)); QString itemHovCss = "QListView::item:hover:!selected {" " background-color: %1;" " border-left: 1px solid %2;" " border-right: 1px solid %3;" "}"; itemHovCss = itemHovCss.arg( StyleSheetUtils::gradient(orientation, bgHovColor, 56), StyleSheetUtils::rgba(leftBorderColor), StyleSheetUtils::rgba(rightBorderColor)); QString css = itemCss + itemSelCss + itemHovCss; if (orientation == Qt::Vertical) { css.replace("left", "top").replace("right", "bottom"); } mThumbnailBar->setStyleSheet(css); } void setupAdapterContainer() { mAdapterContainer = new QWidget; QVBoxLayout* layout = new QVBoxLayout(mAdapterContainer); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); mDocumentViewContainer = new DocumentViewContainer; mDocumentViewContainer->setAutoFillBackground(true); mDocumentViewContainer->setBackgroundRole(QPalette::Base); layout->addWidget(mDocumentViewContainer); layout->addWidget(mToolContainer); layout->addWidget(mStatusBarContainer); } void setupDocumentViewController() { mDocumentViewController = new DocumentViewController(mActionCollection, q); mDocumentViewController->setZoomWidget(mZoomWidget); mDocumentViewController->setToolContainer(mToolContainer); mSynchronizer = new DocumentViewSynchronizer(&mDocumentViews, q); } DocumentView* createDocumentView() { DocumentView* view = mDocumentViewContainer->createView(); // Connect context menu // If you need to connect another view signal, make sure it is disconnected in deleteDocumentView QObject::connect(view, &DocumentView::contextMenuRequested, q, &ViewMainPage::showContextMenu); QObject::connect(view, &DocumentView::completed, q, &ViewMainPage::completed); QObject::connect(view, &DocumentView::previousImageRequested, q, &ViewMainPage::previousImageRequested); QObject::connect(view, &DocumentView::nextImageRequested, q, &ViewMainPage::nextImageRequested); QObject::connect(view, &DocumentView::openUrlRequested, q, &ViewMainPage::openUrlRequested); QObject::connect(view, &DocumentView::openDirUrlRequested, q, &ViewMainPage::openDirUrlRequested); QObject::connect(view, &DocumentView::captionUpdateRequested, q, &ViewMainPage::captionUpdateRequested); QObject::connect(view, &DocumentView::toggleFullScreenRequested, q, &ViewMainPage::toggleFullScreenRequested); QObject::connect(view, &DocumentView::focused, q, &ViewMainPage::slotViewFocused); QObject::connect(view, &DocumentView::hudTrashClicked, q, &ViewMainPage::trashView); QObject::connect(view, &DocumentView::hudDeselectClicked, q, &ViewMainPage::deselectView); QObject::connect(view, &DocumentView::videoFinished, mSlideShow, &SlideShow::resumeAndGoToNextUrl); mDocumentViews << view; #ifdef KF5Activities_FOUND mActivityResources.insert(view, new KActivities::ResourceInstance(q->window()->winId(), view)); #endif return view; } void deleteDocumentView(DocumentView* view) { if (mDocumentViewController->view() == view) { mDocumentViewController->setView(nullptr); } // Make sure we do not get notified about this view while it is going away. // mDocumentViewController->deleteView() animates the view deletion so // the view still exists for a short while when we come back to the // event loop) QObject::disconnect(view, nullptr, q, nullptr); QObject::disconnect(view, nullptr, mSlideShow, nullptr); mDocumentViews.removeOne(view); #ifdef KF5Activities_FOUND mActivityResources.remove(view); #endif mDocumentViewContainer->deleteView(view); } void setupToolContainer() { mToolContainer = new SlideContainer; mToolContainer->setAutoFillBackground(true); mToolContainer->setBackgroundRole(QPalette::Mid); } void setupStatusBar() { mStatusBarContainer = new QWidget; mToggleSideBarButton = new StatusBarToolButton; mToggleThumbnailBarButton = new StatusBarToolButton; mZoomWidget = new ZoomWidget; mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize")); mSynchronizeCheckBox->hide(); mDocumentCountLabel = new KSqueezedTextLabel; mDocumentCountLabel->setAlignment(Qt::AlignCenter); mDocumentCountLabel->setTextElideMode(Qt::ElideRight); QMargins labelMargins = mDocumentCountLabel->contentsMargins(); labelMargins.setLeft(15); labelMargins.setRight(15); mDocumentCountLabel->setContentsMargins(labelMargins); QHBoxLayout* layout = new QHBoxLayout(mStatusBarContainer); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(mToggleSideBarButton); layout->addWidget(mToggleThumbnailBarButton); layout->addStretch(); layout->addWidget(mSynchronizeCheckBox); // Ensure document count label takes up all available space, // so its autohide feature works properly (stretch factor = 1) layout->addWidget(mDocumentCountLabel, 1); layout->addStretch(); layout->addWidget(mZoomWidget); } void setupSplitter() { Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q); mThumbnailBar->setOrientation(orientation); mThumbnailBar->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount()); mThumbnailSplitter->addWidget(mAdapterContainer); mThumbnailSplitter->addWidget(mThumbnailBar); mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); QVBoxLayout* layout = new QVBoxLayout(q); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(mThumbnailSplitter); } void saveSplitterConfig() { if (mThumbnailBar->isVisible()) { GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes()); } } DocumentView* currentView() const { return mDocumentViewController->view(); } void setCurrentView(DocumentView* view) { DocumentView* oldView = currentView(); if (view == oldView) { return; } if (oldView) { oldView->setCurrent(false); #ifdef KF5Activities_FOUND Q_ASSERT(mActivityResources.contains(oldView)); mActivityResources.value(oldView)->notifyFocusedOut(); #endif } view->setCurrent(true); mDocumentViewController->setView(view); mSynchronizer->setCurrentView(view); QModelIndex index = indexForView(view); if (index.isValid()) { // Index may be invalid when Gwenview is started as // `gwenview /foo/image.png` because in this situation it loads image.png // *before* listing /foo (because it matters less to the user) mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); } #ifdef KF5Activities_FOUND Q_ASSERT(mActivityResources.contains(view)); mActivityResources.value(view)->notifyFocusedIn(); #endif QObject::connect(view, &DocumentView::currentToolChanged, q, &ViewMainPage::updateFocus); } QModelIndex indexForView(DocumentView* view) const { QUrl url = view->url(); if (!url.isValid()) { qWarning() << "View does not display any document!"; return QModelIndex(); } SortedDirModel* dirModel = mGvCore->sortedDirModel(); QModelIndex srcIndex = dirModel->indexForUrl(url); if (!mDirModelToBarModelProxyMapper) { // Delay the initialization of the mapper to its first use because // mThumbnailBar->model() is not set after ViewMainPage ctor is // done. const_cast(this)->mDirModelToBarModelProxyMapper = new KModelIndexProxyMapper(dirModel, mThumbnailBar->model(), q); } QModelIndex index = mDirModelToBarModelProxyMapper->mapLeftToRight(srcIndex); return index; } void applyPalette(bool fullScreenMode) { mDocumentViewContainer->applyPalette(mGvCore->palette(fullScreenMode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); setupThumbnailBarStyleSheet(); } void updateDocumentCountLabel() { const int current = mThumbnailBar->currentIndex().row() + 1; // zero-based const int total = mThumbnailBar->model()->rowCount(); const QString text = i18nc("@info:status %1 current document index, %2 total documents", "%1 of %2", current, total); mDocumentCountLabel->setText(text); } }; ViewMainPage::ViewMainPage(QWidget* parent, SlideShow* slideShow, KActionCollection* actionCollection, GvCore* gvCore) : QWidget(parent) , d(new ViewMainPagePrivate) { d->q = this; d->mDirModelToBarModelProxyMapper = nullptr; // Initialized later d->mSlideShow = slideShow; d->mActionCollection = actionCollection; d->mGvCore = gvCore; d->mCompareMode = false; QShortcut* enterKeyShortcut = new QShortcut(Qt::Key_Return, this); connect(enterKeyShortcut, &QShortcut::activated, this, &ViewMainPage::slotEnterPressed); d->setupToolContainer(); d->setupStatusBar(); d->setupAdapterContainer(); d->setupThumbnailBar(); d->setupSplitter(); d->setupDocumentViewController(); KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); d->mToggleThumbnailBarAction = view->add(QStringLiteral("toggle_thumbnailbar")); d->mToggleThumbnailBarAction->setText(i18n("Thumbnail Bar")); d->mToggleThumbnailBarAction->setIcon(QIcon::fromTheme("folder-image")); actionCollection->setDefaultShortcut(d->mToggleThumbnailBarAction, Qt::CTRL + Qt::Key_B); connect(d->mToggleThumbnailBarAction, &KToggleAction::triggered, this, &ViewMainPage::setThumbnailBarVisibility); d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction); d->mSynchronizeAction = view->add("synchronize_views"); d->mSynchronizeAction->setText(i18n("Synchronize")); actionCollection->setDefaultShortcut(d->mSynchronizeAction, Qt::CTRL + Qt::Key_Y); connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizer, &DocumentViewSynchronizer::setActive); // Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync connect(d->mSynchronizeAction, &QAction::toggled, d->mSynchronizeCheckBox, &QAbstractButton::setChecked); connect(d->mSynchronizeCheckBox, &QAbstractButton::toggled, d->mSynchronizeAction, &QAction::setChecked); // Connections for the document count connect(d->mThumbnailBar, &ThumbnailBarView::rowsInsertedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved); connect(d->mThumbnailBar, &ThumbnailBarView::rowsRemovedSignal, this, &ViewMainPage::slotDirModelItemsAddedOrRemoved); installEventFilter(this); } ViewMainPage::~ViewMainPage() { delete d; } void ViewMainPage::loadConfig() { d->applyPalette(window()->isFullScreen()); // FIXME: Not symmetric with saveConfig(). Check if it matters. Q_FOREACH(DocumentView * view, d->mDocumentViews) { view->loadAdapterConfig(); } Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); d->mThumbnailBar->setOrientation(orientation); d->setupThumbnailBarStyleSheet(); d->mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible()); int oldRowCount = d->mThumbnailBar->rowCount(); int newRowCount = GwenviewConfig::thumbnailBarRowCount(); if (oldRowCount != newRowCount) { d->mThumbnailBar->setUpdatesEnabled(false); int gridSize = d->mThumbnailBar->gridSize().width(); d->mThumbnailBar->setRowCount(newRowCount); // Adjust splitter to ensure thumbnail size remains the same int delta = (newRowCount - oldRowCount) * gridSize; QList sizes = d->mThumbnailSplitter->sizes(); Q_ASSERT(sizes.count() == 2); sizes[0] -= delta; sizes[1] += delta; d->mThumbnailSplitter->setSizes(sizes); d->mThumbnailBar->setUpdatesEnabled(true); } d->mZoomMode = GwenviewConfig::zoomMode(); } void ViewMainPage::saveConfig() { d->saveSplitterConfig(); GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked()); } void ViewMainPage::setThumbnailBarVisibility(bool visible) { d->saveSplitterConfig(); d->mThumbnailBar->setVisible(visible); } int ViewMainPage::statusBarHeight() const { return d->mStatusBarContainer->height(); } void ViewMainPage::setStatusBarVisible(bool visible) { d->mStatusBarContainer->setVisible(visible); } void ViewMainPage::setFullScreenMode(bool fullScreenMode) { if (fullScreenMode) { d->mThumbnailBar->setVisible(false); } else { d->mThumbnailBar->setVisible(d->mToggleThumbnailBarAction->isChecked()); } d->applyPalette(fullScreenMode); d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode); } ThumbnailBarView* ViewMainPage::thumbnailBar() const { return d->mThumbnailBar; } inline void addActionToMenu(QMenu* menu, KActionCollection* actionCollection, const char* name) { QAction* action = actionCollection->action(name); if (action) { menu->addAction(action); } } void ViewMainPage::showContextMenu() { QMenu menu(this); addActionToMenu(&menu, d->mActionCollection, "fullscreen"); menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "go_previous"); addActionToMenu(&menu, d->mActionCollection, "go_next"); if (d->currentView()->canZoom()) { menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "view_actual_size"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_in"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_out"); } if (d->mCompareMode) { menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "synchronize_views"); } menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "file_copy_to"); addActionToMenu(&menu, d->mActionCollection, "file_move_to"); addActionToMenu(&menu, d->mActionCollection, "file_link_to"); menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "file_open_with"); addActionToMenu(&menu, d->mActionCollection, "file_open_containing_folder"); menu.exec(QCursor::pos()); } QSize ViewMainPage::sizeHint() const { return QSize(400, 300); } QSize ViewMainPage::minimumSizeHint() const { if (!layout()) { return QSize(); } QSize minimumSize = layout()->minimumSize(); if (window()->isFullScreen()) { // Check minimum width of the overlay fullscreen bar // since there is no layout link which could do this const FullScreenBar* fullScreenBar = findChild(); if (fullScreenBar && fullScreenBar->layout()) { const int fullScreenBarWidth = fullScreenBar->layout()->minimumSize().width(); if (fullScreenBarWidth > minimumSize.width()) { minimumSize.setWidth(fullScreenBarWidth); } } } return minimumSize; } QUrl ViewMainPage::url() const { GV_RETURN_VALUE_IF_FAIL(d->currentView(), QUrl()); return d->currentView()->url(); } Document::Ptr ViewMainPage::currentDocument() const { if (!d->currentView()) { LOG("!d->documentView()"); return Document::Ptr(); } return d->currentView()->document(); } bool ViewMainPage::isEmpty() const { return !currentDocument(); } RasterImageView* ViewMainPage::imageView() const { if (!d->currentView()) { return nullptr; } return d->currentView()->imageView(); } DocumentView* ViewMainPage::documentView() const { return d->currentView(); } void ViewMainPage::openUrl(const QUrl &url) { openUrls(QList() << url, url); } void ViewMainPage::openUrls(const QList& allUrls, const QUrl ¤tUrl) { DocumentView::Setup setup; QSet urls = allUrls.toSet(); d->mCompareMode = urls.count() > 1; typedef QMap ViewForUrlMap; ViewForUrlMap viewForUrlMap; if (!d->mDocumentViews.isEmpty()) { d->mDocumentViewContainer->updateSetup(d->mDocumentViews.last()); } if (d->mDocumentViews.isEmpty() || d->mZoomMode == ZoomMode::Autofit) { setup.valid = true; setup.zoomToFit = true; } else { setup = d->mDocumentViews.last()->setup(); } // Destroy views which show urls we don't care about, remove from "urls" the // urls which already have a view. Q_FOREACH(DocumentView * view, d->mDocumentViews) { QUrl url = view->url(); if (urls.contains(url)) { // view displays an url we must display, keep it urls.remove(url); viewForUrlMap.insert(url, view); } else { // view url is not interesting, drop it d->deleteDocumentView(view); } } // Create view for remaining urls Q_FOREACH(const QUrl &url, urls) { if (d->mDocumentViews.count() >= MaxViewCount) { qWarning() << "Too many documents to show"; break; } DocumentView* view = d->createDocumentView(); viewForUrlMap.insert(url, view); } // Set sortKey to match url order int sortKey = 0; Q_FOREACH(const QUrl &url, allUrls) { viewForUrlMap[url]->setSortKey(sortKey); ++sortKey; } d->mDocumentViewContainer->updateLayout(); // Load urls for new views. Do it only now because the view must have the // correct size before it starts loading its url. Do not do it later because // view->url() needs to be set for the next loop. ViewForUrlMap::ConstIterator it = viewForUrlMap.constBegin(), end = viewForUrlMap.constEnd(); for (; it != end; ++it) { QUrl url = it.key(); DocumentView* view = it.value(); DocumentView::Setup savedSetup = d->mDocumentViewContainer->savedSetup(url); view->openUrl(url, d->mZoomMode == ZoomMode::Individual && savedSetup.valid ? savedSetup : setup); #ifdef KF5Activities_FOUND d->mActivityResources.value(view)->setUri(url); #endif } // Init views Q_FOREACH(DocumentView * view, d->mDocumentViews) { view->setCompareMode(d->mCompareMode); if (view->url() == currentUrl) { d->setCurrentView(view); } else { view->setCurrent(false); } } d->mSynchronizeCheckBox->setVisible(d->mCompareMode); if (d->mCompareMode) { d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked()); } else { d->mSynchronizer->setActive(false); } d->updateDocumentCountLabel(); d->mDocumentCountLabel->setVisible(!d->mCompareMode); } void ViewMainPage::reload() { DocumentView *view = d->currentView(); if (!view) { return; } Document::Ptr doc = view->document(); if (!doc) { qWarning() << "!doc"; return; } if (doc->isModified()) { KGuiItem cont = KStandardGuiItem::cont(); cont.setText(i18nc("@action:button", "Discard Changes and Reload")); int answer = KMessageBox::warningContinueCancel(this, i18nc("@info", "This image has been modified. Reloading it will discard all your changes."), QString() /* caption */, cont); if (answer != KMessageBox::Continue) { return; } } doc->reload(); // Call openUrl again because DocumentView may need to switch to a new // adapter (for example because document was broken and it is not anymore) d->currentView()->openUrl(doc->url(), d->currentView()->setup()); } void ViewMainPage::reset() { d->mDocumentViewController->reset(); d->mDocumentViewContainer->reset(); d->mDocumentViews.clear(); } void ViewMainPage::slotViewFocused(DocumentView* view) { d->setCurrentView(view); } void ViewMainPage::slotEnterPressed() { DocumentView *view = d->currentView(); if (view) { AbstractRasterImageViewTool *tool = view->currentTool(); if (tool) { QKeyEvent event(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier); tool->keyPressEvent(&event); if (event.isAccepted()) { return; } } } emit goToBrowseModeRequested(); } bool ViewMainPage::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::ShortcutOverride) { const int key = static_cast(event)->key(); if (key == Qt::Key_Space || key == Qt::Key_Escape) { const DocumentView* view = d->currentView(); if (view) { AbstractRasterImageViewTool* tool = view->currentTool(); if (tool) { QKeyEvent toolKeyEvent(QEvent::KeyPress, key, Qt::NoModifier); tool->keyPressEvent(&toolKeyEvent); if (toolKeyEvent.isAccepted()) { event->accept(); } } } } } return QWidget::eventFilter(watched, event); } void ViewMainPage::trashView(DocumentView* view) { QUrl url = view->url(); deselectView(view); FileOperations::trash(QList() << url, this); } void ViewMainPage::deselectView(DocumentView* view) { DocumentView* newCurrentView = nullptr; if (view == d->currentView()) { // We need to find a new view to set as current int idx = d->mDocumentViews.indexOf(view); if (idx + 1 < d->mDocumentViews.count()) { newCurrentView = d->mDocumentViews.at(idx + 1); } else if (idx > 0) { newCurrentView = d->mDocumentViews.at(idx - 1); } else { GV_WARN_AND_RETURN("No view found to set as current"); } } QModelIndex index = d->indexForView(view); QItemSelectionModel* selectionModel = d->mThumbnailBar->selectionModel(); selectionModel->select(index, QItemSelectionModel::Deselect); if (newCurrentView) { d->setCurrentView(newCurrentView); } } QToolButton* ViewMainPage::toggleSideBarButton() const { return d->mToggleSideBarButton; } void ViewMainPage::showMessageWidget(QGraphicsWidget* widget, Qt::Alignment align) { d->mDocumentViewContainer->showMessageWidget(widget, align); } void ViewMainPage::updateFocus(const AbstractRasterImageViewTool* tool) { if (!tool) { d->mDocumentViewContainer->setFocus(); } } void ViewMainPage::slotDirModelItemsAddedOrRemoved() { d->updateDocumentCountLabel(); } } // namespace diff --git a/lib/crop/cropwidget.cpp b/lib/crop/cropwidget.cpp index 775f7cb3..edf4fa57 100644 --- a/lib/crop/cropwidget.cpp +++ b/lib/crop/cropwidget.cpp @@ -1,572 +1,572 @@ // 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 // Qt #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include // Local #include #include #include "croptool.h" #include "cropwidget.h" #include "flowlayout.h" namespace Gwenview { // Euclidean algorithm to compute the greatest common divisor of two integers. // Found at: // http://en.wikipedia.org/wiki/Euclidean_algorithm static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } static QSize ratio(const QSize &size) { const int divisor = gcd(size.width(), size.height()); return size / divisor; } struct CropWidgetPrivate : public QWidget { CropWidget* q; QList mAdvancedWidgets; QWidget* mPreserveAspectRatioWidget; QCheckBox* advancedCheckBox; QComboBox* ratioComboBox; QSpinBox* widthSpinBox; QSpinBox* heightSpinBox; QSpinBox* leftSpinBox; QSpinBox* topSpinBox; QCheckBox* preserveAspectRatioCheckBox; QDialogButtonBox* dialogButtonBox; Document::Ptr mDocument; CropTool* mCropTool; bool mUpdatingFromCropTool; int mCurrentImageComboBoxIndex; int mCropRatioComboBoxCurrentIndex; bool ratioIsConstrained() const { return cropRatio() > 0; } QSizeF chosenRatio() const { // A size of 0 represents no ratio, i.e. the combobox is empty if (ratioComboBox->currentText().isEmpty()) { return QSizeF(0, 0); } // A preset ratio is selected const int index = ratioComboBox->currentIndex(); if (index != -1 && ratioComboBox->currentText() == ratioComboBox->itemText(index)) { return ratioComboBox->currentData().toSizeF(); } // A custom ratio has been entered, extract ratio from the text // If invalid, return zero size instead const QStringList lst = ratioComboBox->currentText().split(QLatin1Char(':')); if (lst.size() != 2) { return QSizeF(0, 0); } bool ok; const double width = lst[0].toDouble(&ok); if (!ok) { return QSizeF(0, 0); } const double height = lst[1].toDouble(&ok); if (!ok) { return QSizeF(0, 0); } // Valid custom value return QSizeF(width, height); } void setChosenRatio(QSizeF size) const { // Size matches preset ratio, let's set the combobox to that const int index = ratioComboBox->findData(size); if (index >= 0) { ratioComboBox->setCurrentIndex(index); return; } // Deselect whatever was selected if anything ratioComboBox->setCurrentIndex(-1); // If size is 0 (represents blank combobox, i.e., unrestricted) if (size.isEmpty()) { ratioComboBox->clearEditText(); return; } // Size must be custom ratio, convert to text and add to combobox QString ratioString = QStringLiteral("%1:%2").arg(size.width()).arg(size.height()); ratioComboBox->setCurrentText(ratioString); } double cropRatio() const { if (q->advancedSettingsEnabled()) { QSizeF size = chosenRatio(); if (size.isEmpty()) { return 0; } return size.height() / size.width(); } if (q->preserveAspectRatio()) { QSizeF size = ratio(mDocument->size()); return size.height() / size.width(); } return 0; } void addRatioToComboBox(const QSizeF& size, const QString& label = QString()) { QString text = label.isEmpty() ? QStringLiteral("%1:%2").arg(size.width()).arg(size.height()) : label; ratioComboBox->addItem(text, QVariant(size)); } void addSectionHeaderToComboBox(const QString& title) { // Insert a line ratioComboBox->insertSeparator(ratioComboBox->count()); // Insert our section header // This header is made of a separator with a text. We reset // Qt::AccessibleDescriptionRole to the header text otherwise QComboBox // delegate will draw a separator line instead of our text. int index = ratioComboBox->count(); ratioComboBox->insertSeparator(index); ratioComboBox->setItemText(index, title); ratioComboBox->setItemData(index, title, Qt::AccessibleDescriptionRole); ratioComboBox->setItemData(index, Qt::AlignHCenter, Qt::TextAlignmentRole); } void initRatioComboBox() { QList ratioList; const qreal sqrt2 = qSqrt(2.); ratioList << QSizeF(16, 9) << QSizeF(7, 5) << QSizeF(3, 2) << QSizeF(4, 3) << QSizeF(5, 4); addRatioToComboBox(ratio(mDocument->size()), i18n("Current Image")); mCurrentImageComboBoxIndex = ratioComboBox->count() - 1; // We need to refer to this ratio later addRatioToComboBox(QSizeF(1, 1), i18n("Square")); addRatioToComboBox(ratio(QApplication::desktop()->screenGeometry().size()), i18n("This Screen")); addSectionHeaderToComboBox(i18n("Landscape")); Q_FOREACH(const QSizeF& size, ratioList) { addRatioToComboBox(size); } addRatioToComboBox(QSizeF(sqrt2, 1), i18n("ISO (A4, A3...)")); addRatioToComboBox(QSizeF(11, 8.5), i18n("US Letter")); addSectionHeaderToComboBox(i18n("Portrait")); Q_FOREACH(QSizeF size, ratioList) { size.transpose(); addRatioToComboBox(size); } addRatioToComboBox(QSizeF(1, sqrt2), i18n("ISO (A4, A3...)")); addRatioToComboBox(QSizeF(8.5, 11), i18n("US Letter")); ratioComboBox->setMaxVisibleItems(ratioComboBox->count()); ratioComboBox->clearEditText(); QLineEdit* edit = qobject_cast(ratioComboBox->lineEdit()); Q_ASSERT(edit); // Do not use i18n("%1:%2") because ':' should not be translated, it is // used to parse the ratio string. edit->setPlaceholderText(QStringLiteral("%1:%2").arg(i18n("Width"), i18n("Height"))); // Enable clear button edit->setClearButtonEnabled(true); // Must manually adjust minimum width because the auto size adjustment doesn't take the // clear button into account const int width = ratioComboBox->minimumSizeHint().width(); ratioComboBox->setMinimumWidth(width + 24); mCropRatioComboBoxCurrentIndex = -1; ratioComboBox->setCurrentIndex(mCropRatioComboBoxCurrentIndex); } QRect cropRect() const { QRect rect( leftSpinBox->value(), topSpinBox->value(), widthSpinBox->value(), heightSpinBox->value() ); return rect; } void initSpinBoxes() { QSize size = mDocument->size(); leftSpinBox->setMaximum(size.width()); widthSpinBox->setMaximum(size.width()); topSpinBox->setMaximum(size.height()); heightSpinBox->setMaximum(size.height()); // When users change the crop rectangle, QSpinBox::setMaximum will be called // again, which then adapts the sizeHint due to a different maximum number // of digits, leading to horizontal movement in the layout. This can be // avoided by setting the minimum width so it fits the largest value possible. leftSpinBox->setMinimumWidth(leftSpinBox->sizeHint().width()); widthSpinBox->setMinimumWidth(widthSpinBox->sizeHint().width()); topSpinBox->setMinimumWidth(topSpinBox->sizeHint().width()); heightSpinBox->setMinimumWidth(heightSpinBox->sizeHint().width()); } void initDialogButtonBox() { QPushButton* cropButton = dialogButtonBox->button(QDialogButtonBox::Ok); cropButton->setIcon(QIcon::fromTheme(QStringLiteral("transform-crop-and-resize"))); cropButton->setText(i18n("Crop")); QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, q, &CropWidget::cropRequested); QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, q, &CropWidget::done); } QWidget* boxWidget(QWidget* parent = nullptr) { QWidget* widget = new QWidget(parent); QHBoxLayout* layout = new QHBoxLayout(widget); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(2); return widget; } void setupUi(QWidget* cropWidget) { cropWidget->setObjectName(QStringLiteral("CropWidget")); FlowLayout* flowLayout = new FlowLayout(cropWidget, 6, 0); flowLayout->setObjectName(QStringLiteral("CropWidgetFlowLayout")); flowLayout->setAlignment(Qt::AlignCenter); flowLayout->setVerticalSpacing(6); // (1) Checkbox QWidget* box = boxWidget(cropWidget); advancedCheckBox = new QCheckBox(i18nc("@option:check", "Advanced settings"), box); advancedCheckBox->setFocusPolicy(Qt::NoFocus); box->layout()->addWidget(advancedCheckBox); flowLayout->addWidget(box); flowLayout->addSpacing(14); // (2) Ratio combobox (Advanced settings) box = boxWidget(cropWidget); mAdvancedWidgets << box; QLabel* label = new QLabel(i18nc("@label:listbox", "Aspect ratio:"), box); label->setMargin(4); box->layout()->addWidget(label); ratioComboBox = new QComboBox(box); ratioComboBox->setEditable(true); ratioComboBox->setInsertPolicy(QComboBox::NoInsert); box->layout()->addWidget(ratioComboBox); flowLayout->addWidget(box); flowLayout->addSpacing(8); // (3) Size spinboxes (Advanced settings) box = boxWidget(cropWidget); mAdvancedWidgets << box; label = new QLabel(i18nc("@label:spinbox", "Size:"), box); label->setMargin(4); box->layout()->addWidget(label); QHBoxLayout* innerLayout = new QHBoxLayout(); innerLayout->setSpacing(3); widthSpinBox = new QSpinBox(box); widthSpinBox->setAlignment(Qt::AlignCenter); innerLayout->addWidget(widthSpinBox); heightSpinBox = new QSpinBox(box); heightSpinBox->setAlignment(Qt::AlignCenter); innerLayout->addWidget(heightSpinBox); box->layout()->addItem(innerLayout); flowLayout->addWidget(box); flowLayout->addSpacing(8); // (4) Position spinboxes (Advanced settings) box = boxWidget(cropWidget); mAdvancedWidgets << box; label = new QLabel(i18nc("@label:spinbox", "Position:"), box); label->setMargin(4); box->layout()->addWidget(label); innerLayout = new QHBoxLayout(); innerLayout->setSpacing(3); leftSpinBox = new QSpinBox(box); leftSpinBox->setAlignment(Qt::AlignCenter); innerLayout->addWidget(leftSpinBox); topSpinBox = new QSpinBox(box); topSpinBox->setAlignment(Qt::AlignCenter); innerLayout->addWidget(topSpinBox); box->layout()->addItem(innerLayout); flowLayout->addWidget(box); flowLayout->addSpacing(18); // (5) Preserve ratio checkbox mPreserveAspectRatioWidget = boxWidget(cropWidget); preserveAspectRatioCheckBox = new QCheckBox(i18nc("@option:check", "Preserve aspect ratio"), mPreserveAspectRatioWidget); mPreserveAspectRatioWidget->layout()->addWidget(preserveAspectRatioCheckBox); flowLayout->addWidget(mPreserveAspectRatioWidget); flowLayout->addSpacing(18); // (6) Dialog buttons box = boxWidget(cropWidget); dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok, box); box->layout()->addWidget(dialogButtonBox); flowLayout->addWidget(box); } }; CropWidget::CropWidget(QWidget* parent, RasterImageView* imageView, CropTool* cropTool) : QWidget(parent) , d(new CropWidgetPrivate) { setWindowFlags(Qt::Tool); d->q = this; d->mDocument = imageView->document(); d->mUpdatingFromCropTool = false; d->mCropTool = cropTool; d->setupUi(this); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); connect(d->advancedCheckBox, &QCheckBox::toggled, this, &CropWidget::slotAdvancedCheckBoxToggled); for (auto w : d->mAdvancedWidgets) { w->setVisible(false); } connect(d->preserveAspectRatioCheckBox, &QCheckBox::toggled, this, &CropWidget::applyRatioConstraint); d->initRatioComboBox(); connect(d->mCropTool, &CropTool::rectUpdated, this, &CropWidget::setCropRect); connect(d->leftSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotPositionChanged); connect(d->topSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotPositionChanged); connect(d->widthSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotWidthChanged); connect(d->heightSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &CropWidget::slotHeightChanged); d->initDialogButtonBox(); // We need to listen for both signals because the combobox is multi-function: // Text Changed: required so that manual ratio entry is detected (index doesn't change) // Index Changed: required so that choosing an item with the same text is detected (e.g. going from US Letter portrait // to US Letter landscape) connect(d->ratioComboBox, &QComboBox::editTextChanged, this, &CropWidget::slotRatioComboBoxChanged); connect(d->ratioComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &CropWidget::slotRatioComboBoxChanged); // Don't do this before signals are connected, otherwise the tool won't get // initialized d->initSpinBoxes(); setCropRect(d->mCropTool->rect()); } CropWidget::~CropWidget() { delete d; } void CropWidget::setAdvancedSettingsEnabled(bool enable) { d->advancedCheckBox->setChecked(enable); } bool CropWidget::advancedSettingsEnabled() const { return d->advancedCheckBox->isChecked(); } void CropWidget::setPreserveAspectRatio(bool preserve) { d->preserveAspectRatioCheckBox->setChecked(preserve); } bool CropWidget::preserveAspectRatio() const { return d->preserveAspectRatioCheckBox->isChecked(); } void CropWidget::setCropRatio(QSizeF size) { d->setChosenRatio(size); } QSizeF CropWidget::cropRatio() const { return d->chosenRatio(); } void CropWidget::setCropRatioIndex(int index) { d->ratioComboBox->setCurrentIndex(index); } int CropWidget::cropRatioIndex() const { return d->mCropRatioComboBoxCurrentIndex; } void CropWidget::setCropRect(const QRect& rect) { d->mUpdatingFromCropTool = true; d->leftSpinBox->setValue(rect.left()); d->topSpinBox->setValue(rect.top()); d->widthSpinBox->setValue(rect.width()); d->heightSpinBox->setValue(rect.height()); d->mUpdatingFromCropTool = false; } void CropWidget::slotPositionChanged() { const QSize size = d->mDocument->size(); d->widthSpinBox->setMaximum(size.width() - d->leftSpinBox->value()); d->heightSpinBox->setMaximum(size.height() - d->topSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } d->mCropTool->setRect(d->cropRect()); } void CropWidget::slotWidthChanged() { d->leftSpinBox->setMaximum(d->mDocument->width() - d->widthSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } if (d->ratioIsConstrained()) { int height = int(d->widthSpinBox->value() * d->cropRatio()); d->heightSpinBox->setValue(height); } d->mCropTool->setRect(d->cropRect()); } void CropWidget::slotHeightChanged() { d->topSpinBox->setMaximum(d->mDocument->height() - d->heightSpinBox->value()); if (d->mUpdatingFromCropTool) { return; } if (d->ratioIsConstrained()) { int width = int(d->heightSpinBox->value() / d->cropRatio()); d->widthSpinBox->setValue(width); } d->mCropTool->setRect(d->cropRect()); } void CropWidget::applyRatioConstraint() { double ratio = d->cropRatio(); d->mCropTool->setCropRatio(ratio); if (!d->ratioIsConstrained()) { return; } QRect rect = d->cropRect(); rect.setHeight(int(rect.width() * ratio)); d->mCropTool->setRect(rect); } void CropWidget::slotAdvancedCheckBoxToggled(bool checked) { for (auto w : d->mAdvancedWidgets) { w->setVisible(checked); } d->mPreserveAspectRatioWidget->setVisible(!checked); applyRatioConstraint(); } void CropWidget::slotRatioComboBoxChanged() { const QString text = d->ratioComboBox->currentText(); // If text cleared, clear the current item as well if (text.isEmpty()) { d->ratioComboBox->setCurrentIndex(-1); } // We want to keep track of the selected ratio, including when the user has entered a custom ratio // or cleared the text. We can't simply use currentIndex() because this stays >= 0 when the user manually // enters text. We also can't set the current index to -1 when there is no match like above because that // interferes when manually entering text. // Furthermore, since there can be duplicate text items, we can't rely on findText() as it will stop on // the first match it finds. Therefore we must check if there's a match, and if so, get the index directly. if (d->ratioComboBox->findText(text) >= 0) { d->mCropRatioComboBoxCurrentIndex = d->ratioComboBox->currentIndex(); } else { d->mCropRatioComboBoxCurrentIndex = -1; } applyRatioConstraint(); } void CropWidget::updateCropRatio() { // First we need to re-calculate the "Current Image" ratio in case the user rotated the image d->ratioComboBox->setItemData(d->mCurrentImageComboBoxIndex, QVariant(ratio(d->mDocument->size()))); // Always re-apply the constraint, even though we only need to when the user has "Current Image" // selected or the "Preserve aspect ratio" checked, since there's no harm applyRatioConstraint(); // If the ratio is unrestricted, calling applyRatioConstraint doesn't update the rect, so we call // this manually to make sure the rect is adjusted to fit within the image d->mCropTool->setRect(d->mCropTool->rect()); } } // namespace diff --git a/lib/datewidget.cpp b/lib/datewidget.cpp index 20ae455c..007fcff5 100644 --- a/lib/datewidget.cpp +++ b/lib/datewidget.cpp @@ -1,145 +1,145 @@ // 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 "datewidget.h" // Qt #include #include #include // KDE #include #include // Local #include namespace Gwenview { struct DateWidgetPrivate { DateWidget* q; QDate mDate; KDatePicker* mDatePicker; StatusBarToolButton* mPreviousButton; StatusBarToolButton* mDateButton; StatusBarToolButton* mNextButton; void setupDatePicker() { mDatePicker = new KDatePicker; /* Use Qt::Tool instead of Qt::Window so that the bubble does not appear in the task bar */ //mDatePicker->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint); mDatePicker->setWindowFlags(Qt::Popup); mDatePicker->hide(); mDatePicker->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); QObject::connect(mDatePicker, &KDatePicker::dateEntered, q, &DateWidget::slotDatePickerModified); QObject::connect(mDatePicker, &KDatePicker::dateSelected, q, &DateWidget::slotDatePickerModified); } void updateButton() { mDateButton->setText(QLocale().toString(mDate, QLocale::ShortFormat)); } void adjustDate(int delta) { mDate = mDate.addDays(delta); updateButton(); emit q->dateChanged(mDate); } }; DateWidget::DateWidget(QWidget* parent) : QWidget(parent) , d(new DateWidgetPrivate) { d->q = this; d->setupDatePicker(); d->mPreviousButton = new StatusBarToolButton; d->mPreviousButton->setGroupPosition(StatusBarToolButton::GroupLeft); // FIXME: RTL d->mPreviousButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); connect(d->mPreviousButton, &StatusBarToolButton::clicked, this, &DateWidget::goToPrevious); d->mDateButton = new StatusBarToolButton; d->mDateButton->setGroupPosition(StatusBarToolButton::GroupCenter); connect(d->mDateButton, &StatusBarToolButton::clicked, this, &DateWidget::showDatePicker); d->mNextButton = new StatusBarToolButton; d->mNextButton->setGroupPosition(StatusBarToolButton::GroupRight); d->mNextButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); connect(d->mNextButton, &StatusBarToolButton::clicked, this, &DateWidget::goToNext); QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(d->mPreviousButton); layout->addWidget(d->mDateButton); layout->addWidget(d->mNextButton); } DateWidget::~DateWidget() { delete d->mDatePicker; delete d; } QDate DateWidget::date() const { return d->mDate; } void DateWidget::showDatePicker() { d->mDatePicker->setDate(d->mDate); d->mDatePicker->adjustSize(); const QPoint pos = mapToGlobal(QPoint(0, -d->mDatePicker->height())); d->mDatePicker->move(pos); d->mDatePicker->show(); } void DateWidget::slotDatePickerModified(const QDate& date) { d->mDatePicker->hide(); d->mDate = date; emit dateChanged(date); d->updateButton(); } void DateWidget::goToPrevious() { d->adjustDate(-1); } void DateWidget::goToNext() { d->adjustDate(1); } } // namespace diff --git a/lib/document/documentloadedimpl.cpp b/lib/document/documentloadedimpl.cpp index 8cd98259..6e0026a2 100644 --- a/lib/document/documentloadedimpl.cpp +++ b/lib/document/documentloadedimpl.cpp @@ -1,123 +1,123 @@ // 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 "documentloadedimpl.h" // Qt #include #include #include -#include +#include #include #include // KDE // Local #include "documentjob.h" #include "imageutils.h" #include "savejob.h" namespace Gwenview { struct DocumentLoadedImplPrivate { QByteArray mRawData; bool mQuietInit; }; DocumentLoadedImpl::DocumentLoadedImpl(Document* document, const QByteArray& rawData, bool quietInit) : AbstractDocumentImpl(document) , d(new DocumentLoadedImplPrivate) { if (document->keepRawData()) { d->mRawData = rawData; } d->mQuietInit = quietInit; } DocumentLoadedImpl::~DocumentLoadedImpl() { delete d; } void DocumentLoadedImpl::init() { if (!d->mQuietInit) { emit imageRectUpdated(document()->image().rect()); emit loaded(); } } bool DocumentLoadedImpl::isEditable() const { return true; } Document::LoadingState DocumentLoadedImpl::loadingState() const { return Document::Loaded; } bool DocumentLoadedImpl::saveInternal(QIODevice* device, const QByteArray& format) { QImageWriter writer(device, format); bool ok = writer.write(document()->image()); if (ok) { setDocumentFormat(format); } else { setDocumentErrorString(writer.errorString()); } return ok; } DocumentJob* DocumentLoadedImpl::save(const QUrl &url, const QByteArray& format) { return new SaveJob(this, url, format); } AbstractDocumentEditor* DocumentLoadedImpl::editor() { return this; } void DocumentLoadedImpl::setImage(const QImage& image) { setDocumentImage(image); emit imageRectUpdated(image.rect()); } void DocumentLoadedImpl::applyTransformation(Orientation orientation) { QImage image = document()->image(); - QMatrix matrix = ImageUtils::transformMatrix(orientation); + QTransform matrix = ImageUtils::transformMatrix(orientation); image = image.transformed(matrix); setDocumentImage(image); emit imageRectUpdated(image.rect()); } QByteArray DocumentLoadedImpl::rawData() const { return d->mRawData; } } // namespace diff --git a/lib/imageutils.cpp b/lib/imageutils.cpp index de591840..0341d58d 100644 --- a/lib/imageutils.cpp +++ b/lib/imageutils.cpp @@ -1,74 +1,74 @@ // 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. */ #include "imageutils.h" // Qt -#include +#include namespace Gwenview { namespace ImageUtils { -QMatrix transformMatrix(Orientation orientation) +QTransform transformMatrix(Orientation orientation) { - QMatrix matrix; + QTransform matrix; switch (orientation) { case NOT_AVAILABLE: case NORMAL: break; case HFLIP: matrix.scale(-1, 1); break; case ROT_180: matrix.rotate(180); break; case VFLIP: matrix.scale(1, -1); break; case TRANSPOSE: matrix.scale(-1, 1); matrix.rotate(90); break; case ROT_90: matrix.rotate(90); break; case TRANSVERSE: matrix.scale(1, -1); matrix.rotate(90); break; case ROT_270: matrix.rotate(270); break; } return matrix; } } // namespace } // namespace diff --git a/lib/imageutils.h b/lib/imageutils.h index bdccdc82..cf9bb32d 100644 --- a/lib/imageutils.h +++ b/lib/imageutils.h @@ -1,39 +1,39 @@ // 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. */ #ifndef IMAGEUTILS_H #define IMAGEUTILS_H #include #include -class QMatrix; +class QTransform; namespace Gwenview { namespace ImageUtils { -GWENVIEWLIB_EXPORT QMatrix transformMatrix(Orientation); +GWENVIEWLIB_EXPORT QTransform transformMatrix(Orientation); } // namespace } // namespace #endif /* IMAGEUTILS_H */ diff --git a/lib/jpegcontent.cpp b/lib/jpegcontent.cpp index 37121d3e..e3a9170f 100644 --- a/lib/jpegcontent.cpp +++ b/lib/jpegcontent.cpp @@ -1,644 +1,644 @@ // 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. */ #include "jpegcontent.h" // System #include #include #include #include extern "C" { #include #include "transupp.h" } // Qt #include #include #include #include -#include +#include #include // KDE #include // Exiv2 #include // Local #include "jpegerrormanager.h" #include "iodevicejpegsourcemanager.h" #include "exiv2imageloader.h" #include "gwenviewconfig.h" namespace Gwenview { const int INMEM_DST_DELTA = 4096; //----------------------------------------------- // // In-memory data destination manager for libjpeg // //----------------------------------------------- struct inmem_dest_mgr : public jpeg_destination_mgr { QByteArray* mOutput; void dump() { qDebug() << "dest_mgr:\n"; qDebug() << "- next_output_byte: " << next_output_byte; qDebug() << "- free_in_buffer: " << free_in_buffer; qDebug() << "- output size: " << mOutput->size(); } }; void inmem_init_destination(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); if (dest->mOutput->size() == 0) { dest->mOutput->resize(INMEM_DST_DELTA); } dest->free_in_buffer = dest->mOutput->size(); dest->next_output_byte = (JOCTET*)(dest->mOutput->data()); } boolean inmem_empty_output_buffer(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); dest->mOutput->resize(dest->mOutput->size() + INMEM_DST_DELTA); dest->next_output_byte = (JOCTET*)(dest->mOutput->data() + dest->mOutput->size() - INMEM_DST_DELTA); dest->free_in_buffer = INMEM_DST_DELTA; return true; } void inmem_term_destination(j_compress_ptr cinfo) { inmem_dest_mgr* dest = (inmem_dest_mgr*)(cinfo->dest); int finalSize = dest->next_output_byte - (JOCTET*)(dest->mOutput->data()); Q_ASSERT(finalSize >= 0); dest->mOutput->resize(finalSize); } //--------------------- // // JpegContent::Private // //--------------------- struct JpegContent::Private { // JpegContent usually stores the image pixels as compressed JPEG data in // mRawData. However if the image is set with setImage() because the user // performed a lossy image manipulation, mRawData is cleared and the image // pixels are kept in mImage until updateRawDataFromImage() is called. QImage mImage; QByteArray mRawData; QSize mSize; QString mComment; bool mPendingTransformation; - QMatrix mTransformMatrix; + QTransform mTransformMatrix; Exiv2::ExifData mExifData; QString mErrorString; Private() { mPendingTransformation = false; } void setupInmemDestination(j_compress_ptr cinfo, QByteArray* outputData) { Q_ASSERT(!cinfo->dest); inmem_dest_mgr* dest = (inmem_dest_mgr*) (*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(inmem_dest_mgr)); cinfo->dest = (struct jpeg_destination_mgr*)(dest); dest->init_destination = inmem_init_destination; dest->empty_output_buffer = inmem_empty_output_buffer; dest->term_destination = inmem_term_destination; dest->mOutput = outputData; } bool readSize() { struct jpeg_decompress_struct srcinfo; // Init JPEG structs JPEGErrorManager errorManager; // Initialize the JPEG decompression object srcinfo.err = &errorManager; jpeg_create_decompress(&srcinfo); if (setjmp(errorManager.jmp_buffer)) { qCritical() << "libjpeg fatal error\n"; return false; } // Specify data source for decompression QBuffer buffer(&mRawData); buffer.open(QIODevice::ReadOnly); IODeviceJpegSourceManager::setup(&srcinfo, &buffer); // Read the header jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); int result = jpeg_read_header(&srcinfo, true); if (result != JPEG_HEADER_OK) { qCritical() << "Could not read jpeg header\n"; jpeg_destroy_decompress(&srcinfo); return false; } mSize = QSize(srcinfo.image_width, srcinfo.image_height); jpeg_destroy_decompress(&srcinfo); return true; } bool updateRawDataFromImage() { QBuffer buffer; QImageWriter writer(&buffer, "jpeg"); if (!writer.write(mImage)) { mErrorString = writer.errorString(); return false; } mRawData = buffer.data(); mImage = QImage(); return true; } }; //------------ // // JpegContent // //------------ JpegContent::JpegContent() { d = new JpegContent::Private(); } JpegContent::~JpegContent() { delete d; } bool JpegContent::load(const QString& path) { QFile file(path); if (!file.open(QIODevice::ReadOnly)) { qCritical() << "Could not open '" << path << "' for reading\n"; return false; } return loadFromData(file.readAll()); } bool JpegContent::loadFromData(const QByteArray& data) { std::unique_ptr image; Exiv2ImageLoader loader; if (!loader.load(data)) { qCritical() << "Could not load image with Exiv2, reported error:" << loader.errorMessage(); } image.reset(loader.popImage().release()); return loadFromData(data, image.get()); } bool JpegContent::loadFromData(const QByteArray& data, Exiv2::Image* exiv2Image) { d->mPendingTransformation = false; d->mTransformMatrix.reset(); d->mRawData = data; if (d->mRawData.size() == 0) { qCritical() << "No data\n"; return false; } if (!d->readSize()) return false; d->mExifData = exiv2Image->exifData(); d->mComment = QString::fromUtf8(exiv2Image->comment().c_str()); if (!GwenviewConfig::applyExifOrientation()) { return true; } // Adjust the size according to the orientation switch (orientation()) { case TRANSPOSE: case ROT_90: case TRANSVERSE: case ROT_270: d->mSize.transpose(); break; default: break; } return true; } QByteArray JpegContent::rawData() const { return d->mRawData; } Orientation JpegContent::orientation() const { Exiv2::ExifKey key("Exif.Image.Orientation"); Exiv2::ExifData::iterator it = d->mExifData.findKey(key); // We do the same checks as in libexiv2's src/crwimage.cpp: // http://dev.exiv2.org/projects/exiv2/repository/entry/trunk/src/crwimage.cpp?rev=2681#L1336 if (it == d->mExifData.end() || it->count() == 0 || it->typeId() != Exiv2::unsignedShort) { return NOT_AVAILABLE; } return Orientation(it->toLong()); } int JpegContent::dotsPerMeterX() const { return dotsPerMeter(QStringLiteral("XResolution")); } int JpegContent::dotsPerMeterY() const { return dotsPerMeter(QStringLiteral("YResolution")); } int JpegContent::dotsPerMeter(const QString& keyName) const { Exiv2::ExifKey keyResUnit("Exif.Image.ResolutionUnit"); Exiv2::ExifData::iterator it = d->mExifData.findKey(keyResUnit); if (it == d->mExifData.end()) { return 0; } int res = it->toLong(); QString keyVal = QStringLiteral("Exif.Image.") + keyName; Exiv2::ExifKey keyResolution(keyVal.toLocal8Bit().data()); it = d->mExifData.findKey(keyResolution); if (it == d->mExifData.end()) { return 0; } // The unit for measuring XResolution and YResolution. The same unit is used for both XResolution and YResolution. // If the image resolution in unknown, 2 (inches) is designated. // Default = 2 // 2 = inches // 3 = centimeters // Other = reserved const float INCHESPERMETER = (100. / 2.54); switch (res) { case 3: // dots per cm return int(it->toLong() * 100); default: // dots per inch return int(it->toLong() * INCHESPERMETER); } return 0; } void JpegContent::resetOrientation() { Exiv2::ExifKey key("Exif.Image.Orientation"); Exiv2::ExifData::iterator it = d->mExifData.findKey(key); if (it == d->mExifData.end()) { return; } *it = uint16_t(NORMAL); } QSize JpegContent::size() const { return d->mSize; } QString JpegContent::comment() const { return d->mComment; } void JpegContent::setComment(const QString& comment) { d->mComment = comment; } -static QMatrix createRotMatrix(int angle) +static QTransform createRotMatrix(int angle) { - QMatrix matrix; + QTransform matrix; matrix.rotate(angle); return matrix; } -static QMatrix createScaleMatrix(int dx, int dy) +static QTransform createScaleMatrix(int dx, int dy) { - QMatrix matrix; + QTransform matrix; matrix.scale(dx, dy); return matrix; } struct OrientationInfo { OrientationInfo() : orientation(NOT_AVAILABLE) , jxform(JXFORM_NONE) {} - OrientationInfo(Orientation o, const QMatrix &m, JXFORM_CODE j) + OrientationInfo(Orientation o, const QTransform &m, JXFORM_CODE j) : orientation(o), matrix(m), jxform(j) {} Orientation orientation; - QMatrix matrix; + QTransform matrix; JXFORM_CODE jxform; }; typedef QList OrientationInfoList; static const OrientationInfoList& orientationInfoList() { static OrientationInfoList list; if (list.size() == 0) { - QMatrix rot90 = createRotMatrix(90); - QMatrix hflip = createScaleMatrix(-1, 1); - QMatrix vflip = createScaleMatrix(1, -1); + QTransform rot90 = createRotMatrix(90); + QTransform hflip = createScaleMatrix(-1, 1); + QTransform vflip = createScaleMatrix(1, -1); list << OrientationInfo() - << OrientationInfo(NORMAL, QMatrix(), JXFORM_NONE) + << OrientationInfo(NORMAL, QTransform(), JXFORM_NONE) << OrientationInfo(HFLIP, hflip, JXFORM_FLIP_H) << OrientationInfo(ROT_180, createRotMatrix(180), JXFORM_ROT_180) << OrientationInfo(VFLIP, vflip, JXFORM_FLIP_V) << OrientationInfo(TRANSPOSE, hflip * rot90, JXFORM_TRANSPOSE) << OrientationInfo(ROT_90, rot90, JXFORM_ROT_90) << OrientationInfo(TRANSVERSE, vflip * rot90, JXFORM_TRANSVERSE) << OrientationInfo(ROT_270, createRotMatrix(270), JXFORM_ROT_270) ; } return list; } void JpegContent::transform(Orientation orientation) { if (orientation != NOT_AVAILABLE && orientation != NORMAL) { d->mPendingTransformation = true; OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); for (; it != end; ++it) { if ((*it).orientation == orientation) { d->mTransformMatrix = (*it).matrix * d->mTransformMatrix; break; } } if (it == end) { qWarning() << "Could not find matrix for orientation\n"; } } } #if 0 -static void dumpMatrix(const QMatrix& matrix) +static void dumpMatrix(const QTransform& matrix) { qDebug() << "matrix | " << matrix.m11() << ", " << matrix.m12() << " |\n"; qDebug() << " | " << matrix.m21() << ", " << matrix.m22() << " |\n"; qDebug() << " ( " << matrix.dx() << ", " << matrix.dy() << " )\n"; } #endif -static bool matricesAreSame(const QMatrix& m1, const QMatrix& m2, double tolerance) +static bool matricesAreSame(const QTransform& m1, const QTransform& m2, double tolerance) { return fabs(m1.m11() - m2.m11()) < tolerance && fabs(m1.m12() - m2.m12()) < tolerance && fabs(m1.m21() - m2.m21()) < tolerance && fabs(m1.m22() - m2.m22()) < tolerance && fabs(m1.dx() - m2.dx()) < tolerance && fabs(m1.dy() - m2.dy()) < tolerance; } -static JXFORM_CODE findJxform(const QMatrix& matrix) +static JXFORM_CODE findJxform(const QTransform& matrix) { OrientationInfoList::ConstIterator it(orientationInfoList().begin()), end(orientationInfoList().end()); for (; it != end; ++it) { if (matricesAreSame((*it).matrix, matrix, 0.001)) { return (*it).jxform; } } qWarning() << "findJxform: failed\n"; return JXFORM_NONE; } void JpegContent::applyPendingTransformation() { if (d->mRawData.size() == 0) { qCritical() << "No data loaded\n"; return; } // The following code is inspired by jpegtran.c from the libjpeg // Init JPEG structs struct jpeg_decompress_struct srcinfo; struct jpeg_compress_struct dstinfo; jvirt_barray_ptr * src_coef_arrays; jvirt_barray_ptr * dst_coef_arrays; // Initialize the JPEG decompression object JPEGErrorManager srcErrorManager; srcinfo.err = &srcErrorManager; jpeg_create_decompress(&srcinfo); if (setjmp(srcErrorManager.jmp_buffer)) { qCritical() << "libjpeg error in src\n"; return; } // Initialize the JPEG compression object JPEGErrorManager dstErrorManager; dstinfo.err = &dstErrorManager; jpeg_create_compress(&dstinfo); if (setjmp(dstErrorManager.jmp_buffer)) { qCritical() << "libjpeg error in dst\n"; return; } // Specify data source for decompression QBuffer buffer(&d->mRawData); buffer.open(QIODevice::ReadOnly); IODeviceJpegSourceManager::setup(&srcinfo, &buffer); // Enable saving of extra markers that we want to copy jcopy_markers_setup(&srcinfo, JCOPYOPT_ALL); (void) jpeg_read_header(&srcinfo, true); // Init transformation jpeg_transform_info transformoption; memset(&transformoption, 0, sizeof(jpeg_transform_info)); transformoption.transform = findJxform(d->mTransformMatrix); jtransform_request_workspace(&srcinfo, &transformoption); /* Read source file as DCT coefficients */ src_coef_arrays = jpeg_read_coefficients(&srcinfo); /* Initialize destination compression parameters from source values */ jpeg_copy_critical_parameters(&srcinfo, &dstinfo); /* Adjust destination parameters if required by transform options; * also find out which set of coefficient arrays will hold the output. */ dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); /* Specify data destination for compression */ QByteArray output; output.resize(d->mRawData.size()); d->setupInmemDestination(&dstinfo, &output); /* Start compressor (note no image data is actually written here) */ jpeg_write_coefficients(&dstinfo, dst_coef_arrays); /* Copy to the output file any extra markers that we want to preserve */ jcopy_markers_execute(&srcinfo, &dstinfo, JCOPYOPT_ALL); /* Execute image transformation, if any */ jtransform_execute_transformation(&srcinfo, &dstinfo, src_coef_arrays, &transformoption); /* Finish compression and release memory */ jpeg_finish_compress(&dstinfo); jpeg_destroy_compress(&dstinfo); (void) jpeg_finish_decompress(&srcinfo); jpeg_destroy_decompress(&srcinfo); // Set rawData to our new JPEG d->mRawData = output; } QImage JpegContent::thumbnail() const { QImage image; if (!d->mExifData.empty()) { #if(EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumbC thumb(d->mExifData); Exiv2::DataBuf thumbnail = thumb.copy(); #else Exiv2::DataBuf thumbnail = d->mExifData.copyThumbnail(); #endif image.loadFromData(thumbnail.pData_, thumbnail.size_); } return image; } void JpegContent::setThumbnail(const QImage& thumbnail) { if (d->mExifData.empty()) { return; } QByteArray array; QBuffer buffer(&array); buffer.open(QIODevice::WriteOnly); QImageWriter writer(&buffer, "JPEG"); if (!writer.write(thumbnail)) { qCritical() << "Could not write thumbnail\n"; return; } #if (EXIV2_TEST_VERSION(0,17,91)) Exiv2::ExifThumb thumb(d->mExifData); thumb.setJpegThumbnail((unsigned char*)array.data(), array.size()); #else d->mExifData.setJpegThumbnail((unsigned char*)array.data(), array.size()); #endif } bool JpegContent::save(const QString& path) { QFile file(path); if (!file.open(QIODevice::WriteOnly)) { d->mErrorString = i18nc("@info", "Could not open file for writing."); return false; } return save(&file); } bool JpegContent::save(QIODevice* device) { if (!d->mImage.isNull()) { if (!d->updateRawDataFromImage()) { return false; } } if (d->mRawData.size() == 0) { d->mErrorString = i18nc("@info", "No data to store."); return false; } if (d->mPendingTransformation) { applyPendingTransformation(); d->mPendingTransformation = false; } std::unique_ptr image; image.reset(Exiv2::ImageFactory::open((unsigned char*)d->mRawData.data(), d->mRawData.size()).release()); // Store Exif info image->setExifData(d->mExifData); image->setComment(d->mComment.toUtf8().toStdString()); image->writeMetadata(); // Update mRawData Exiv2::BasicIo& io = image->io(); d->mRawData.resize(io.size()); io.read((unsigned char*)d->mRawData.data(), io.size()); QDataStream stream(device); stream.writeRawData(d->mRawData.data(), d->mRawData.size()); // Make sure we are up to date loadFromData(d->mRawData); return true; } QString JpegContent::errorString() const { return d->mErrorString; } void JpegContent::setImage(const QImage& image) { d->mRawData.clear(); d->mImage = image; d->mSize = image.size(); d->mExifData["Exif.Photo.PixelXDimension"] = image.width(); d->mExifData["Exif.Photo.PixelYDimension"] = image.height(); resetOrientation(); d->mPendingTransformation = false; - d->mTransformMatrix = QMatrix(); + d->mTransformMatrix = QTransform(); } } // namespace diff --git a/lib/print/printoptionspage.cpp b/lib/print/printoptionspage.cpp index 6c7c04fd..79e03738 100644 --- a/lib/print/printoptionspage.cpp +++ b/lib/print/printoptionspage.cpp @@ -1,232 +1,232 @@ // 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 "printoptionspage.h" // Qt #include #include #include #include // KDE #include // Local #include #include #include namespace Gwenview { static inline double unitToInches(PrintOptionsPage::Unit unit) { if (unit == PrintOptionsPage::Inches) { return 1.; } else if (unit == PrintOptionsPage::Centimeters) { return 1 / 2.54; } else { // Millimeters return 1 / 25.4; } } struct PrintOptionsPagePrivate : public Ui_PrintOptionsPage { QSize mImageSize; QButtonGroup mScaleGroup; QButtonGroup mPositionGroup; KConfigDialogManager* mConfigDialogManager; void initPositionFrame() { mPositionFrame->setStyleSheet( QStringLiteral("QFrame {" " background-color: palette(mid);" " border: 1px solid palette(dark);" "}" "QToolButton {" " border: none;" " background: palette(base);" "}" "QToolButton:hover {" " background: palette(alternate-base);" " border: 1px solid palette(highlight);" "}" "QToolButton:checked {" " background-color: palette(highlight);" "}") ); QGridLayout* layout = new QGridLayout(mPositionFrame); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(1); for (int row = 0; row < 3; ++row) { for (int col = 0; col < 3; ++col) { QToolButton* button = new QToolButton(mPositionFrame); button->setFixedSize(40, 40); button->setCheckable(true); layout->addWidget(button, row, col); Qt::Alignment alignment; if (row == 0) { alignment = Qt::AlignTop; } else if (row == 1) { alignment = Qt::AlignVCenter; } else { alignment = Qt::AlignBottom; } if (col == 0) { alignment |= Qt::AlignLeft; } else if (col == 1) { alignment |= Qt::AlignHCenter; } else { alignment |= Qt::AlignRight; } mPositionGroup.addButton(button, int(alignment)); } } } }; PrintOptionsPage::PrintOptionsPage(const QSize& imageSize) : QWidget() , d(new PrintOptionsPagePrivate) { d->setupUi(this); d->mImageSize = imageSize; d->mConfigDialogManager = new KConfigDialogManager(this, GwenviewConfig::self()); d->initPositionFrame(); d->mScaleGroup.addButton(d->mNoScale, NoScale); d->mScaleGroup.addButton(d->mScaleToPage, ScaleToPage); d->mScaleGroup.addButton(d->mScaleTo, ScaleToCustomSize); connect(d->kcfg_PrintWidth, SIGNAL(valueChanged(double)), SLOT(adjustHeightToRatio())); connect(d->kcfg_PrintHeight, SIGNAL(valueChanged(double)), SLOT(adjustWidthToRatio())); connect(d->kcfg_PrintKeepRatio, &QAbstractButton::toggled, this, &PrintOptionsPage::adjustHeightToRatio); } PrintOptionsPage::~PrintOptionsPage() { delete d; } Qt::Alignment PrintOptionsPage::alignment() const { int id = d->mPositionGroup.checkedId(); qWarning() << "alignment=" << id; return Qt::Alignment(id); } PrintOptionsPage::ScaleMode PrintOptionsPage::scaleMode() const { return PrintOptionsPage::ScaleMode(d->mScaleGroup.checkedId()); } bool PrintOptionsPage::enlargeSmallerImages() const { return d->kcfg_PrintEnlargeSmallerImages->isChecked(); } PrintOptionsPage::Unit PrintOptionsPage::scaleUnit() const { return PrintOptionsPage::Unit(d->kcfg_PrintUnit->currentIndex()); } double PrintOptionsPage::scaleWidth() const { return d->kcfg_PrintWidth->value() * unitToInches(scaleUnit()); } double PrintOptionsPage::scaleHeight() const { return d->kcfg_PrintHeight->value() * unitToInches(scaleUnit()); } void PrintOptionsPage::adjustWidthToRatio() { if (!d->kcfg_PrintKeepRatio->isChecked()) { return; } double width = d->mImageSize.width() * d->kcfg_PrintHeight->value() / d->mImageSize.height(); SignalBlocker blocker(d->kcfg_PrintWidth); d->kcfg_PrintWidth->setValue(width ? width : 1.); } void PrintOptionsPage::adjustHeightToRatio() { if (!d->kcfg_PrintKeepRatio->isChecked()) { return; } double height = d->mImageSize.height() * d->kcfg_PrintWidth->value() / d->mImageSize.width(); SignalBlocker blocker(d->kcfg_PrintHeight); d->kcfg_PrintHeight->setValue(height ? height : 1.); } void PrintOptionsPage::loadConfig() { QAbstractButton* button; button = d->mPositionGroup.button(GwenviewConfig::printPosition()); if (button) { button->setChecked(true); } else { qWarning() << "Unknown button for position group"; } button = d->mScaleGroup.button(GwenviewConfig::printScaleMode()); if (button) { button->setChecked(true); } else { qWarning() << "Unknown button for scale group"; } d->mConfigDialogManager->updateWidgets(); if (d->kcfg_PrintKeepRatio->isChecked()) { adjustHeightToRatio(); } } void PrintOptionsPage::saveConfig() { int position = d->mPositionGroup.checkedId(); GwenviewConfig::setPrintPosition(position); ScaleMode scaleMode = ScaleMode(d->mScaleGroup.checkedId()); GwenviewConfig::setPrintScaleMode(scaleMode); d->mConfigDialogManager->updateSettings(); GwenviewConfig::self()->save(); } } // namespace diff --git a/lib/resize/resizeimagedialog.cpp b/lib/resize/resizeimagedialog.cpp index ffad7bc1..d40bfe8f 100644 --- a/lib/resize/resizeimagedialog.cpp +++ b/lib/resize/resizeimagedialog.cpp @@ -1,187 +1,187 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2010 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 "resizeimagedialog.h" // Qt #include #include // KDE #include #include // Local #include namespace Gwenview { struct ResizeImageDialogPrivate : public Ui_ResizeImageWidget { bool mUpdateFromRatio; bool mUpdateFromSizeOrPercentage; QSize mOriginalSize; }; ResizeImageDialog::ResizeImageDialog(QWidget* parent) : QDialog(parent) , d(new ResizeImageDialogPrivate) { d->mUpdateFromRatio = false; d->mUpdateFromSizeOrPercentage = false; QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->setSizeConstraint(QLayout::SetFixedSize); QWidget* content = new QWidget(this); d->setupUi(content); mainLayout->addWidget(content); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ResizeImageDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ResizeImageDialog::reject); mainLayout->addWidget(buttonBox); - content->layout()->setMargin(0); + content->layout()->setContentsMargins(0, 0, 0, 0); KGuiItem::assign(okButton, KGuiItem(i18n("Resize"), QStringLiteral("transform-scale"))); setWindowTitle(content->windowTitle()); d->mWidthSpinBox->setFocus(); connect(d->mWidthSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &ResizeImageDialog::slotWidthChanged); connect(d->mHeightSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &ResizeImageDialog::slotHeightChanged); connect(d->mWidthPercentSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &ResizeImageDialog::slotWidthPercentChanged); connect(d->mHeightPercentSpinBox, QOverload::of(&QDoubleSpinBox::valueChanged), this, &ResizeImageDialog::slotHeightPercentChanged); connect(d->mKeepAspectCheckBox, &QCheckBox::toggled, this, &ResizeImageDialog::slotKeepAspectChanged); } ResizeImageDialog::~ResizeImageDialog() { delete d; } void ResizeImageDialog::setOriginalSize(const QSize& size) { d->mOriginalSize = size; d->mOriginalWidthLabel->setText(QString::number(size.width()) + QStringLiteral(" px")); d->mOriginalHeightLabel->setText(QString::number(size.height()) + QStringLiteral(" px")); d->mWidthSpinBox->setValue(size.width()); d->mHeightSpinBox->setValue(size.height()); } QSize ResizeImageDialog::size() const { return QSize( d->mWidthSpinBox->value(), d->mHeightSpinBox->value() ); } void ResizeImageDialog::slotWidthChanged(int width) { // Update width percentage to match width, only if this was a manual adjustment if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) { d->mUpdateFromSizeOrPercentage = true; d->mWidthPercentSpinBox->setValue((double(width) / d->mOriginalSize.width()) * 100); d->mUpdateFromSizeOrPercentage = false; } if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) { return; } // Update height to keep ratio, only if ratio locked and this was a manual adjustment d->mUpdateFromRatio = true; d->mHeightSpinBox->setValue(d->mOriginalSize.height() * width / d->mOriginalSize.width()); d->mUpdateFromRatio = false; } void ResizeImageDialog::slotHeightChanged(int height) { // Update height percentage to match height, only if this was a manual adjustment if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) { d->mUpdateFromSizeOrPercentage = true; d->mHeightPercentSpinBox->setValue((double(height) / d->mOriginalSize.height()) * 100); d->mUpdateFromSizeOrPercentage = false; } if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) { return; } // Update width to keep ratio, only if ratio locked and this was a manual adjustment d->mUpdateFromRatio = true; d->mWidthSpinBox->setValue(d->mOriginalSize.width() * height / d->mOriginalSize.height()); d->mUpdateFromRatio = false; } void ResizeImageDialog::slotWidthPercentChanged(double widthPercent) { // Update width to match width percentage, only if this was a manual adjustment if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) { d->mUpdateFromSizeOrPercentage = true; d->mWidthSpinBox->setValue((widthPercent / 100) * d->mOriginalSize.width()); d->mUpdateFromSizeOrPercentage = false; } if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) { return; } // Keep height percentage in sync with width percentage, only if ratio locked and this was a manual adjustment d->mUpdateFromRatio = true; d->mHeightPercentSpinBox->setValue(d->mWidthPercentSpinBox->value()); d->mUpdateFromRatio = false; } void ResizeImageDialog::slotHeightPercentChanged(double heightPercent) { // Update height to match height percentage, only if this was a manual adjustment if (!d->mUpdateFromSizeOrPercentage && !d->mUpdateFromRatio) { d->mUpdateFromSizeOrPercentage = true; d->mHeightSpinBox->setValue((heightPercent / 100) * d->mOriginalSize.height()); d->mUpdateFromSizeOrPercentage = false; } if (!d->mKeepAspectCheckBox->isChecked() || d->mUpdateFromRatio) { return; } // Keep height percentage in sync with width percentage, only if ratio locked and this was a manual adjustment d->mUpdateFromRatio = true; d->mWidthPercentSpinBox->setValue(d->mHeightPercentSpinBox->value()); d->mUpdateFromRatio = false; } void ResizeImageDialog::slotKeepAspectChanged(bool value) { if (value) { d->mUpdateFromSizeOrPercentage = true; slotWidthChanged(d->mWidthSpinBox->value()); slotWidthPercentChanged(d->mWidthPercentSpinBox->value()); d->mUpdateFromSizeOrPercentage = false; } } } // namespace diff --git a/lib/semanticinfo/tagwidget.cpp b/lib/semanticinfo/tagwidget.cpp index 552ccfa4..51c6b7ed 100644 --- a/lib/semanticinfo/tagwidget.cpp +++ b/lib/semanticinfo/tagwidget.cpp @@ -1,257 +1,257 @@ // 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 "tagwidget.h" // Qt #include #include #include #include #include #include #include #include #include #include #include // KDE #include // Local #include #include namespace Gwenview { class TagCompleterModel : public QSortFilterProxyModel { public: TagCompleterModel(QObject* parent) : QSortFilterProxyModel(parent) { } void setTagInfo(const TagInfo& tagInfo) { mExcludedTagSet.clear(); TagInfo::ConstIterator it = tagInfo.begin(), end = tagInfo.end(); for (; it != end; ++it) { if (it.value()) { mExcludedTagSet << it.key(); } } invalidate(); } void setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd* backEnd) { setSourceModel(TagModel::createAllTagsModel(this, backEnd)); } protected: bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override { QModelIndex sourceIndex = sourceModel()->index(sourceRow, 0, sourceParent); SemanticInfoTag tag = sourceIndex.data(TagModel::TagRole).toString(); return !mExcludedTagSet.contains(tag); } private: TagSet mExcludedTagSet; }; /** * A simple class to eat return keys. We use it to avoid propagating the return * key from our KLineEdit to a dialog using TagWidget. * We can't use KLineEdit::setTrapReturnKey() because it does not play well * with QCompleter, it only deals with KCompletion. */ class ReturnKeyEater : public QObject { public: explicit ReturnKeyEater(QObject* parent = nullptr) : QObject(parent) {} protected: bool eventFilter(QObject*, QEvent* event) override { if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); switch (keyEvent->key()) { case Qt::Key_Return: case Qt::Key_Enter: return true; default: return false; } } return false; } }; struct TagWidgetPrivate { TagWidget* q; TagInfo mTagInfo; QListView* mListView; QComboBox* mComboBox; QPushButton* mAddButton; AbstractSemanticInfoBackEnd* mBackEnd; TagCompleterModel* mTagCompleterModel; TagModel* mAssignedTagModel; void setupWidgets() { mListView = new QListView; TagItemDelegate* delegate = new TagItemDelegate(mListView); QObject::connect(delegate, SIGNAL(removeTagRequested(SemanticInfoTag)), q, SLOT(removeTag(SemanticInfoTag))); QObject::connect(delegate, SIGNAL(assignTagToAllRequested(SemanticInfoTag)), q, SLOT(assignTag(SemanticInfoTag))); mListView->setItemDelegate(delegate); mListView->setModel(mAssignedTagModel); mComboBox = new QComboBox; mComboBox->setEditable(true); mComboBox->setInsertPolicy(QComboBox::NoInsert); mTagCompleterModel = new TagCompleterModel(q); QCompleter* completer = new QCompleter(q); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModel(mTagCompleterModel); mComboBox->setCompleter(completer); mComboBox->setModel(mTagCompleterModel); mAddButton = new QPushButton; mAddButton->setIcon(QIcon::fromTheme("list-add")); mAddButton->setToolTip(i18n("Add tag")); mAddButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QObject::connect(mAddButton, SIGNAL(clicked()), q, SLOT(addTagFromComboBox())); QVBoxLayout* layout = new QVBoxLayout(q); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(mListView); QHBoxLayout* hLayout = new QHBoxLayout; hLayout->addWidget(mComboBox); hLayout->addWidget(mAddButton); layout->addLayout(hLayout); q->setTabOrder(mComboBox, mListView); } void fillTagModel() { Q_ASSERT(mBackEnd); mAssignedTagModel->clear(); TagInfo::ConstIterator it = mTagInfo.constBegin(), end = mTagInfo.constEnd(); for (; it != end; ++it) { mAssignedTagModel->addTag( it.key(), QString(), it.value() ? TagModel::FullyAssigned : TagModel::PartiallyAssigned); } } void updateCompleterModel() { mTagCompleterModel->setTagInfo(mTagInfo); } }; TagWidget::TagWidget(QWidget* parent) : QWidget(parent) , d(new TagWidgetPrivate) { d->q = this; d->mBackEnd = nullptr; d->mAssignedTagModel = new TagModel(this); d->setupWidgets(); installEventFilter(new ReturnKeyEater(this)); connect(d->mComboBox->lineEdit(), &QLineEdit::returnPressed, this, &TagWidget::addTagFromComboBox); } TagWidget::~TagWidget() { delete d; } void TagWidget::setSemanticInfoBackEnd(AbstractSemanticInfoBackEnd* backEnd) { d->mBackEnd = backEnd; d->mAssignedTagModel->setSemanticInfoBackEnd(backEnd); d->mTagCompleterModel->setSemanticInfoBackEnd(backEnd); } void TagWidget::setTagInfo(const TagInfo& tagInfo) { d->mTagInfo = tagInfo; d->fillTagModel(); d->updateCompleterModel(); } void TagWidget::addTagFromComboBox() { Q_ASSERT(d->mBackEnd); QString label = d->mComboBox->currentText(); if (label.isEmpty()) { return; } assignTag(d->mBackEnd->tagForLabel(label.trimmed())); // Use a QTimer because if the tag is new, it will be inserted in the model // and QComboBox will sometimes select it. At least it does so when the // model is empty. QTimer::singleShot(0, d->mComboBox, &QComboBox::clearEditText); } void TagWidget::assignTag(const SemanticInfoTag& tag) { d->mTagInfo[tag] = true; d->mAssignedTagModel->addTag(tag); d->updateCompleterModel(); emit tagAssigned(tag); } void TagWidget::removeTag(const SemanticInfoTag& tag) { d->mTagInfo.remove(tag); d->mAssignedTagModel->removeTag(tag); d->updateCompleterModel(); emit tagRemoved(tag); } } // namespace diff --git a/lib/thumbnailprovider/thumbnailgenerator.cpp b/lib/thumbnailprovider/thumbnailgenerator.cpp index 4ba08ca1..a76df8e0 100644 --- a/lib/thumbnailprovider/thumbnailgenerator.cpp +++ b/lib/thumbnailprovider/thumbnailgenerator.cpp @@ -1,315 +1,315 @@ // 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. */ // Self #include "thumbnailgenerator.h" // Local #include "imageutils.h" #include "jpegcontent.h" #include "gwenviewconfig.h" #include "exiv2imageloader.h" // KDE #include #ifdef KDCRAW_FOUND #include #endif // Qt #include -#include +#include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif const int MIN_PREV_SIZE = 1000; //------------------------------------------------------------------------ // // ThumbnailContext // //------------------------------------------------------------------------ bool ThumbnailContext::load(const QString &pixPath, int pixelSize) { mImage = QImage(); mNeedCaching = true; Orientation orientation = NORMAL; QImage originalImage; QSize originalSize; QByteArray formatHint = pixPath.section(QLatin1Char('.'), -1).toLocal8Bit().toLower(); QImageReader reader(pixPath); JpegContent content; QByteArray format; QByteArray data; QBuffer buffer; int previewRatio = 1; #ifdef KDCRAW_FOUND // raw images deserve special treatment if (KDcrawIface::KDcraw::rawFilesList().contains(QString::fromLatin1(formatHint))) { // use KDCraw to extract the preview bool ret = KDcrawIface::KDcraw::loadEmbeddedPreview(data, pixPath); // We need QImage. Loading JpegContent from QImage - exif lost // Loading QImage from JpegContent - unimplemented, would go with loadFromData if (!ret || !originalImage.loadFromData(data) || qMin(originalImage.width(), originalImage.height()) < MIN_PREV_SIZE) { // if the embedded preview loading failed or gets just a small image, load // half preview instead. That's slower... if (!KDcrawIface::KDcraw::loadHalfPreview(data, pixPath)) { qWarning() << "unable to get preview for " << pixPath.toUtf8().constData(); return false; } previewRatio = 2; } // And we need JpegContent too because of EXIF (orientation!). if (!content.loadFromData(data)) { qWarning() << "unable to load preview for " << pixPath.toUtf8().constData(); return false; } buffer.setBuffer(&data); buffer.open(QIODevice::ReadOnly); reader.setDevice(&buffer); reader.setFormat(formatHint); } else { #else { #endif if (!reader.canRead()) { reader.setDecideFormatFromContent(true); // Set filename again, otherwise QImageReader won't restart from scratch reader.setFileName(pixPath); } if (reader.format() == "jpeg" && GwenviewConfig::applyExifOrientation()) { content.load(pixPath); } } // If there's jpeg content (from jpg or raw files), try to load an embedded thumbnail, if available. // If applyExifOrientation is not set, don't use the // embedded thumbnail since it might be rotated differently // than the actual image if (!content.rawData().isEmpty() && GwenviewConfig::applyExifOrientation()) { QImage thumbnail = content.thumbnail(); orientation = content.orientation(); if (qMax(thumbnail.width(), thumbnail.height()) >= pixelSize) { mImage = thumbnail; if (orientation != NORMAL && orientation != NOT_AVAILABLE) { - QMatrix matrix = ImageUtils::transformMatrix(orientation); + QTransform matrix = ImageUtils::transformMatrix(orientation); mImage = mImage.transformed(matrix); } mOriginalWidth = content.size().width(); mOriginalHeight = content.size().height(); return true; } } // Generate thumbnail from full image originalSize = reader.size(); if (originalSize.isValid() && reader.supportsOption(QImageIOHandler::ScaledSize) && qMax(originalSize.width(), originalSize.height()) >= pixelSize) { QSizeF scaledSize = originalSize; scaledSize.scale(pixelSize, pixelSize, Qt::KeepAspectRatio); if (!scaledSize.isEmpty()) { reader.setScaledSize(scaledSize.toSize()); } } // Rotate if necessary if (GwenviewConfig::applyExifOrientation()) { reader.setAutoTransform(true); } // format() is empty after QImageReader::read() is called format = reader.format(); if (!reader.read(&originalImage)) { return false; } if (!originalSize.isValid()) { originalSize = originalImage.size(); } mOriginalWidth = originalSize.width() * previewRatio; mOriginalHeight = originalSize.height() * previewRatio; if (qMax(mOriginalWidth, mOriginalHeight) <= pixelSize) { mImage = originalImage; mNeedCaching = format != "png"; } else { mImage = originalImage.scaled(pixelSize, pixelSize, Qt::KeepAspectRatio); } if (reader.autoTransform() && (reader.transformation() & QImageIOHandler::TransformationRotate90)) { qSwap(mOriginalWidth, mOriginalHeight); } return true; } //------------------------------------------------------------------------ // // ThumbnailGenerator // //------------------------------------------------------------------------ ThumbnailGenerator::ThumbnailGenerator() : mCancel(false) {} void ThumbnailGenerator::load( const QString& originalUri, time_t originalTime, KIO::filesize_t originalFileSize, const QString& originalMimeType, const QString& pixPath, const QString& thumbnailPath, ThumbnailGroup::Enum group) { QMutexLocker lock(&mMutex); Q_ASSERT(mPixPath.isNull()); mOriginalUri = originalUri; mOriginalTime = originalTime; mOriginalFileSize = originalFileSize; mOriginalMimeType = originalMimeType; mPixPath = pixPath; mThumbnailPath = thumbnailPath; mThumbnailGroup = group; if (!isRunning()) start(); mCond.wakeOne(); } QString ThumbnailGenerator::originalUri() const { return mOriginalUri; } time_t ThumbnailGenerator::originalTime() const { return mOriginalTime; } KIO::filesize_t ThumbnailGenerator::originalFileSize() const { return mOriginalFileSize; } QString ThumbnailGenerator::originalMimeType() const { return mOriginalMimeType; } bool ThumbnailGenerator::testCancel() { QMutexLocker lock(&mMutex); return mCancel; } void ThumbnailGenerator::cancel() { QMutexLocker lock(&mMutex); mCancel = true; mCond.wakeOne(); } void ThumbnailGenerator::run() { LOG(""); while (!testCancel()) { QString pixPath; int pixelSize; { QMutexLocker lock(&mMutex); // empty mPixPath means nothing to do LOG("Waiting for mPixPath"); if (mPixPath.isNull()) { LOG("mPixPath.isNull"); mCond.wait(&mMutex); } } if (testCancel()) { return; } { QMutexLocker lock(&mMutex); pixPath = mPixPath; pixelSize = ThumbnailGroup::pixelSize(mThumbnailGroup); } Q_ASSERT(!pixPath.isNull()); LOG("Loading" << pixPath); ThumbnailContext context; bool ok = context.load(pixPath, pixelSize); { QMutexLocker lock(&mMutex); if (ok) { mImage = context.mImage; mOriginalWidth = context.mOriginalWidth; mOriginalHeight = context.mOriginalHeight; if (context.mNeedCaching && mThumbnailGroup <= ThumbnailGroup::Large) { cacheThumbnail(); } } else { qWarning() << "Could not generate thumbnail for file" << mOriginalUri; } mPixPath.clear(); // done, ready for next } if (testCancel()) { return; } { QSize size(mOriginalWidth, mOriginalHeight); LOG("emitting done signal, size=" << size); QMutexLocker lock(&mMutex); emit done(mImage, size); LOG("Done"); } } LOG("Ending thread"); } void ThumbnailGenerator::cacheThumbnail() { mImage.setText(QStringLiteral("Thumb::URI") , mOriginalUri); mImage.setText(QStringLiteral("Thumb::MTime") , QString::number(mOriginalTime)); mImage.setText(QStringLiteral("Thumb::Size") , QString::number(mOriginalFileSize)); mImage.setText(QStringLiteral("Thumb::Mimetype") , mOriginalMimeType); mImage.setText(QStringLiteral("Thumb::Image::Width") , QString::number(mOriginalWidth)); mImage.setText(QStringLiteral("Thumb::Image::Height"), QString::number(mOriginalHeight)); mImage.setText(QStringLiteral("Software") , QStringLiteral("Gwenview")); emit thumbnailReadyToBeCached(mThumbnailPath, mImage); } } // namespace diff --git a/lib/zoomslider.cpp b/lib/zoomslider.cpp index 246f5c16..e624c798 100644 --- a/lib/zoomslider.cpp +++ b/lib/zoomslider.cpp @@ -1,157 +1,157 @@ // 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 "zoomslider.h" // Qt #include #include #include #include #include // KDE // Local namespace Gwenview { struct ZoomSliderPrivate { QToolButton* mZoomOutButton; QToolButton* mZoomInButton; QSlider* mSlider; QAction* mZoomInAction; QAction* mZoomOutAction; void updateButtons() { // Use QSlider::sliderPosition(), not QSlider::value() because when we are // called from slotZoomSliderActionTriggered(), QSlider::value() has not // been updated yet. mZoomOutButton->setEnabled(mSlider->sliderPosition() > mSlider->minimum()); mZoomInButton->setEnabled(mSlider->sliderPosition() < mSlider->maximum()); } }; static QToolButton* createZoomButton(const QString &iconName) { QToolButton* button = new QToolButton; button->setIcon(QIcon::fromTheme(iconName)); button->setAutoRaise(true); button->setAutoRepeat(true); return button; } ZoomSlider::ZoomSlider(QWidget* parent) : QWidget(parent) , d(new ZoomSliderPrivate) { d->mZoomInButton = createZoomButton(QStringLiteral("zoom-in")); d->mZoomOutButton = createZoomButton(QStringLiteral("zoom-out")); d->mZoomInAction = nullptr; d->mZoomOutAction = nullptr; d->mSlider = new QSlider; d->mSlider->setOrientation(Qt::Horizontal); QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(d->mZoomOutButton); layout->addWidget(d->mSlider); layout->addWidget(d->mZoomInButton); connect(d->mSlider, &QSlider::actionTriggered, this, &ZoomSlider::slotActionTriggered); connect(d->mSlider, &QSlider::valueChanged, this, &ZoomSlider::valueChanged); connect(d->mZoomOutButton, &QToolButton::clicked, this, &ZoomSlider::zoomOut); connect(d->mZoomInButton, &QToolButton::clicked, this, &ZoomSlider::zoomIn); } ZoomSlider::~ZoomSlider() { delete d; } int ZoomSlider::value() const { return d->mSlider->value(); } void ZoomSlider::setValue(int value) { d->mSlider->setValue(value); d->updateButtons(); } void ZoomSlider::setMinimum(int value) { d->mSlider->setMinimum(value); d->updateButtons(); } void ZoomSlider::setMaximum(int value) { d->mSlider->setMaximum(value); d->updateButtons(); } void ZoomSlider::setZoomInAction(QAction* action) { d->mZoomInAction = action; } void ZoomSlider::setZoomOutAction(QAction* action) { d->mZoomOutAction = action; } void ZoomSlider::slotActionTriggered(int) { d->updateButtons(); } void ZoomSlider::zoomOut() { if (d->mZoomOutAction) { d->mZoomOutAction->trigger(); } else { d->mSlider->triggerAction(QAbstractSlider::SliderPageStepSub); } } void ZoomSlider::zoomIn() { if (d->mZoomInAction) { d->mZoomInAction->trigger(); } else { d->mSlider->triggerAction(QAbstractSlider::SliderPageStepAdd); } } QSlider* ZoomSlider::slider() const { return d->mSlider; } } // namespace diff --git a/lib/zoomwidget.cpp b/lib/zoomwidget.cpp index dec9f1d8..56f5c7b2 100644 --- a/lib/zoomwidget.cpp +++ b/lib/zoomwidget.cpp @@ -1,201 +1,201 @@ // 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 "zoomwidget.h" // stdc++ #include // Qt #include #include #include #include #include // KDE // Local #include "zoomslider.h" #include "signalblocker.h" #include "statusbartoolbutton.h" namespace Gwenview { static const qreal MAGIC_K = 1.04; static const qreal MAGIC_OFFSET = 16.; static const qreal PRECISION = 100.; inline int sliderValueForZoom(qreal zoom) { return int(PRECISION * (log(zoom) / log(MAGIC_K) + MAGIC_OFFSET)); } inline qreal zoomForSliderValue(int sliderValue) { return pow(MAGIC_K, sliderValue / PRECISION - MAGIC_OFFSET); } struct ZoomWidgetPrivate { ZoomWidget* q; StatusBarToolButton* mZoomToFitButton; StatusBarToolButton* mActualSizeButton; StatusBarToolButton* mZoomToFillButton; QLabel* mZoomLabel; ZoomSlider* mZoomSlider; QAction* mZoomToFitAction; QAction* mActualSizeAction; QAction* mZoomToFillAction; bool mZoomUpdatedBySlider; void emitZoomChanged() { // Use QSlider::sliderPosition(), not QSlider::value() because when we are // called from slotZoomSliderActionTriggered(), QSlider::value() has not // been updated yet. qreal zoom = zoomForSliderValue(mZoomSlider->slider()->sliderPosition()); mZoomUpdatedBySlider = true; emit q->zoomChanged(zoom); mZoomUpdatedBySlider = false; } }; ZoomWidget::ZoomWidget(QWidget* parent) : QFrame(parent) , d(new ZoomWidgetPrivate) { d->q = this; d->mZoomUpdatedBySlider = false; setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); d->mZoomToFitButton = new StatusBarToolButton; d->mActualSizeButton = new StatusBarToolButton; d->mZoomToFillButton = new StatusBarToolButton; d->mZoomToFitButton->setCheckable(true); d->mActualSizeButton->setCheckable(true); d->mZoomToFillButton->setCheckable(true); d->mZoomToFitButton->setChecked(true); if (QApplication::isLeftToRight()) { d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupLeft); d->mZoomToFillButton->setGroupPosition(StatusBarToolButton::GroupCenter); d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupRight); } else { d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupLeft); d->mZoomToFillButton->setGroupPosition(StatusBarToolButton::GroupCenter); d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupRight); } d->mZoomLabel = new QLabel; d->mZoomLabel->setFixedWidth(d->mZoomLabel->fontMetrics().width(QStringLiteral(" 1000% "))); d->mZoomLabel->setAlignment(Qt::AlignCenter); d->mZoomSlider = new ZoomSlider; d->mZoomSlider->setMinimumWidth(150); d->mZoomSlider->slider()->setSingleStep(int(PRECISION)); d->mZoomSlider->slider()->setPageStep(3 * int(PRECISION)); connect(d->mZoomSlider->slider(), &QAbstractSlider::actionTriggered, this, &ZoomWidget::slotZoomSliderActionTriggered); // Layout QHBoxLayout* layout = new QHBoxLayout(this); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->addWidget(d->mZoomToFitButton); layout->addWidget(d->mZoomToFillButton); layout->addWidget(d->mActualSizeButton); layout->addWidget(d->mZoomSlider); layout->addWidget(d->mZoomLabel); } ZoomWidget::~ZoomWidget() { delete d; } void ZoomWidget::setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction, QAction* zoomToFillAction) { d->mZoomToFitAction = zoomToFitAction; d->mActualSizeAction = actualSizeAction; d->mZoomToFillAction = zoomToFillAction; d->mZoomToFitButton->setDefaultAction(zoomToFitAction); d->mActualSizeButton->setDefaultAction(actualSizeAction); d->mZoomToFillButton->setDefaultAction(zoomToFillAction); d->mZoomSlider->setZoomInAction(zoomInAction); d->mZoomSlider->setZoomOutAction(zoomOutAction); QActionGroup *actionGroup = new QActionGroup(d->q); actionGroup->addAction(d->mZoomToFitAction); actionGroup->addAction(d->mZoomToFillAction); actionGroup->addAction(d->mActualSizeAction); actionGroup->setExclusive(true); // Adjust sizes int width = std::max({d->mZoomToFitButton->sizeHint().width(), d->mActualSizeButton->sizeHint().width(), d->mZoomToFillButton->sizeHint().width()}); d->mZoomToFitButton->setFixedWidth(width); d->mActualSizeButton->setFixedWidth(width); d->mZoomToFillButton->setFixedWidth(width); } void ZoomWidget::slotZoomSliderActionTriggered() { // The slider value changed because of the user (not because of range // changes). In this case disable zoom and apply slider value. d->emitZoomChanged(); } void ZoomWidget::setZoom(qreal zoom) { int intZoom = qRound(zoom * 100); d->mZoomLabel->setText(QStringLiteral("%1%").arg(intZoom)); // Don't change slider value if we come here because the slider change, // avoids choppy sliding scroll. if (!d->mZoomUpdatedBySlider) { QSlider* slider = d->mZoomSlider->slider(); SignalBlocker blocker(slider); int value = sliderValueForZoom(zoom); if (value < slider->minimum()) { // It is possible that we are called *before* setMinimumZoom() as // been called. In this case, define the minimum ourself. d->mZoomSlider->setMinimum(value); } d->mZoomSlider->setValue(value); } } void ZoomWidget::setMinimumZoom(qreal minimumZoom) { d->mZoomSlider->setMinimum(sliderValueForZoom(minimumZoom)); } void ZoomWidget::setMaximumZoom(qreal zoom) { d->mZoomSlider->setMaximum(sliderValueForZoom(zoom)); } } // namespace diff --git a/tests/auto/documenttest.cpp b/tests/auto/documenttest.cpp index 99ea2db7..fa27e244 100644 --- a/tests/auto/documenttest.cpp +++ b/tests/auto/documenttest.cpp @@ -1,894 +1,894 @@ /* 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. */ // Qt #include #include #include // KDE #include #include #include #include #include // Local #include "../lib/abstractimageoperation.h" #include "../lib/document/abstractdocumenteditor.h" #include "../lib/document/documentjob.h" #include "../lib/document/documentfactory.h" #include "../lib/imagemetainfomodel.h" #include "../lib/imageutils.h" #include "../lib/transformimageoperation.h" #include "testutils.h" #include #include "documenttest.h" QTEST_MAIN(DocumentTest) using namespace Gwenview; static void waitUntilMetaInfoLoaded(Document::Ptr doc) { while (doc->loadingState() < Document::MetaInfoLoaded) { QTest::qWait(100); } } static bool waitUntilJobIsDone(DocumentJob* job) { JobWatcher watcher(job); watcher.wait(); return watcher.error() == KJob::NoError; } void DocumentTest::initTestCase() { qRegisterMetaType("QUrl"); } void DocumentTest::init() { DocumentFactory::instance()->clearCache(); } void DocumentTest::testLoad() { QFETCH(QString, fileName); QFETCH(QByteArray, expectedFormat); QFETCH(int, expectedKindInt); QFETCH(bool, expectedIsAnimated); QFETCH(QImage, expectedImage); QFETCH(int, maxHeight); // number of lines to test. -1 to test all lines MimeTypeUtils::Kind expectedKind = MimeTypeUtils::Kind(expectedKindInt); QUrl url = urlForTestFile(fileName); // testing RAW loading. For raw, QImage directly won't work -> load it using KDCRaw QByteArray mFormatHint = url.fileName().section('.', -1).toLocal8Bit().toLower(); if (KDcrawIface::KDcraw::rawFilesList().contains(QString(mFormatHint))) { if (!KDcrawIface::KDcraw::loadEmbeddedPreview(expectedImage, url.toLocalFile())) { QSKIP("Not running this test: failed to get expectedImage. Try running ./fetch_testing_raw.sh\ in the tests/data directory and then rerun the tests."); } } if (expectedKind != MimeTypeUtils::KIND_SVG_IMAGE) { if (expectedImage.isNull()) { QSKIP("Not running this test: QImage failed to load the test image"); } } Document::Ptr doc = DocumentFactory::instance()->load(url); QSignalSpy spy(doc.data(), SIGNAL(isAnimatedUpdated())); doc->waitUntilLoaded(); QCOMPARE(doc->loadingState(), Document::Loaded); QCOMPARE(doc->kind(), expectedKind); QCOMPARE(doc->isAnimated(), expectedIsAnimated); QCOMPARE(spy.count(), doc->isAnimated() ? 1 : 0); if (doc->kind() == MimeTypeUtils::KIND_RASTER_IMAGE) { QImage image = doc->image(); if (maxHeight > -1) { QRect poiRect(0, 0, image.width(), maxHeight); image = image.copy(poiRect); expectedImage = expectedImage.copy(poiRect); } QCOMPARE(image, expectedImage); QCOMPARE(QString(doc->format()), QString(expectedFormat)); } } static void testLoad_newRow( const char* fileName, const QByteArray& format, MimeTypeUtils::Kind kind = MimeTypeUtils::KIND_RASTER_IMAGE, bool isAnimated = false, int maxHeight = -1 ) { QTest::newRow(fileName) << fileName << QByteArray(format) << int(kind) << isAnimated << QImage(pathForTestFile(fileName), format) << maxHeight; } void DocumentTest::testLoad_data() { QTest::addColumn("fileName"); QTest::addColumn("expectedFormat"); QTest::addColumn("expectedKindInt"); QTest::addColumn("expectedIsAnimated"); QTest::addColumn("expectedImage"); QTest::addColumn("maxHeight"); testLoad_newRow("test.png", "png"); testLoad_newRow("160216_no_size_before_decoding.eps", "eps"); testLoad_newRow("160382_corrupted.jpeg", "jpeg", MimeTypeUtils::KIND_RASTER_IMAGE, false, 55); testLoad_newRow("1x10k.png", "png"); testLoad_newRow("1x10k.jpg", "jpeg"); testLoad_newRow("test.xcf", "xcf"); testLoad_newRow("188191_does_not_load.tga", "tga"); testLoad_newRow("289819_does_not_load.png", "png"); testLoad_newRow("png-with-jpeg-extension.jpg", "png"); testLoad_newRow("jpg-with-gif-extension.gif", "jpeg"); // RAW preview testLoad_newRow("CANON-EOS350D-02.CR2", "cr2", MimeTypeUtils::KIND_RASTER_IMAGE, false); testLoad_newRow("dsc_0093.nef", "nef", MimeTypeUtils::KIND_RASTER_IMAGE, false); // SVG testLoad_newRow("test.svg", "", MimeTypeUtils::KIND_SVG_IMAGE); // FIXME: Test svgz // Animated testLoad_newRow("4frames.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, true); testLoad_newRow("1frame.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); testLoad_newRow("185523_1frame_with_graphic_control_extension.gif", "gif", MimeTypeUtils::KIND_RASTER_IMAGE, false); } void DocumentTest::testLoadTwoPasses() { QUrl url = urlForTestFile("test.png"); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load 'test.png'"); Document::Ptr doc = DocumentFactory::instance()->load(url); waitUntilMetaInfoLoaded(doc); QVERIFY2(doc->image().isNull(), "Image shouldn't have been loaded at this time"); QCOMPARE(doc->format().data(), "png"); doc->waitUntilLoaded(); QCOMPARE(image, doc->image()); } void DocumentTest::testLoadEmpty() { QUrl url = urlForTestFile("empty.png"); Document::Ptr doc = DocumentFactory::instance()->load(url); while (doc->loadingState() <= Document::KindDetermined) { QTest::qWait(100); } QCOMPARE(doc->loadingState(), Document::LoadingFailed); } #define NEW_ROW(fileName) QTest::newRow(fileName) << fileName void DocumentTest::testLoadDownSampled_data() { QTest::addColumn("fileName"); NEW_ROW("orient6.jpg"); NEW_ROW("1x10k.jpg"); } #undef NEW_ROW void DocumentTest::testLoadDownSampled() { // Note: for now we only support down sampling on jpeg, do not use test.png // here QFETCH(QString, fileName); QUrl url = urlForTestFile(fileName); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load test image"); Document::Ptr doc = DocumentFactory::instance()->load(url); QSignalSpy downSampledImageReadySpy(doc.data(), SIGNAL(downSampledImageReady())); QSignalSpy loadingFailedSpy(doc.data(), SIGNAL(loadingFailed(QUrl))); QSignalSpy loadedSpy(doc.data(), SIGNAL(loaded(QUrl))); bool ready = doc->prepareDownSampledImageForZoom(0.2); QVERIFY2(!ready, "There should not be a down sampled image at this point"); while (downSampledImageReadySpy.count() == 0 && loadingFailedSpy.count() == 0 && loadedSpy.count() == 0) { QTest::qWait(100); } QImage downSampledImage = doc->downSampledImageForZoom(0.2); QVERIFY2(!downSampledImage.isNull(), "Down sampled image should not be null"); QSize expectedSize = doc->size() / 2; if (expectedSize.isEmpty()) { expectedSize = image.size(); } QCOMPARE(downSampledImage.size(), expectedSize); } /** * Down sampling is not supported on png. We should get a complete image * instead. */ void DocumentTest::testLoadDownSampledPng() { QUrl url = urlForTestFile("test.png"); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load test image"); Document::Ptr doc = DocumentFactory::instance()->load(url); LoadingStateSpy stateSpy(doc); connect(doc.data(), &Document::loaded, &stateSpy, &LoadingStateSpy::readState); bool ready = doc->prepareDownSampledImageForZoom(0.2); QVERIFY2(!ready, "There should not be a down sampled image at this point"); doc->waitUntilLoaded(); QCOMPARE(stateSpy.mCallCount, 1); QCOMPARE(stateSpy.mState, Document::Loaded); } void DocumentTest::testLoadRemote() { QUrl url = setUpRemoteTestDir("test.png"); if (!url.isValid()) { QSKIP("Not running this test: failed to setup remote test dir."); } url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + '/' + "test.png"); QVERIFY2(KIO::stat(url, KIO::StatJob::SourceSide, 0)->exec(), "test url not found"); Document::Ptr doc = DocumentFactory::instance()->load(url); doc->waitUntilLoaded(); QImage image = doc->image(); QCOMPARE(image.width(), 150); QCOMPARE(image.height(), 100); } void DocumentTest::testLoadAnimated() { QUrl srcUrl = urlForTestFile("40frames.gif"); Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); QSignalSpy spy(doc.data(), SIGNAL(imageRectUpdated(QRect))); doc->waitUntilLoaded(); QVERIFY(doc->isAnimated()); // Test we receive only one imageRectUpdated() until animation is started // (the imageRectUpdated() is triggered by the loading of the first image) QTest::qWait(1000); QCOMPARE(spy.count(), 1); // Test we now receive some imageRectUpdated() doc->startAnimation(); QTest::qWait(1000); int count = spy.count(); doc->stopAnimation(); QVERIFY2(count > 0, "No imageRectUpdated() signal received"); // Test we do not receive imageRectUpdated() anymore QTest::qWait(1000); QCOMPARE(count, spy.count()); // Start again, we should receive imageRectUpdated() again doc->startAnimation(); QTest::qWait(1000); QVERIFY2(spy.count() > count, "No imageRectUpdated() signal received after restarting"); } void DocumentTest::testPrepareDownSampledAfterFailure() { QUrl url = urlForTestFile("empty.png"); Document::Ptr doc = DocumentFactory::instance()->load(url); doc->waitUntilLoaded(); QCOMPARE(doc->loadingState(), Document::LoadingFailed); bool ready = doc->prepareDownSampledImageForZoom(0.25); QVERIFY2(!ready, "Down sampled image should not be ready"); } void DocumentTest::testSaveRemote() { QUrl dstUrl = setUpRemoteTestDir(); if (!dstUrl.isValid()) { QSKIP("Not running this test: failed to setup remote test dir."); } QUrl srcUrl = urlForTestFile("test.png"); Document::Ptr doc = DocumentFactory::instance()->load(srcUrl); doc->waitUntilLoaded(); dstUrl = dstUrl.adjusted(QUrl::StripTrailingSlash); dstUrl.setPath(dstUrl.path() + '/' + "testSaveRemote.png"); QVERIFY(waitUntilJobIsDone(doc->save(dstUrl, "png"))); } /** * Check that deleting a document while it is loading does not crash */ void DocumentTest::testDeleteWhileLoading() { { QUrl url = urlForTestFile("test.png"); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load 'test.png'"); Document::Ptr doc = DocumentFactory::instance()->load(url); } DocumentFactory::instance()->clearCache(); // Wait two seconds. If the test fails we will get a segfault while waiting QTest::qWait(2000); } void DocumentTest::testLoadRotated() { QUrl url = urlForTestFile("orient6.jpg"); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load 'orient6.jpg'"); - QMatrix matrix = ImageUtils::transformMatrix(ROT_90); + QTransform matrix = ImageUtils::transformMatrix(ROT_90); image = image.transformed(matrix); Document::Ptr doc = DocumentFactory::instance()->load(url); doc->waitUntilLoaded(); QCOMPARE(image, doc->image()); // RAW preview on rotated image url = urlForTestFile("dsd_1838.nef"); if (!KDcrawIface::KDcraw::loadEmbeddedPreview(image, url.toLocalFile())) { QSKIP("Not running this test: failed to get image. Try running ./fetch_testing_raw.sh\ in the tests/data directory and then rerun the tests."); } matrix = ImageUtils::transformMatrix(ROT_270); image = image.transformed(matrix); doc = DocumentFactory::instance()->load(url); doc->waitUntilLoaded(); QCOMPARE(image, doc->image()); } /** * Checks that asking the DocumentFactory the same document twice in a row does * not load it twice */ void DocumentTest::testMultipleLoads() { QUrl url = urlForTestFile("orient6.jpg"); Document::Ptr doc1 = DocumentFactory::instance()->load(url); Document::Ptr doc2 = DocumentFactory::instance()->load(url); QCOMPARE(doc1.data(), doc2.data()); } void DocumentTest::testSaveAs() { QUrl url = urlForTestFile("orient6.jpg"); DocumentFactory* factory = DocumentFactory::instance(); Document::Ptr doc = factory->load(url); QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl,QUrl))); QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(QUrl))); doc->startLoadingFullImage(); QUrl destUrl = urlForTestOutputFile("result.png"); QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); QCOMPARE(doc->format().data(), "png"); QCOMPARE(doc->url(), destUrl); QCOMPARE(doc->metaInfo()->getValueForKey("General.Name"), destUrl.fileName()); QVERIFY2(doc->loadingState() == Document::Loaded, "Document is supposed to finish loading before saving" ); QTest::qWait(100); // saved() is emitted asynchronously QCOMPARE(savedSpy.count(), 1); QVariantList args = savedSpy.takeFirst(); QCOMPARE(args.at(0).toUrl(), url); QCOMPARE(args.at(1).toUrl(), destUrl); QImage image("result.png", "png"); QCOMPARE(doc->image(), image); QVERIFY(!DocumentFactory::instance()->hasUrl(url)); QVERIFY(DocumentFactory::instance()->hasUrl(destUrl)); QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); // No changes were made QCOMPARE(documentChangedSpy.count(), 1); args = documentChangedSpy.takeFirst(); QCOMPARE(args.at(0).toUrl(), destUrl); } void DocumentTest::testLosslessSave() { QUrl url1 = urlForTestFile("orient6.jpg"); Document::Ptr doc = DocumentFactory::instance()->load(url1); doc->startLoadingFullImage(); QUrl url2 = urlForTestOutputFile("orient1.jpg"); QVERIFY(waitUntilJobIsDone(doc->save(url2, "jpeg"))); QImage image1; QVERIFY(image1.load(url1.toLocalFile())); QImage image2; QVERIFY(image2.load(url2.toLocalFile())); QCOMPARE(image1, image2); } void DocumentTest::testLosslessRotate() { // Generate test image QImage image1(200, 96, QImage::Format_RGB32); { QPainter painter(&image1); QConicalGradient gradient(QPointF(100, 48), 100); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, Qt::blue); painter.fillRect(image1.rect(), gradient); } QUrl url1 = urlForTestOutputFile("lossless1.jpg"); QVERIFY(image1.save(url1.toLocalFile(), "jpeg")); // Load it as a Gwenview document Document::Ptr doc = DocumentFactory::instance()->load(url1); doc->waitUntilLoaded(); // Rotate one time QVERIFY(doc->editor()); doc->editor()->applyTransformation(ROT_90); // Save it QUrl url2 = urlForTestOutputFile("lossless2.jpg"); waitUntilJobIsDone(doc->save(url2, "jpeg")); // Load the saved image doc = DocumentFactory::instance()->load(url2); doc->waitUntilLoaded(); // Rotate the other way QVERIFY(doc->editor()); doc->editor()->applyTransformation(ROT_270); waitUntilJobIsDone(doc->save(url2, "jpeg")); // Compare the saved images QVERIFY(image1.load(url1.toLocalFile())); QImage image2; QVERIFY(image2.load(url2.toLocalFile())); QCOMPARE(image1, image2); } void DocumentTest::testModifyAndSaveAs() { QVariantList args; class TestOperation : public AbstractImageOperation { public: void redo() override { QImage image(10, 10, QImage::Format_ARGB32); image.fill(QColor(Qt::white).rgb()); document()->editor()->setImage(image); finish(true); } }; QUrl url = urlForTestFile("orient6.jpg"); DocumentFactory* factory = DocumentFactory::instance(); Document::Ptr doc = factory->load(url); QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl,QUrl))); QSignalSpy modifiedDocumentListChangedSpy(factory, SIGNAL(modifiedDocumentListChanged())); QSignalSpy documentChangedSpy(factory, SIGNAL(documentChanged(QUrl))); doc->waitUntilLoaded(); QVERIFY(!doc->isModified()); QCOMPARE(modifiedDocumentListChangedSpy.count(), 0); // Modify image QVERIFY(doc->editor()); TestOperation* op = new TestOperation; op->applyToDocument(doc); QTest::qWait(100); QVERIFY(doc->isModified()); QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); modifiedDocumentListChangedSpy.clear(); QList lst = factory->modifiedDocumentList(); QCOMPARE(lst.count(), 1); QCOMPARE(lst.first(), url); QCOMPARE(documentChangedSpy.count(), 1); args = documentChangedSpy.takeFirst(); QCOMPARE(args.at(0).toUrl(), url); // Save it under a new name QUrl destUrl = urlForTestOutputFile("modify.png"); QVERIFY(waitUntilJobIsDone(doc->save(destUrl, "png"))); // Wait a bit because save() will clear the undo stack when back to the // event loop QTest::qWait(100); QVERIFY(!doc->isModified()); QVERIFY(!factory->hasUrl(url)); QVERIFY(factory->hasUrl(destUrl)); QCOMPARE(modifiedDocumentListChangedSpy.count(), 1); QVERIFY(DocumentFactory::instance()->modifiedDocumentList().isEmpty()); QCOMPARE(documentChangedSpy.count(), 2); QList modifiedUrls = QList() << url << destUrl; QVERIFY(modifiedUrls.contains(url)); QVERIFY(modifiedUrls.contains(destUrl)); } void DocumentTest::testMetaInfoJpeg() { QUrl url = urlForTestFile("orient6.jpg"); Document::Ptr doc = DocumentFactory::instance()->load(url); // We cleared the cache, so the document should not be loaded Q_ASSERT(doc->loadingState() <= Document::KindDetermined); // Wait until we receive the metaInfoUpdated() signal QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); while (metaInfoUpdatedSpy.count() == 0) { QTest::qWait(100); } // Extract an exif key QString value = doc->metaInfo()->getValueForKey("Exif.Image.Make"); QCOMPARE(value, QString::fromUtf8("Canon")); } void DocumentTest::testMetaInfoBmp() { QUrl url = urlForTestOutputFile("metadata.bmp"); const int width = 200; const int height = 100; QImage image(width, height, QImage::Format_ARGB32); image.fill(Qt::black); image.save(url.toLocalFile(), "BMP"); Document::Ptr doc = DocumentFactory::instance()->load(url); QSignalSpy metaInfoUpdatedSpy(doc.data(), SIGNAL(metaInfoUpdated())); waitUntilMetaInfoLoaded(doc); Q_ASSERT(metaInfoUpdatedSpy.count() >= 1); QString value = doc->metaInfo()->getValueForKey("General.ImageSize"); QString expectedValue = QStringLiteral("%1x%2").arg(width).arg(height); QCOMPARE(value, expectedValue); } void DocumentTest::testForgetModifiedDocument() { QSignalSpy spy(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged())); DocumentFactory::instance()->forget(QUrl("file://does/not/exist.png")); QCOMPARE(spy.count(), 0); // Generate test image QImage image1(200, 96, QImage::Format_RGB32); { QPainter painter(&image1); QConicalGradient gradient(QPointF(100, 48), 100); gradient.setColorAt(0, Qt::white); gradient.setColorAt(1, Qt::blue); painter.fillRect(image1.rect(), gradient); } QUrl url = urlForTestOutputFile("testForgetModifiedDocument.png"); QVERIFY(image1.save(url.toLocalFile(), "png")); // Load it as a Gwenview document Document::Ptr doc = DocumentFactory::instance()->load(url); doc->waitUntilLoaded(); // Modify it TransformImageOperation* op = new TransformImageOperation(ROT_90); op->applyToDocument(doc); QTest::qWait(100); QCOMPARE(spy.count(), 1); QList lst = DocumentFactory::instance()->modifiedDocumentList(); QCOMPARE(lst.length(), 1); QCOMPARE(lst.first(), url); // Forget it DocumentFactory::instance()->forget(url); QCOMPARE(spy.count(), 2); lst = DocumentFactory::instance()->modifiedDocumentList(); QVERIFY(lst.isEmpty()); } void DocumentTest::testModifiedAndSavedSignals() { TransformImageOperation* op; QUrl url = urlForTestFile("orient6.jpg"); Document::Ptr doc = DocumentFactory::instance()->load(url); QSignalSpy modifiedSpy(doc.data(), SIGNAL(modified(QUrl))); QSignalSpy savedSpy(doc.data(), SIGNAL(saved(QUrl,QUrl))); doc->waitUntilLoaded(); QCOMPARE(modifiedSpy.count(), 0); QCOMPARE(savedSpy.count(), 0); op = new TransformImageOperation(ROT_90); op->applyToDocument(doc); QTest::qWait(100); QCOMPARE(modifiedSpy.count(), 1); op = new TransformImageOperation(ROT_90); op->applyToDocument(doc); QTest::qWait(100); QCOMPARE(modifiedSpy.count(), 2); doc->undoStack()->undo(); QTest::qWait(100); QCOMPARE(modifiedSpy.count(), 3); doc->undoStack()->undo(); QTest::qWait(100); QCOMPARE(savedSpy.count(), 1); } class TestJob : public DocumentJob { public: TestJob(QString* str, char ch) : mStr(str) , mCh(ch) {} protected: void doStart() override { *mStr += mCh; emitResult(); } private: QString* mStr; char mCh; }; void DocumentTest::testJobQueue() { QUrl url = urlForTestFile("orient6.jpg"); Document::Ptr doc = DocumentFactory::instance()->load(url); QSignalSpy spy(doc.data(), SIGNAL(busyChanged(QUrl,bool))); QString str; doc->enqueueJob(new TestJob(&str, 'a')); doc->enqueueJob(new TestJob(&str, 'b')); doc->enqueueJob(new TestJob(&str, 'c')); QVERIFY(doc->isBusy()); QEventLoop loop; connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); loop.exec(); QVERIFY(!doc->isBusy()); QCOMPARE(spy.count(), 2); QVariantList row = spy.takeFirst(); QCOMPARE(row.at(0).toUrl(), url); QVERIFY(row.at(1).toBool()); row = spy.takeFirst(); QCOMPARE(row.at(0).toUrl(), url); QVERIFY(!row.at(1).toBool()); QCOMPARE(str, QStringLiteral("abc")); } class TestCheckDocumentEditorJob : public DocumentJob { public: TestCheckDocumentEditorJob(int* hasEditor) : mHasEditor(hasEditor) { *mHasEditor = -1; } protected: void doStart() override { document()->waitUntilLoaded(); *mHasEditor = checkDocumentEditor() ? 1 : 0; emitResult(); } private: int* mHasEditor; }; class TestUiDelegate : public KJobUiDelegate { public: TestUiDelegate(bool* showErrorMessageCalled) : mShowErrorMessageCalled(showErrorMessageCalled) { setAutoErrorHandlingEnabled(true); *mShowErrorMessageCalled = false; } void showErrorMessage() override { //qDebug(); *mShowErrorMessageCalled = true; } private: bool* mShowErrorMessageCalled; }; /** * Test that an error is reported when a DocumentJob fails because there is no * document editor available */ void DocumentTest::testCheckDocumentEditor() { int hasEditor; bool showErrorMessageCalled; QEventLoop loop; Document::Ptr doc; TestCheckDocumentEditorJob* job; doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); job = new TestCheckDocumentEditorJob(&hasEditor); job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); doc->enqueueJob(job); connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); loop.exec(); QVERIFY(!showErrorMessageCalled); QCOMPARE(hasEditor, 1); doc = DocumentFactory::instance()->load(urlForTestFile("test.svg")); job = new TestCheckDocumentEditorJob(&hasEditor); job->setUiDelegate(new TestUiDelegate(&showErrorMessageCalled)); doc->enqueueJob(job); connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); loop.exec(); QVERIFY(showErrorMessageCalled); QCOMPARE(hasEditor, 0); } /** * An operation should only pushed to the document undo stack if it succeed */ void DocumentTest::testUndoStackPush() { class SuccessOperation : public AbstractImageOperation { protected: void redo() override { QMetaObject::invokeMethod(this, "finish", Qt::QueuedConnection, Q_ARG(bool, true)); } }; class FailureOperation : public AbstractImageOperation { protected: void redo() override { QMetaObject::invokeMethod(this, "finish", Qt::QueuedConnection, Q_ARG(bool, false)); } }; AbstractImageOperation* op; Document::Ptr doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); // A successful operation should be added to the undo stack op = new SuccessOperation; op->applyToDocument(doc); QTest::qWait(100); QVERIFY(!doc->undoStack()->isClean()); // Reset doc->undoStack()->undo(); QVERIFY(doc->undoStack()->isClean()); // A failed operation should not be added to the undo stack op = new FailureOperation; op->applyToDocument(doc); QTest::qWait(100); QVERIFY(doc->undoStack()->isClean()); } void DocumentTest::testUndoRedo() { class SuccessOperation : public AbstractImageOperation { public: int mRedoCount = 0; int mUndoCount = 0; protected: void redo() override { mRedoCount++; finish(true); } void undo() override { mUndoCount++; finish(true); } }; Document::Ptr doc = DocumentFactory::instance()->load(urlForTestFile("orient6.jpg")); QSignalSpy modifiedSpy(doc.data(), &Document::modified); QSignalSpy savedSpy(doc.data(), &Document::saved); SuccessOperation* op = new SuccessOperation; QCOMPARE(op->mRedoCount, 0); QCOMPARE(op->mUndoCount, 0); // Apply (redo) operation op->applyToDocument(doc); QVERIFY(modifiedSpy.wait()); QCOMPARE(op->mRedoCount, 1); QCOMPARE(op->mUndoCount, 0); QCOMPARE(doc->undoStack()->count(), 1); QVERIFY(!doc->undoStack()->isClean()); // Undo operation doc->undoStack()->undo(); QVERIFY(savedSpy.wait()); QCOMPARE(op->mRedoCount, 1); QCOMPARE(op->mUndoCount, 1); QCOMPARE(doc->undoStack()->count(), 1); QVERIFY(doc->undoStack()->isClean()); // Redo operation doc->undoStack()->redo(); QVERIFY(modifiedSpy.wait()); QCOMPARE(op->mRedoCount, 2); QCOMPARE(op->mUndoCount, 1); QCOMPARE(doc->undoStack()->count(), 1); QVERIFY(!doc->undoStack()->isClean()); // Undo operation again doc->undoStack()->undo(); QVERIFY(savedSpy.wait()); QCOMPARE(op->mRedoCount, 2); QCOMPARE(op->mUndoCount, 2); QCOMPARE(doc->undoStack()->count(), 1); QVERIFY(doc->undoStack()->isClean()); } diff --git a/tests/auto/slidecontainerautotest.cpp b/tests/auto/slidecontainerautotest.cpp index b9b9ae1b..afe1956a 100644 --- a/tests/auto/slidecontainerautotest.cpp +++ b/tests/auto/slidecontainerautotest.cpp @@ -1,145 +1,145 @@ // 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 "slidecontainerautotest.h" // Local #include // KDE #include // Qt #include #include #include QTEST_MAIN(SlideContainerAutoTest) using namespace Gwenview; struct TestWindow : public QWidget { explicit TestWindow(QWidget* parent = 0) : QWidget(parent) , mContainer(new SlideContainer) , mContent(0) { createContent(); mMainWidget = new QTextEdit(); QVBoxLayout* layout = new QVBoxLayout(this); layout->setSpacing(0); - layout->setMargin(0); + layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(mMainWidget); layout->addWidget(mContainer); } void createContent() { mContent = new QTextEdit; mContent->setFixedSize(100, 40); mContainer->setContent(mContent); } SlideContainer* mContainer; QWidget* mMainWidget; QWidget* mContent; }; void SlideContainerAutoTest::testInit() { // Even with content, a SlideContainer should be invisible until slideIn() // is called TestWindow window; window.show(); QTest::qWait(500); QCOMPARE(window.mMainWidget->height(), window.height()); } void SlideContainerAutoTest::testSlideIn() { TestWindow window; QSignalSpy inSpy(window.mContainer, SIGNAL(slidedIn())); QSignalSpy outSpy(window.mContainer, SIGNAL(slidedOut())); window.show(); window.mContainer->slideIn(); while (window.mContainer->slideHeight() != window.mContent->height()) { QTest::qWait(100); } QCOMPARE(window.mContainer->height(), window.mContent->height()); QCOMPARE(inSpy.count(), 1); QCOMPARE(outSpy.count(), 0); } void SlideContainerAutoTest::testSlideOut() { TestWindow window; window.show(); window.mContainer->slideIn(); while (window.mContainer->slideHeight() != window.mContent->height()) { QTest::qWait(100); } QSignalSpy inSpy(window.mContainer, SIGNAL(slidedIn())); QSignalSpy outSpy(window.mContainer, SIGNAL(slidedOut())); window.mContainer->slideOut(); while (window.mContainer->slideHeight() != 0) { QTest::qWait(100); } QCOMPARE(window.mContainer->height(), 0); QCOMPARE(inSpy.count(), 0); QCOMPARE(outSpy.count(), 1); } void SlideContainerAutoTest::testSlideInDeleteSlideOut() { // If content is deleted while visible, slideOut() should still produce an // animation TestWindow window; window.show(); window.mContainer->slideIn(); while (window.mContainer->slideHeight() != window.mContent->height()) { QTest::qWait(100); } window.mContent->deleteLater(); window.mContainer->slideOut(); while (window.mContainer->slideHeight() != 0) { QTest::qWait(100); } QCOMPARE(window.mContainer->height(), 0); } void SlideContainerAutoTest::testHiddenContentResize() { // Resizing content should not trigger a slide if it is not visible. TestWindow window; window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); window.mContent->show(); window.mContent->setFixedSize(150, 80); QTest::qWait(500); QCOMPARE(window.mContainer->height(), 0); } diff --git a/tests/auto/transformimageoperationtest.cpp b/tests/auto/transformimageoperationtest.cpp index a3c4e9c8..81ea4476 100644 --- a/tests/auto/transformimageoperationtest.cpp +++ b/tests/auto/transformimageoperationtest.cpp @@ -1,69 +1,69 @@ /* Gwenview: an image viewer Copyright 2010 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 // KDE #include #include // Local #include "../lib/document/documentfactory.h" #include "../lib/imageutils.h" #include "../lib/transformimageoperation.h" #include "testutils.h" #include "transformimageoperationtest.h" QTEST_MAIN(TransformImageOperationTest) using namespace Gwenview; void TransformImageOperationTest::initTestCase() { qRegisterMetaType("QUrl"); } void TransformImageOperationTest::init() { DocumentFactory::instance()->clearCache(); } void TransformImageOperationTest::testRotate90() { QUrl url = urlForTestFile("test.png"); QImage image; bool ok = image.load(url.toLocalFile()); QVERIFY2(ok, "Could not load 'test.png'"); - QMatrix matrix = ImageUtils::transformMatrix(ROT_90); + QTransform matrix = ImageUtils::transformMatrix(ROT_90); image = image.transformed(matrix); Document::Ptr doc = DocumentFactory::instance()->load(url); TransformImageOperation* op = new TransformImageOperation(ROT_90); QEventLoop loop; connect(doc.data(), &Document::allTasksDone, &loop, &QEventLoop::quit); op->applyToDocument(doc); loop.exec(); QCOMPARE(image, doc->image()); }