diff --git a/lib/documentview/abstractdocumentviewadapter.h b/lib/documentview/abstractdocumentviewadapter.h --- a/lib/documentview/abstractdocumentviewadapter.h +++ b/lib/documentview/abstractdocumentviewadapter.h @@ -39,7 +39,7 @@ namespace Gwenview { -class ImageView; +class AbstractImageView; class RasterImageView; /** @@ -60,7 +60,7 @@ virtual MimeTypeUtils::Kind kind() const = 0; - virtual ImageView* imageView() const + virtual AbstractImageView* imageView() const { return 0; } diff --git a/lib/documentview/abstractimageview.h b/lib/documentview/abstractimageview.h --- a/lib/documentview/abstractimageview.h +++ b/lib/documentview/abstractimageview.h @@ -105,6 +105,8 @@ void applyPendingScrollPos(); + void resetDrag(); + public Q_SLOTS: void updateCursor(); diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp --- a/lib/documentview/abstractimageview.cpp +++ b/lib/documentview/abstractimageview.cpp @@ -596,4 +596,10 @@ d->adjustScrollPos(); } +void AbstractImageView::resetDrag() +{ + d->mLastDragPos = QPointF(); + updateCursor(); +} + } // namespace diff --git a/lib/documentview/documentview.h b/lib/documentview/documentview.h --- a/lib/documentview/documentview.h +++ b/lib/documentview/documentview.h @@ -228,6 +228,9 @@ void slotFadeInFinished(); + void dragThumbnailLoaded(const KFileItem&, const QPixmap&, const QSize&, qulonglong); + void dragThumbnailLoadingFailed(const KFileItem&); + private: friend struct DocumentViewPrivate; DocumentViewPrivate* const d; diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp --- a/lib/documentview/documentview.cpp +++ b/lib/documentview/documentview.cpp @@ -38,10 +38,13 @@ #include #include #include +#include #include +#include // KDE #include +#include // Local #include @@ -62,6 +65,8 @@ #include #include #include +#include +#include namespace Gwenview { @@ -105,6 +110,10 @@ bool mCompareMode; int controlWheelAccumulatedDelta; + QPointF mDragStartPosition; + QPointer mThumbnailProvider; + QPointer mViewDrag; + void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); @@ -338,6 +347,65 @@ q->isAnimatedChanged(); anim->start(QAbstractAnimation::DeleteWhenStopped); } + + bool canPan() const + { + // Can't pan if we can't zoom + if (!q->canZoom()) { + return false; + } + + // Calculate whether image is larger than the viewport - if it is, panning is possible + const QSize zoomedImageSize = mDocument->size() * q->zoom(); + const QSize viewPortSize = q->boundingRect().size().toSize(); + return (zoomedImageSize.width() > viewPortSize.width() || zoomedImageSize.height() > viewPortSize.height()); + } + + void setDragPixmap(const QPixmap& pix) + { + DragPixmapGenerator::DragPixmap dragPixmap = DragPixmapGenerator::generate({pix}, 1); + mViewDrag->setPixmap(dragPixmap.pix); + mViewDrag->setHotSpot(dragPixmap.hotSpot); + } + + void executeDrag() + { + mViewDrag->exec(); + if (mAdapter->imageView()) { + mAdapter->imageView()->resetDrag(); + } + } + + void startDragIfSensible() + { + if (q->document()->loadingState() == Document::LoadingFailed) { + return; + } + + if (q->currentTool()) { + return; + } + + if (mViewDrag) { + mViewDrag->deleteLater(); + } + mViewDrag = new QDrag(q); + const QUrl url = q->document()->url(); + const KFileItem item = KFileItem(url, MimeTypeUtils::urlMimeType(url)); + const KFileItemList itemList = KFileItemList(QList {item}); + mViewDrag->setMimeData(MimeTypeUtils::selectionMimeData(itemList)); + + // Set the drag pixmap and execute drag + if (q->document()->isModified()) { + // Skip generating thumbnail from file and use edited image + setDragPixmap(QPixmap::fromImage(q->document()->image())); + executeDrag(); + } else { + // Generate thumbnail from the file item + // Note: drag is executed on success or failure + mThumbnailProvider->appendItems(itemList); + } + } }; DocumentView::DocumentView(QGraphicsScene* scene) @@ -353,6 +421,16 @@ d->mCurrent = false; d->mCompareMode = false; d->controlWheelAccumulatedDelta = 0; + d->mDragStartPosition = QPointF(0, 0); + d->mViewDrag = nullptr; + + // This provides thumbnail generation for drag operations + // We use it to generate only one thumbnail at a time + d->mThumbnailProvider = new ThumbnailProvider(); + connect(d->mThumbnailProvider, &ThumbnailProvider::thumbnailLoaded, + this, &DocumentView::dragThumbnailLoaded); + connect(d->mThumbnailProvider, &ThumbnailProvider::thumbnailLoadingFailed, + this, &DocumentView::dragThumbnailLoadingFailed); // 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 @@ -373,6 +451,8 @@ DocumentView::~DocumentView() { + delete d->mThumbnailProvider; + delete d->mViewDrag; delete d; } @@ -796,11 +876,21 @@ bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) { if (event->type() == QEvent::GraphicsSceneMousePress) { + 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) { + QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); + const qreal dragDistance = (mouseEvent->pos() - d->mDragStartPosition).manhattanLength(); + if (!d->canPan() && dragDistance >= QGuiApplication::styleHints()->startDragDistance()) { + d->startDragIfSensible(); + } } return false; } @@ -849,4 +939,22 @@ } } +void DocumentView::dragThumbnailLoaded(const KFileItem& item, const QPixmap& pix, const QSize&, qulonglong) +{ + if (d->mViewDrag) { + d->setDragPixmap(pix); + d->executeDrag(); + } + d->mThumbnailProvider->removeItems(KFileItemList(QList {item})); +} + +void DocumentView::dragThumbnailLoadingFailed(const KFileItem& item) +{ + // Better to execute drag without pixmap than not execute at all + if (d->mViewDrag) { + d->executeDrag(); + } + d->mThumbnailProvider->removeItems(KFileItemList(QList {item})); +} + } // namespace diff --git a/lib/documentview/rasterimageviewadapter.h b/lib/documentview/rasterimageviewadapter.h --- a/lib/documentview/rasterimageviewadapter.h +++ b/lib/documentview/rasterimageviewadapter.h @@ -78,6 +78,7 @@ virtual void loadConfig() Q_DECL_OVERRIDE; virtual RasterImageView* rasterImageView() const Q_DECL_OVERRIDE; + virtual AbstractImageView* imageView() const override; virtual QPointF scrollPos() const Q_DECL_OVERRIDE; virtual void setScrollPos(const QPointF& pos) Q_DECL_OVERRIDE; diff --git a/lib/documentview/rasterimageviewadapter.cpp b/lib/documentview/rasterimageviewadapter.cpp --- a/lib/documentview/rasterimageviewadapter.cpp +++ b/lib/documentview/rasterimageviewadapter.cpp @@ -147,6 +147,11 @@ return d->mView; } +AbstractImageView* RasterImageViewAdapter::imageView() const +{ + return d->mView; +} + QPointF RasterImageViewAdapter::scrollPos() const { return d->mView->scrollPos(); diff --git a/lib/documentview/svgviewadapter.h b/lib/documentview/svgviewadapter.h --- a/lib/documentview/svgviewadapter.h +++ b/lib/documentview/svgviewadapter.h @@ -114,6 +114,8 @@ virtual QRectF visibleDocumentRect() const override; + virtual AbstractImageView* imageView() const override; + private: SvgViewAdapterPrivate* const d; }; diff --git a/lib/documentview/svgviewadapter.cpp b/lib/documentview/svgviewadapter.cpp --- a/lib/documentview/svgviewadapter.cpp +++ b/lib/documentview/svgviewadapter.cpp @@ -261,4 +261,9 @@ return QRectF(d->mView->imageOffset(), d->mView->visibleImageSize()); } +AbstractImageView* SvgViewAdapter::imageView() const +{ + return d->mView; +} + } // namespace