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,8 @@ void slotFadeInFinished(); + void slotGotDragThumbnail(const KFileItem&, const QPixmap&); + 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,14 @@ #include #include #include +#include #include +#include // KDE #include +#include +#include // Local #include @@ -62,6 +66,7 @@ #include #include #include +#include namespace Gwenview { @@ -105,6 +110,10 @@ bool mCompareMode; int controlWheelAccumulatedDelta; + QPixmap mDragThumbnail; + QPointF mDragStartPosition; + const int mDragPixmapMaxPixelSize = 100; + void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); @@ -338,6 +347,77 @@ 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 generateDragThumbnail() + { + // We use KIO so we get a nice thumbnail for all file types including videos + const QUrl url = q->document()->url(); + const KFileItem item = KFileItem(url, MimeTypeUtils::urlMimeType(url)); + KFileItemList itemList; + itemList << item; + const QStringList availPlugins = KIO::PreviewJob::availablePlugins(); + KIO::Job* job = KIO::filePreview(itemList, QSize(mDragPixmapMaxPixelSize, mDragPixmapMaxPixelSize), &availPlugins); + KIO::Job::connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), + q, SLOT(slotGotDragThumbnail(KFileItem,QPixmap))); + job->start(); + } + + DragPixmapGenerator::DragPixmap generateDragPixmap() { + QList pixList; + // If image is modified, generate a new thumbnail + if (q->document()->isModified()) { + const QSize size = QSize(mDragPixmapMaxPixelSize, mDragPixmapMaxPixelSize); + pixList << QPixmap::fromImage(q->document()->image().scaled(size, Qt::KeepAspectRatio)); + } else { + pixList << mDragThumbnail; + } + + return DragPixmapGenerator::generate(pixList, pixList.count()); + } + + void startDragIfSensible() + { + if (q->document()->loadingState() == Document::LoadingFailed) { + return; + } + + if (q->currentTool()) { + return; + } + + // Is the image pannable (actual size > viewport size)? +// const QSize zoomedImageSize = mDocument->size() * q->zoom(); +// const QSize viewPortSize = q->boundingRect().size().toSize(); +// if (q->canZoom() && (zoomedImageSize.width() > viewPortSize.width() || zoomedImageSize.height() > viewPortSize.height())) { +// return; +// } + + QDrag* drag = new QDrag(q); + // Set mime data + const QUrl url = q->document()->url(); + const KFileItem item = KFileItem(url, MimeTypeUtils::urlMimeType(url)); + const KFileItemList itemList = KFileItemList(QList {item}); + drag->setMimeData(MimeTypeUtils::selectionMimeData(itemList)); + // Set drag pixmap + DragPixmapGenerator::DragPixmap dragPixmap = generateDragPixmap(); + drag->setPixmap(dragPixmap.pix); + drag->setHotSpot(dragPixmap.hotSpot); + + drag->exec(Qt::CopyAction); + } }; DocumentView::DocumentView(QGraphicsScene* scene) @@ -353,6 +433,8 @@ d->mCurrent = false; d->mCompareMode = false; d->controlWheelAccumulatedDelta = 0; + d->mDragStartPosition = QPointF(0, 0); + d->mDragThumbnail = QPixmap(); // 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 @@ -376,6 +458,11 @@ delete d; } +void DocumentView::slotGotDragThumbnail(const KFileItem& /*item*/, const QPixmap& pixmap) +{ + d->mDragThumbnail = pixmap; +} + void DocumentView::createAdapterForDocument() { const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); @@ -455,6 +542,8 @@ SLOT(slotLoadingFailed())); d->mAdapter->setDocument(d->mDocument); d->updateCaption(); + + d->generateDragThumbnail(); } void DocumentView::loadAdapterConfig() @@ -796,11 +885,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; }