diff --git a/app/configdialog.cpp b/app/configdialog.cpp index 624a014e..7e325acf 100644 --- a/app/configdialog.cpp +++ b/app/configdialog.cpp @@ -1,132 +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); 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, SIGNAL(valueChanged(int)), SLOT(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/imageviewconfigpage.ui b/app/imageviewconfigpage.ui index 6ee7c89d..8819b122 100644 --- a/app/imageviewconfigpage.ui +++ b/app/imageviewconfigpage.ui @@ -1,661 +1,686 @@ ImageViewConfigPage 0 0 500 650 Show transparency with: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter surroundingRadioButton 6 0 Surrounding background true Qt::Horizontal 40 20 6 0 &Checkerboard background false Qt::Horizontal 40 20 6 0 &Solid color background: false Qt::Horizontal 20 20 Qt::Vertical QSizePolicy::Fixed 207 17 Mouse wheel behavior: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mouseWheelScrollRadioButton Scroll true Qt::Horizontal 40 20 Browse Qt::Horizontal 40 20 + + + + + Zoom + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + Qt::Vertical QSizePolicy::Fixed 207 17 - + Zoom mode: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter autofitZoomModeRadioButton - + Autofit each image true Qt::Horizontal 40 20 - + Keep same zoom and position Qt::Horizontal 40 20 - + Per image zoom and position Qt::Horizontal 40 20 - + Qt::Vertical QSizePolicy::Fixed 207 17 - + Enlarge smaller images Qt::Horizontal 40 20 - + Qt::Vertical QSizePolicy::Fixed 204 17 - + Animations: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter glAnimationRadioButton - + OpenGL true Qt::Horizontal 40 20 - + Software Qt::Horizontal 40 20 - + None Qt::Horizontal 40 20 - + Qt::Vertical QSizePolicy::Fixed 204 17 - + <b>Thumbnail Bar</b> - + Qt::Vertical QSizePolicy::Fixed 204 12 - + Orientation: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter horizontalRadioButton - + Horizontal Qt::Horizontal 40 20 - + Vertical Qt::Horizontal 40 20 - + Row count: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter kcfg_ThumbnailBarRowCount - + 1 10 Qt::Horizontal 40 20 - + Qt::Vertical 20 60 KColorButton QPushButton
kcolorbutton.h
QSpinBox QSpinBox
knuminput.h
checkBoardRadioButton solidColorRadioButton kcfg_AlphaBackgroundColor mouseWheelScrollRadioButton mouseWheelBrowseRadioButton + mouseWheelZoomRadioButton kcfg_EnlargeSmallerImages glAnimationRadioButton softwareAnimationRadioButton noAnimationRadioButton horizontalRadioButton verticalRadioButton kcfg_ThumbnailBarRowCount solidColorRadioButton toggled(bool) kcfg_AlphaBackgroundColor setEnabled(bool) 195 49 292 56
diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp index 9b822e07..2035df28 100644 --- a/lib/documentview/documentview.cpp +++ b/lib/documentview/documentview.cpp @@ -1,1018 +1,1022 @@ // 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 "documentview.h" // C++ Standard library #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include // Local #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 static const qreal REAL_DELTA = 0.001; static const qreal MAXIMUM_ZOOM_VALUE = qreal(DocumentView::MaximumZoom); static const auto MINSTEP = sqrt(0.5); static const auto MAXSTEP = sqrt(2.0); static const int COMPARE_MARGIN = 4; const int DocumentView::MaximumZoom = 16; const int DocumentView::AnimDuration = 250; struct DocumentViewPrivate { DocumentView* q; int mSortKey; // Used to sort views when displayed in compare mode HudWidget* mHud; BirdEyeView* mBirdEyeView; QPointer mMoveAnimation; QPointer mFadeAnimation; QGraphicsOpacityEffect* mOpacityEffect; LoadingIndicator* mLoadingIndicator; QScopedPointer mAdapter; QList mZoomSnapValues; Document::Ptr mDocument; DocumentView::Setup mSetup; bool mCurrent; bool mCompareMode; int controlWheelAccumulatedDelta; QPointF mDragStartPosition; QPointer mDragThumbnailProvider; QPointer mDrag; void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); mAdapter.reset(adapter); adapter->widget()->setParentItem(q); resizeAdapterWidget(); if (adapter->canZoom()) { QObject::connect(adapter, SIGNAL(zoomChanged(qreal)), q, SLOT(slotZoomChanged(qreal))); QObject::connect(adapter, SIGNAL(zoomInRequested(QPointF)), q, SLOT(zoomIn(QPointF))); QObject::connect(adapter, SIGNAL(zoomOutRequested(QPointF)), q, SLOT(zoomOut(QPointF))); QObject::connect(adapter, SIGNAL(zoomToFitChanged(bool)), q, SIGNAL(zoomToFitChanged(bool))); QObject::connect(adapter, SIGNAL(zoomToFillChanged(bool)), q, SIGNAL(zoomToFillChanged(bool))); } QObject::connect(adapter, SIGNAL(scrollPosChanged()), q, SIGNAL(positionChanged())); QObject::connect(adapter, SIGNAL(previousImageRequested()), q, SIGNAL(previousImageRequested())); QObject::connect(adapter, SIGNAL(nextImageRequested()), q, SIGNAL(nextImageRequested())); QObject::connect(adapter, SIGNAL(toggleFullScreenRequested()), q, SIGNAL(toggleFullScreenRequested())); QObject::connect(adapter, SIGNAL(completed()), q, SLOT(slotCompleted())); adapter->loadConfig(); adapter->widget()->installSceneEventFilter(q); if (mCurrent) { adapter->widget()->setFocus(); } if (mSetup.valid && adapter->canZoom()) { adapter->setZoomToFit(mSetup.zoomToFit); adapter->setZoomToFill(mSetup.zoomToFill); if (!mSetup.zoomToFit && !mSetup.zoomToFill) { adapter->setZoom(mSetup.zoom); adapter->setScrollPos(mSetup.position); } } emit q->adapterChanged(); emit q->positionChanged(); if (adapter->canZoom()) { if (adapter->zoomToFit()) { emit q->zoomToFitChanged(true); } else if (adapter->zoomToFill()) { emit q->zoomToFillChanged(true); } else { emit q->zoomChanged(adapter->zoom()); } } if (adapter->rasterImageView()) { QObject::connect(adapter->rasterImageView(), SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)), q, SIGNAL(currentToolChanged(AbstractRasterImageViewTool*))); } } void setupLoadingIndicator() { mLoadingIndicator = new LoadingIndicator(q); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); floater->setChildWidget(mLoadingIndicator); } HudButton* createHudButton(const QString& text, const QString &iconName, bool showText) { HudButton* button = new HudButton; if (showText) { button->setText(text); } else { button->setToolTip(text); } button->setIcon(QIcon::fromTheme(iconName)); return button; } void setupHud() { HudButton* trashButton = createHudButton(i18nc("@info:tooltip", "Trash"), QStringLiteral("user-trash"), false); HudButton* deselectButton = createHudButton(i18nc("@action:button", "Deselect"), QStringLiteral("list-remove"), true); QGraphicsWidget* content = new QGraphicsWidget; QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(content); layout->addItem(trashButton); layout->addItem(deselectButton); mHud = new HudWidget(q); mHud->init(content, HudWidget::OptionNone); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); floater->setChildWidget(mHud); floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); QObject::connect(trashButton, SIGNAL(clicked()), q, SLOT(emitHudTrashClicked())); QObject::connect(deselectButton, SIGNAL(clicked()), q, SLOT(emitHudDeselectClicked())); mHud->hide(); } void setupBirdEyeView() { if (mBirdEyeView) { delete mBirdEyeView; } mBirdEyeView = new BirdEyeView(q); mBirdEyeView->setZValue(1); } void updateCaption() { if (!mCurrent) { return; } QString caption; Document::Ptr doc = mAdapter->document(); if (!doc) { emit q->captionUpdateRequested(caption); return; } caption = doc->url().fileName(); QSize size = doc->size(); if (size.isValid()) { caption += QStringLiteral(" - %1x%2") .arg(size.width()) .arg(size.height()); if (mAdapter->canZoom()) { int intZoom = qRound(mAdapter->zoom() * 100); caption += QStringLiteral(" - %1%") .arg(intZoom); } } emit q->captionUpdateRequested(caption); } void uncheckZoomToFit() { if (mAdapter->zoomToFit()) { mAdapter->setZoomToFit(false); } } void uncheckZoomToFill() { if (mAdapter->zoomToFill()) { mAdapter->setZoomToFill(false); } } void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1)) { uncheckZoomToFit(); uncheckZoomToFill(); zoom = qBound(q->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE); mAdapter->setZoom(zoom, center); } void updateZoomSnapValues() { qreal min = q->minimumZoom(); mZoomSnapValues.clear(); for (qreal zoom = MINSTEP; zoom > min; zoom *= MINSTEP) { mZoomSnapValues << zoom; } mZoomSnapValues << min; std::reverse(mZoomSnapValues.begin(), mZoomSnapValues.end()); for (qreal zoom = 1; zoom < MAXIMUM_ZOOM_VALUE; zoom *= MAXSTEP) { mZoomSnapValues << zoom; } mZoomSnapValues << MAXIMUM_ZOOM_VALUE; emit q->minimumZoomChanged(min); } void showLoadingIndicator() { if (!mLoadingIndicator) { setupLoadingIndicator(); } mLoadingIndicator->show(); mLoadingIndicator->setZValue(1); } void hideLoadingIndicator() { if (!mLoadingIndicator) { return; } mLoadingIndicator->hide(); } void resizeAdapterWidget() { QRectF rect = QRectF(QPointF(0, 0), q->boundingRect().size()); if (mCompareMode) { rect.adjust(COMPARE_MARGIN, COMPARE_MARGIN, -COMPARE_MARGIN, -COMPARE_MARGIN); } mAdapter->widget()->setGeometry(rect); } void fadeTo(qreal value) { if (mFadeAnimation.data()) { qreal endValue = mFadeAnimation.data()->endValue().toReal(); if (qFuzzyCompare(value, endValue)) { // Same end value, don't change the actual animation return; } } // Create a new fade animation QPropertyAnimation* anim = new QPropertyAnimation(mOpacityEffect, "opacity"); anim->setStartValue(mOpacityEffect->opacity()); anim->setEndValue(value); if (qFuzzyCompare(value, 1)) { QObject::connect(anim, SIGNAL(finished()), q, SLOT(slotFadeInFinished())); } QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged())); anim->setDuration(DocumentView::AnimDuration); mFadeAnimation = anim; emit q->isAnimatedChanged(); anim->start(QAbstractAnimation::DeleteWhenStopped); } bool canPan() const { if (!q->canZoom()) { return false; } const QSize zoomedImageSize = mDocument->size() * q->zoom(); const QSize viewPortSize = q->boundingRect().size().toSize(); const bool imageWiderThanViewport = zoomedImageSize.width() > viewPortSize.width(); const bool imageTallerThanViewport = zoomedImageSize.height() > viewPortSize.height(); return (imageWiderThanViewport || imageTallerThanViewport); } void setDragPixmap(const QPixmap& pix) { if (mDrag) { DragPixmapGenerator::DragPixmap dragPixmap = DragPixmapGenerator::generate({pix}, 1); mDrag->setPixmap(dragPixmap.pix); mDrag->setHotSpot(dragPixmap.hotSpot); } } void executeDrag() { if (mDrag) { if (mAdapter->imageView()) { mAdapter->imageView()->resetDragCursor(); } mDrag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction); } } void initDragThumbnailProvider() { mDragThumbnailProvider = new ThumbnailProvider(); QObject::connect(mDragThumbnailProvider, &ThumbnailProvider::thumbnailLoaded, q, &DocumentView::dragThumbnailLoaded); QObject::connect(mDragThumbnailProvider, &ThumbnailProvider::thumbnailLoadingFailed, q, &DocumentView::dragThumbnailLoadingFailed); } void startDragIfSensible() { if (q->document()->loadingState() == Document::LoadingFailed) { return; } if (q->currentTool()) { return; } if (mDrag) { mDrag->deleteLater(); } mDrag = new QDrag(q); const auto itemList = KFileItemList({q->document()->url()}); mDrag->setMimeData(MimeTypeUtils::selectionMimeData(itemList, MimeTypeUtils::DropTarget)); if (q->document()->isModified()) { setDragPixmap(QPixmap::fromImage(q->document()->image())); executeDrag(); } else { // Drag is triggered on success or failure of thumbnail generation if (mDragThumbnailProvider.isNull()) { initDragThumbnailProvider(); } mDragThumbnailProvider->appendItems(itemList); } } QPointF cursorPosition() { const QGraphicsScene* sc = q->scene(); if (sc) { const auto views = sc->views(); for (const QGraphicsView* view : views) { if (view->underMouse()) { return q->mapFromScene(view->mapFromGlobal(QCursor::pos())); } } } return QPointF(-1, -1); } }; DocumentView::DocumentView(QGraphicsScene* scene) : d(new DocumentViewPrivate) { setFlag(ItemIsFocusable); setFlag(ItemIsSelectable); setFlag(ItemClipsChildrenToShape); d->q = this; d->mLoadingIndicator = nullptr; d->mBirdEyeView = nullptr; d->mCurrent = false; d->mCompareMode = false; d->controlWheelAccumulatedDelta = 0; d->mDragStartPosition = QPointF(0, 0); d->mDrag = nullptr; // We use an opacity effect instead of using the opacity property directly, because the latter operates at // the painter level, which means if you draw multiple layers in paint(), all layers get the specified // opacity, resulting in all layers being visible when 0 < opacity < 1. // QGraphicsEffects on the other hand, operate after all painting is done, therefore 'flattening' all layers. // This is important for fade effects, where we don't want any background layers visible during the fade. d->mOpacityEffect = new QGraphicsOpacityEffect(this); d->mOpacityEffect->setOpacity(0); setGraphicsEffect(d->mOpacityEffect); scene->addItem(this); d->setupHud(); d->setCurrentAdapter(new EmptyAdapter); setAcceptDrops(true); connect(DocumentFactory::instance(), &DocumentFactory::documentChanged, this, [this]() { d->updateCaption(); }); } DocumentView::~DocumentView() { delete d->mDragThumbnailProvider; delete d->mDrag; delete d; } void DocumentView::createAdapterForDocument() { const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) { // Do not reuse for KIND_UNKNOWN: we may need to change the message LOG("Reusing current adapter"); return; } AbstractDocumentViewAdapter* adapter = nullptr; switch (documentKind) { case MimeTypeUtils::KIND_RASTER_IMAGE: adapter = new RasterImageViewAdapter; break; case MimeTypeUtils::KIND_SVG_IMAGE: adapter = new SvgViewAdapter; break; case MimeTypeUtils::KIND_VIDEO: adapter = new VideoViewAdapter; connect(adapter, SIGNAL(videoFinished()), SIGNAL(videoFinished())); break; case MimeTypeUtils::KIND_UNKNOWN: adapter = new MessageViewAdapter; static_cast(adapter)->setErrorMessage(i18n("Gwenview does not know how to display this kind of document")); break; default: qWarning() << "should not be called for documentKind=" << documentKind; adapter = new MessageViewAdapter; break; } d->setCurrentAdapter(adapter); } void DocumentView::openUrl(const QUrl &url, const DocumentView::Setup& setup) { if (d->mDocument) { if (url == d->mDocument->url()) { return; } disconnect(d->mDocument.data(), nullptr, this, nullptr); } d->mSetup = setup; d->mDocument = DocumentFactory::instance()->load(url); connect(d->mDocument.data(), SIGNAL(busyChanged(QUrl,bool)), SLOT(slotBusyChanged(QUrl,bool))); connect(d->mDocument.data(), &Document::modified, this, [this]() { d->updateZoomSnapValues(); }); if (d->mDocument->loadingState() < Document::KindDetermined) { MessageViewAdapter* messageViewAdapter = qobject_cast(d->mAdapter.data()); if (messageViewAdapter) { messageViewAdapter->setInfoMessage(QString()); } d->showLoadingIndicator(); connect(d->mDocument.data(), SIGNAL(kindDetermined(QUrl)), SLOT(finishOpenUrl())); } else { QMetaObject::invokeMethod(this, "finishOpenUrl", Qt::QueuedConnection); } d->setupBirdEyeView(); } void DocumentView::finishOpenUrl() { disconnect(d->mDocument.data(), SIGNAL(kindDetermined(QUrl)), this, SLOT(finishOpenUrl())); GV_RETURN_IF_FAIL(d->mDocument->loadingState() >= Document::KindDetermined); if (d->mDocument->loadingState() == Document::LoadingFailed) { slotLoadingFailed(); return; } createAdapterForDocument(); connect(d->mDocument.data(), SIGNAL(loadingFailed(QUrl)), SLOT(slotLoadingFailed())); d->mAdapter->setDocument(d->mDocument); d->updateCaption(); } void DocumentView::loadAdapterConfig() { d->mAdapter->loadConfig(); } RasterImageView* DocumentView::imageView() const { return d->mAdapter->rasterImageView(); } void DocumentView::slotCompleted() { d->hideLoadingIndicator(); d->updateCaption(); d->updateZoomSnapValues(); if (!d->mAdapter->zoomToFit() || !d->mAdapter->zoomToFill()) { qreal min = minimumZoom(); if (d->mAdapter->zoom() < min) { d->mAdapter->setZoom(min); } } emit completed(); } DocumentView::Setup DocumentView::setup() const { Setup setup; if (d->mAdapter->canZoom()) { setup.valid = true; setup.zoomToFit = zoomToFit(); setup.zoomToFill = zoomToFill(); if (!setup.zoomToFit && !setup.zoomToFill) { setup.zoom = zoom(); setup.position = position(); } } return setup; } void DocumentView::slotLoadingFailed() { d->hideLoadingIndicator(); MessageViewAdapter* adapter = new MessageViewAdapter; adapter->setDocument(d->mDocument); QString message = xi18n("Loading %1 failed", d->mDocument->url().fileName()); adapter->setErrorMessage(message, d->mDocument->errorString()); d->setCurrentAdapter(adapter); emit completed(); } bool DocumentView::canZoom() const { return d->mAdapter->canZoom(); } void DocumentView::setZoomToFit(bool on) { if (on == d->mAdapter->zoomToFit()) { return; } d->mAdapter->setZoomToFit(on); } void DocumentView::toggleZoomToFit() { const bool zoomToFitOn = d->mAdapter->zoomToFit(); d->mAdapter->setZoomToFit(!zoomToFitOn); if (zoomToFitOn) { d->setZoom(1., d->cursorPosition()); } } void DocumentView::setZoomToFill(bool on) { if (on == d->mAdapter->zoomToFill()) { return; } d->mAdapter->setZoomToFill(on, d->cursorPosition()); } void DocumentView::toggleZoomToFill() { const bool zoomToFillOn = d->mAdapter->zoomToFill(); d->mAdapter->setZoomToFill(!zoomToFillOn, d->cursorPosition()); if (zoomToFillOn) { d->setZoom(1., d->cursorPosition()); } } bool DocumentView::zoomToFit() const { return d->mAdapter->zoomToFit(); } bool DocumentView::zoomToFill() const { return d->mAdapter->zoomToFill(); } void DocumentView::zoomActualSize() { d->uncheckZoomToFit(); d->uncheckZoomToFill(); d->mAdapter->setZoom(1., d->cursorPosition()); } void DocumentView::zoomIn(QPointF center) { if (center == QPointF(-1, -1)) { center = d->cursorPosition(); } qreal currentZoom = d->mAdapter->zoom(); Q_FOREACH(qreal zoom, d->mZoomSnapValues) { if (zoom > currentZoom + REAL_DELTA) { d->setZoom(zoom, center); return; } } } void DocumentView::zoomOut(QPointF center) { if (center == QPointF(-1, -1)) { center = d->cursorPosition(); } qreal currentZoom = d->mAdapter->zoom(); QListIterator it(d->mZoomSnapValues); it.toBack(); while (it.hasPrevious()) { qreal zoom = it.previous(); if (zoom < currentZoom - REAL_DELTA) { d->setZoom(zoom, center); return; } } } void DocumentView::slotZoomChanged(qreal zoom) { d->updateCaption(); emit zoomChanged(zoom); } void DocumentView::setZoom(qreal zoom) { d->setZoom(zoom); } qreal DocumentView::zoom() const { return d->mAdapter->zoom(); } void DocumentView::resizeEvent(QGraphicsSceneResizeEvent *event) { d->resizeAdapterWidget(); d->updateZoomSnapValues(); QGraphicsWidget::resizeEvent(event); } void DocumentView::mousePressEvent(QGraphicsSceneMouseEvent* event) { QGraphicsWidget::mousePressEvent(event); if (d->mAdapter->canZoom() && event->button() == Qt::MiddleButton) { if (event->modifiers() == Qt::NoModifier) { toggleZoomToFit(); } else if (event->modifiers() == Qt::SHIFT) { toggleZoomToFill(); } } } void DocumentView::wheelEvent(QGraphicsSceneWheelEvent* event) { - if (d->mAdapter->canZoom() && event->modifiers() & Qt::ControlModifier) { - d->controlWheelAccumulatedDelta += event->delta(); - // Ctrl + wheel => zoom in or out - if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) { - zoomIn(event->pos()); - d->controlWheelAccumulatedDelta = 0; - } else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) { - zoomOut(event->pos()); - d->controlWheelAccumulatedDelta = 0; + if (d->mAdapter->canZoom()) { + if ((event->modifiers() & Qt::ControlModifier) || + (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Zoom + && event->modifiers() == Qt::NoModifier)) { + d->controlWheelAccumulatedDelta += event->delta(); + // Ctrl + wheel => zoom in or out + if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) { + zoomIn(event->pos()); + d->controlWheelAccumulatedDelta = 0; + } else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) { + zoomOut(event->pos()); + d->controlWheelAccumulatedDelta = 0; + } + return; } - return; } if (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse && event->modifiers() == Qt::NoModifier) { d->controlWheelAccumulatedDelta += event->delta(); // Browse with mouse wheel if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) { emit previousImageRequested(); d->controlWheelAccumulatedDelta = 0; } else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) { emit nextImageRequested(); d->controlWheelAccumulatedDelta = 0; } return; } // Scroll qreal dx = 0; // 16 = pixels for one line // 120: see QWheelEvent::delta() doc qreal dy = -qApp->wheelScrollLines() * 16 * event->delta() / 120; if (event->orientation() == Qt::Horizontal) { qSwap(dx, dy); } d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + QPointF(dx, dy)); } void DocumentView::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { // Filter out context menu if Ctrl is down to avoid showing it when // zooming out with Ctrl + Right button if (event->modifiers() != Qt::ControlModifier) { emit contextMenuRequested(); } } void DocumentView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { // Fill background manually, because setAutoFillBackground(true) fill with QPalette::Window, // but our palettes use QPalette::Base for the background color/texture painter->fillRect(rect(), palette().base()); // Selection indicator/highlight if (d->mCompareMode && d->mCurrent) { painter->save(); painter->setBrush(Qt::NoBrush); painter->setPen(QPen(palette().highlight().color(), 2)); painter->setRenderHint(QPainter::Antialiasing); const QRectF visibleRectF = mapRectFromItem(d->mAdapter->widget(), d->mAdapter->visibleDocumentRect()); // Round the point and size independently. This is different than calling toRect(), // and is necessary to keep consistent rects, otherwise the selection rect can be // drawn 1 pixel too big or small. const QRect visibleRect = QRect(visibleRectF.topLeft().toPoint(), visibleRectF.size().toSize()); const QRect selectionRect = visibleRect.adjusted(-1, -1, 1, 1); painter->drawRoundedRect(selectionRect, 3, 3); painter->restore(); } } void DocumentView::slotBusyChanged(const QUrl&, bool busy) { if (busy) { d->showLoadingIndicator(); } else { d->hideLoadingIndicator(); } } qreal DocumentView::minimumZoom() const { // There is no point zooming out less than zoomToFit, but make sure it does // not get too small either return qBound(qreal(0.001), d->mAdapter->computeZoomToFit(), qreal(1.)); } void DocumentView::setCompareMode(bool compare) { d->mCompareMode = compare; if (compare) { d->mHud->show(); d->mHud->setZValue(1); } else { d->mHud->hide(); } } void DocumentView::setCurrent(bool value) { d->mCurrent = value; if (value) { d->mAdapter->widget()->setFocus(); d->updateCaption(); } update(); } bool DocumentView::isCurrent() const { return d->mCurrent; } QPoint DocumentView::position() const { return d->mAdapter->scrollPos().toPoint(); } void DocumentView::setPosition(const QPoint& pos) { d->mAdapter->setScrollPos(pos); } Document::Ptr DocumentView::document() const { return d->mDocument; } QUrl DocumentView::url() const { Document::Ptr doc = d->mDocument; return doc ? doc->url() : QUrl(); } void DocumentView::emitHudDeselectClicked() { emit hudDeselectClicked(this); } void DocumentView::emitHudTrashClicked() { emit hudTrashClicked(this); } void DocumentView::emitFocused() { emit focused(this); } void DocumentView::setGeometry(const QRectF& rect) { QGraphicsWidget::setGeometry(rect); if (d->mBirdEyeView) { d->mBirdEyeView->slotZoomOrSizeChanged(); } } void DocumentView::moveTo(const QRect& rect) { if (d->mMoveAnimation) { d->mMoveAnimation.data()->setEndValue(rect); } else { setGeometry(rect); } } void DocumentView::moveToAnimated(const QRect& rect) { QPropertyAnimation* anim = new QPropertyAnimation(this, "geometry"); anim->setStartValue(geometry()); anim->setEndValue(rect); anim->setDuration(DocumentView::AnimDuration); connect(anim, SIGNAL(finished()), SIGNAL(isAnimatedChanged())); d->mMoveAnimation = anim; emit isAnimatedChanged(); anim->start(QAbstractAnimation::DeleteWhenStopped); } QPropertyAnimation* DocumentView::fadeIn() { d->fadeTo(1); return d->mFadeAnimation.data(); } void DocumentView::fadeOut() { d->fadeTo(0); } void DocumentView::slotFadeInFinished() { emit fadeInFinished(this); } bool DocumentView::isAnimated() const { return d->mMoveAnimation || d->mFadeAnimation; } bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) { if (event->type() == QEvent::GraphicsSceneMousePress) { const QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { d->mDragStartPosition = mouseEvent->pos(); } QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection); } else if (event->type() == QEvent::GraphicsSceneHoverMove) { if (d->mBirdEyeView) { d->mBirdEyeView->onMouseMoved(); } } else if (event->type() == QEvent::GraphicsSceneMouseMove) { const QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); const qreal dragDistance = (mouseEvent->pos() - d->mDragStartPosition).manhattanLength(); const qreal minDistanceToStartDrag = QGuiApplication::styleHints()->startDragDistance(); if (!d->canPan() && dragDistance >= minDistanceToStartDrag) { d->startDragIfSensible(); } } return false; } AbstractRasterImageViewTool* DocumentView::currentTool() const { return imageView() ? imageView()->currentTool() : nullptr; } int DocumentView::sortKey() const { return d->mSortKey; } void DocumentView::setSortKey(int sortKey) { d->mSortKey = sortKey; } void DocumentView::hideAndDeleteLater() { hide(); deleteLater(); } void DocumentView::setGraphicsEffectOpacity(qreal opacity) { d->mOpacityEffect->setOpacity(opacity); } void DocumentView::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dragEnterEvent(event); const auto urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); bool acceptDrag = !urls.isEmpty(); if (urls.size() == 1 && urls.first() == url()) { // Do not allow dragging a single image onto itself acceptDrag = false; } event->setAccepted(acceptDrag); } void DocumentView::dropEvent(QGraphicsSceneDragDropEvent* event) { QGraphicsWidget::dropEvent(event); // Since we're capturing drops in View mode, we only support one url const QUrl url = event->mimeData()->urls().first(); if (UrlUtils::urlIsDirectory(url)) { emit openDirUrlRequested(url); } else { emit openUrlRequested(url); } } void DocumentView::dragThumbnailLoaded(const KFileItem& item, const QPixmap& pix) { d->setDragPixmap(pix); d->executeDrag(); d->mDragThumbnailProvider->removeItems(KFileItemList({item})); } void DocumentView::dragThumbnailLoadingFailed(const KFileItem& item) { d->executeDrag(); d->mDragThumbnailProvider->removeItems(KFileItemList({item})); } } // namespace diff --git a/lib/gwenviewconfig.kcfg b/lib/gwenviewconfig.kcfg index fbdddd85..89d23c76 100644 --- a/lib/gwenviewconfig.kcfg +++ b/lib/gwenviewconfig.kcfg @@ -1,341 +1,342 @@ lib/sorting.h lib/zoommode.h lib/thumbnailactions.h lib/mousewheelbehavior.h lib/documentview/documentview.h lib/documentview/rasterimageview.h lib/print/printoptionspage.h lib/renderingintent.h General.Name,General.ImageSize,Exif.Photo.ExposureTime,Exif.Photo.Flash true true false 100 true 0.5 The percentage of memory used by Gwenview before it warns the user and suggest saving changes. new A list of filename extensions Gwenview should not try to load. We exclude *.new as well because this is the extension used for temporary files by KSaveFile. false Horizontal 1 false false information ThumbnailActions::AllButtons General.Name,Exif.Image.DateTime true false AbstractImageView::AlphaBackgroundNone #ffffff + MouseWheelBehavior::Scroll false true 350, 100 DocumentView::SoftwareAnimation ZoomMode::Autofit Defines what happens when going to image B after having zoomed in on an area of image A. If set to Autofit, image B is zoomed out to fit the screen. If set to KeepSame, all images share the same zoom and position: image B is set to the same zoom parameters as image A (and if these are changed, image A will then be displayed with the updated zoom and position). If set to Individual, all images remember their own zoom and position: image B is initially set to the same zoom parameters as image A, but will then remember its own zoom and position (if these are changed, image A will NOT be displayed with the updated zoom and position). RenderingIntent::Perceptual Defines how colors are rendered when your display uses an ICC color profile and an image has colors that do not fit within the profile's color gamut. "Perceptual" will scale the colors of the entire image so that they all fit within the display's capabilities. "Relative" will squash only the colors that cannot be displayed, and leave the other colors alone. 128 3./2. false Sorting::Name false 1 true Qt::AlignHCenter | Qt::AlignVCenter PrintOptionsPage::ScaleToPage false 15.0 10.0 PrintOptionsPage::Centimeters true false true false false 5.0 24 false false -1 0 0 true true false diff --git a/lib/mousewheelbehavior.h b/lib/mousewheelbehavior.h index c91029a9..a359da48 100644 --- a/lib/mousewheelbehavior.h +++ b/lib/mousewheelbehavior.h @@ -1,44 +1,45 @@ // 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. */ #ifndef MOUSEWHEELBEHAVIOR_H #define MOUSEWHEELBEHAVIOR_H // Qt // KDE // Local namespace Gwenview { namespace MouseWheelBehavior { enum Enum { Scroll, - Browse + Browse, + Zoom }; } // namespace MouseWheelBehavior } // namespace Gwenview #endif /* MOUSEWHEELBEHAVIOR_H */