Changeset View
Standalone View
lib/documentview/documentview.cpp
Show All 32 Lines | |||||
33 | #include <QGraphicsSceneWheelEvent> | 33 | #include <QGraphicsSceneWheelEvent> | ||
34 | #include <QGraphicsOpacityEffect> | 34 | #include <QGraphicsOpacityEffect> | ||
35 | #include <QPainter> | 35 | #include <QPainter> | ||
36 | #include <QPropertyAnimation> | 36 | #include <QPropertyAnimation> | ||
37 | #include <QPointer> | 37 | #include <QPointer> | ||
38 | #include <QDebug> | 38 | #include <QDebug> | ||
39 | #include <QIcon> | 39 | #include <QIcon> | ||
40 | #include <QUrl> | 40 | #include <QUrl> | ||
41 | #include <QDrag> | ||||
41 | #include <QMimeData> | 42 | #include <QMimeData> | ||
43 | #include <QStyleHints> | ||||
42 | 44 | | |||
43 | // KDE | 45 | // KDE | ||
44 | #include <KLocalizedString> | 46 | #include <KLocalizedString> | ||
47 | #include <KFileItem> | ||||
48 | #include <KIO/PreviewJob> | ||||
45 | 49 | | |||
46 | // Local | 50 | // Local | ||
47 | #include <lib/document/document.h> | 51 | #include <lib/document/document.h> | ||
48 | #include <lib/document/documentfactory.h> | 52 | #include <lib/document/documentfactory.h> | ||
49 | #include <lib/documentview/abstractrasterimageviewtool.h> | 53 | #include <lib/documentview/abstractrasterimageviewtool.h> | ||
50 | #include <lib/documentview/birdeyeview.h> | 54 | #include <lib/documentview/birdeyeview.h> | ||
51 | #include <lib/documentview/loadingindicator.h> | 55 | #include <lib/documentview/loadingindicator.h> | ||
52 | #include <lib/documentview/messageviewadapter.h> | 56 | #include <lib/documentview/messageviewadapter.h> | ||
53 | #include <lib/documentview/rasterimageview.h> | 57 | #include <lib/documentview/rasterimageview.h> | ||
54 | #include <lib/documentview/rasterimageviewadapter.h> | 58 | #include <lib/documentview/rasterimageviewadapter.h> | ||
55 | #include <lib/documentview/svgviewadapter.h> | 59 | #include <lib/documentview/svgviewadapter.h> | ||
56 | #include <lib/documentview/videoviewadapter.h> | 60 | #include <lib/documentview/videoviewadapter.h> | ||
57 | #include <lib/hud/hudbutton.h> | 61 | #include <lib/hud/hudbutton.h> | ||
58 | #include <lib/hud/hudwidget.h> | 62 | #include <lib/hud/hudwidget.h> | ||
59 | #include <lib/graphicswidgetfloater.h> | 63 | #include <lib/graphicswidgetfloater.h> | ||
60 | #include <lib/gvdebug.h> | 64 | #include <lib/gvdebug.h> | ||
61 | #include <lib/gwenviewconfig.h> | 65 | #include <lib/gwenviewconfig.h> | ||
62 | #include <lib/mimetypeutils.h> | 66 | #include <lib/mimetypeutils.h> | ||
63 | #include <lib/signalblocker.h> | 67 | #include <lib/signalblocker.h> | ||
64 | #include <lib/urlutils.h> | 68 | #include <lib/urlutils.h> | ||
69 | #include <lib/thumbnailview/dragpixmapgenerator.h> | ||||
65 | 70 | | |||
66 | namespace Gwenview | 71 | namespace Gwenview | ||
67 | { | 72 | { | ||
68 | 73 | | |||
69 | #undef ENABLE_LOG | 74 | #undef ENABLE_LOG | ||
70 | #undef LOG | 75 | #undef LOG | ||
71 | //#define ENABLE_LOG | 76 | //#define ENABLE_LOG | ||
72 | #ifdef ENABLE_LOG | 77 | #ifdef ENABLE_LOG | ||
Show All 27 Lines | 94 | { | |||
100 | QScopedPointer<AbstractDocumentViewAdapter> mAdapter; | 105 | QScopedPointer<AbstractDocumentViewAdapter> mAdapter; | ||
101 | QList<qreal> mZoomSnapValues; | 106 | QList<qreal> mZoomSnapValues; | ||
102 | Document::Ptr mDocument; | 107 | Document::Ptr mDocument; | ||
103 | DocumentView::Setup mSetup; | 108 | DocumentView::Setup mSetup; | ||
104 | bool mCurrent; | 109 | bool mCurrent; | ||
105 | bool mCompareMode; | 110 | bool mCompareMode; | ||
106 | int controlWheelAccumulatedDelta; | 111 | int controlWheelAccumulatedDelta; | ||
107 | 112 | | |||
113 | QPixmap mDragThumbnail; | ||||
114 | QPointF mDragStartPosition; | ||||
115 | const int mDragPixmapMaxPixelSize = 100; | ||||
116 | | ||||
108 | void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) | 117 | void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) | ||
109 | { | 118 | { | ||
110 | Q_ASSERT(adapter); | 119 | Q_ASSERT(adapter); | ||
111 | mAdapter.reset(adapter); | 120 | mAdapter.reset(adapter); | ||
112 | 121 | | |||
113 | adapter->widget()->setParentItem(q); | 122 | adapter->widget()->setParentItem(q); | ||
114 | resizeAdapterWidget(); | 123 | resizeAdapterWidget(); | ||
115 | 124 | | |||
▲ Show 20 Lines • Show All 217 Lines • ▼ Show 20 Line(s) | 341 | QObject::connect(anim, SIGNAL(finished()), | |||
333 | q, SLOT(slotFadeInFinished())); | 342 | q, SLOT(slotFadeInFinished())); | ||
334 | } | 343 | } | ||
335 | QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged())); | 344 | QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged())); | ||
336 | anim->setDuration(DocumentView::AnimDuration); | 345 | anim->setDuration(DocumentView::AnimDuration); | ||
337 | mFadeAnimation = anim; | 346 | mFadeAnimation = anim; | ||
338 | q->isAnimatedChanged(); | 347 | q->isAnimatedChanged(); | ||
339 | anim->start(QAbstractAnimation::DeleteWhenStopped); | 348 | anim->start(QAbstractAnimation::DeleteWhenStopped); | ||
340 | } | 349 | } | ||
350 | | ||||
351 | bool canPan() const | ||||
352 | { | ||||
353 | // Can't pan if we can't zoom | ||||
354 | if (!q->canZoom()) { | ||||
355 | return false; | ||||
356 | } | ||||
357 | | ||||
358 | // Calculate whether image is larger than the viewport - if it is, panning is possible | ||||
359 | const QSize zoomedImageSize = mDocument->size() * q->zoom(); | ||||
360 | const QSize viewPortSize = q->boundingRect().size().toSize(); | ||||
361 | return (zoomedImageSize.width() > viewPortSize.width() || zoomedImageSize.height() > viewPortSize.height()); | ||||
362 | } | ||||
363 | | ||||
364 | void generateDragThumbnail() | ||||
365 | { | ||||
366 | // We use KIO so we get a nice thumbnail for all file types including videos | ||||
367 | const QUrl url = q->document()->url(); | ||||
368 | const KFileItem item = KFileItem(url, MimeTypeUtils::urlMimeType(url)); | ||||
369 | KFileItemList itemList; | ||||
370 | itemList << item; | ||||
371 | const QStringList availPlugins = KIO::PreviewJob::availablePlugins(); | ||||
372 | KIO::Job* job = KIO::filePreview(itemList, QSize(mDragPixmapMaxPixelSize, mDragPixmapMaxPixelSize), &availPlugins); | ||||
373 | KIO::Job::connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)), | ||||
374 | q, SLOT(slotGotDragThumbnail(KFileItem,QPixmap))); | ||||
375 | job->start(); | ||||
376 | } | ||||
huoni: Not the most elegant solution to the cursor issue, but it's not too bad. | |||||
377 | | ||||
378 | DragPixmapGenerator::DragPixmap generateDragPixmap() { | ||||
379 | QList<QPixmap> pixList; | ||||
380 | // If image is modified, generate a new thumbnail | ||||
381 | if (q->document()->isModified()) { | ||||
382 | const QSize size = QSize(mDragPixmapMaxPixelSize, mDragPixmapMaxPixelSize); | ||||
383 | pixList << QPixmap::fromImage(q->document()->image().scaled(size, Qt::KeepAspectRatio)); | ||||
384 | } else { | ||||
385 | pixList << mDragThumbnail; | ||||
386 | } | ||||
387 | | ||||
388 | return DragPixmapGenerator::generate(pixList, pixList.count()); | ||||
389 | } | ||||
390 | | ||||
Not entirely sure if this is necessary, but from what I've read, the object referenced isn't destroyed when the point changes. huoni: Not entirely sure if this is necessary, but from what I've read, the object referenced isn't… | |||||
Not sure how to trigger this, but probably makes sense. However, why not delete mDrag? I fear that deleteLater() might execute after you already set the new object. rkflx: Not sure how to trigger this, but probably makes sense. However, why not `delete mDrag`? I fear… | |||||
delete is blocking I believe, I didn't want to delay the drag. I don't think deleteLater is a problem with QPointer, the new drag object will replace the old one, and the old one will get deleted, ...later. huoni: `delete` is blocking I believe, I didn't want to delay the drag. I don't think `deleteLater` is… | |||||
Ah, of course, I think I got confused by the (possibly incorrect) assumption that there can only be a single drag object at any point in time. deleteLater is probably fine here, although it would be interesting to measure whether posting to the event loop is really faster than simply calling delete (not that it would make any difference to the user here). rkflx: Ah, of course, I think I got confused by the (possibly incorrect) assumption that there can… | |||||
391 | void startDragIfSensible() | ||||
392 | { | ||||
393 | if (q->document()->loadingState() == Document::LoadingFailed) { | ||||
394 | return; | ||||
395 | } | ||||
Found a much more concise way to express this: const auto itemList = KFileItemList({q->document()->url()}); rkflx: Found a much more concise way to express this:
const auto itemList = KFileItemList({q… | |||||
huoni: Nice one. | |||||
396 | | ||||
397 | if (q->currentTool()) { | ||||
398 | return; | ||||
399 | } | ||||
400 | | ||||
401 | // Is the image pannable (actual size > viewport size)? | ||||
402 | // const QSize zoomedImageSize = mDocument->size() * q->zoom(); | ||||
403 | // const QSize viewPortSize = q->boundingRect().size().toSize(); | ||||
404 | // if (q->canZoom() && (zoomedImageSize.width() > viewPortSize.width() || zoomedImageSize.height() > viewPortSize.height())) { | ||||
405 | // return; | ||||
406 | // } | ||||
rkflx: ↑ Nothing to see here, keep reading ;) | |||||
407 | | ||||
408 | QDrag* drag = new QDrag(q); | ||||
409 | // Set mime data | ||||
410 | const QUrl url = q->document()->url(); | ||||
411 | const KFileItem item = KFileItem(url, MimeTypeUtils::urlMimeType(url)); | ||||
412 | const KFileItemList itemList = KFileItemList(QList<KFileItem> {item}); | ||||
413 | drag->setMimeData(MimeTypeUtils::selectionMimeData(itemList)); | ||||
414 | // Set drag pixmap | ||||
415 | DragPixmapGenerator::DragPixmap dragPixmap = generateDragPixmap(); | ||||
416 | drag->setPixmap(dragPixmap.pix); | ||||
417 | drag->setHotSpot(dragPixmap.hotSpot); | ||||
418 | | ||||
419 | drag->exec(Qt::CopyAction); | ||||
420 | } | ||||
341 | }; | 421 | }; | ||
342 | 422 | | |||
343 | DocumentView::DocumentView(QGraphicsScene* scene) | 423 | DocumentView::DocumentView(QGraphicsScene* scene) | ||
344 | : d(new DocumentViewPrivate) | 424 | : d(new DocumentViewPrivate) | ||
345 | { | 425 | { | ||
346 | setFlag(ItemIsFocusable); | 426 | setFlag(ItemIsFocusable); | ||
347 | setFlag(ItemIsSelectable); | 427 | setFlag(ItemIsSelectable); | ||
348 | setFlag(ItemClipsChildrenToShape); | 428 | setFlag(ItemClipsChildrenToShape); | ||
349 | 429 | | |||
350 | d->q = this; | 430 | d->q = this; | ||
351 | d->mLoadingIndicator = 0; | 431 | d->mLoadingIndicator = 0; | ||
352 | d->mBirdEyeView = 0; | 432 | d->mBirdEyeView = 0; | ||
353 | d->mCurrent = false; | 433 | d->mCurrent = false; | ||
354 | d->mCompareMode = false; | 434 | d->mCompareMode = false; | ||
355 | d->controlWheelAccumulatedDelta = 0; | 435 | d->controlWheelAccumulatedDelta = 0; | ||
436 | d->mDragStartPosition = QPointF(0, 0); | ||||
437 | d->mDragThumbnail = QPixmap(); | ||||
356 | 438 | | |||
357 | // We use an opacity effect instead of using the opacity property directly, because the latter operates at | 439 | // We use an opacity effect instead of using the opacity property directly, because the latter operates at | ||
358 | // the painter level, which means if you draw multiple layers in paint(), all layers get the specified | 440 | // the painter level, which means if you draw multiple layers in paint(), all layers get the specified | ||
359 | // opacity, resulting in all layers being visible when 0 < opacity < 1. | 441 | // opacity, resulting in all layers being visible when 0 < opacity < 1. | ||
360 | // QGraphicsEffects on the other hand, operate after all painting is done, therefore 'flattening' all layers. | 442 | // QGraphicsEffects on the other hand, operate after all painting is done, therefore 'flattening' all layers. | ||
361 | // This is important for fade effects, where we don't want any background layers visible during the fade. | 443 | // This is important for fade effects, where we don't want any background layers visible during the fade. | ||
362 | d->mOpacityEffect = new QGraphicsOpacityEffect(this); | 444 | d->mOpacityEffect = new QGraphicsOpacityEffect(this); | ||
363 | d->mOpacityEffect->setOpacity(0); | 445 | d->mOpacityEffect->setOpacity(0); | ||
I felt having a single ThumbnailProvider was better than repeatedly creating and destroying one. However, the downside is we always create one even if the user doesn't drag. huoni: I felt having a single `ThumbnailProvider` was better than repeatedly creating and destroying… | |||||
Why not combine the advantages of both options, i.e. have a single object, and create it only on demand? :D rkflx: Why not combine the advantages of both options, i.e. have a single object, and create it only… | |||||
364 | setGraphicsEffect(d->mOpacityEffect); | 446 | setGraphicsEffect(d->mOpacityEffect); | ||
365 | 447 | | |||
366 | scene->addItem(this); | 448 | scene->addItem(this); | ||
367 | 449 | | |||
368 | d->setupHud(); | 450 | d->setupHud(); | ||
369 | d->setCurrentAdapter(new EmptyAdapter); | 451 | d->setCurrentAdapter(new EmptyAdapter); | ||
370 | 452 | | |||
371 | setAcceptDrops(true); | 453 | setAcceptDrops(true); | ||
372 | } | 454 | } | ||
373 | 455 | | |||
374 | DocumentView::~DocumentView() | 456 | DocumentView::~DocumentView() | ||
375 | { | 457 | { | ||
376 | delete d; | 458 | delete d; | ||
377 | } | 459 | } | ||
378 | 460 | | |||
461 | void DocumentView::slotGotDragThumbnail(const KFileItem& /*item*/, const QPixmap& pixmap) | ||||
462 | { | ||||
463 | d->mDragThumbnail = pixmap; | ||||
464 | } | ||||
465 | | ||||
379 | void DocumentView::createAdapterForDocument() | 466 | void DocumentView::createAdapterForDocument() | ||
380 | { | 467 | { | ||
381 | const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); | 468 | const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); | ||
382 | if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) { | 469 | if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) { | ||
383 | // Do not reuse for KIND_UNKNOWN: we may need to change the message | 470 | // Do not reuse for KIND_UNKNOWN: we may need to change the message | ||
384 | LOG("Reusing current adapter"); | 471 | LOG("Reusing current adapter"); | ||
385 | return; | 472 | return; | ||
386 | } | 473 | } | ||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Line(s) | 535 | if (d->mDocument->loadingState() == Document::LoadingFailed) { | |||
450 | return; | 537 | return; | ||
451 | } | 538 | } | ||
452 | createAdapterForDocument(); | 539 | createAdapterForDocument(); | ||
453 | 540 | | |||
454 | connect(d->mDocument.data(), SIGNAL(loadingFailed(QUrl)), | 541 | connect(d->mDocument.data(), SIGNAL(loadingFailed(QUrl)), | ||
455 | SLOT(slotLoadingFailed())); | 542 | SLOT(slotLoadingFailed())); | ||
456 | d->mAdapter->setDocument(d->mDocument); | 543 | d->mAdapter->setDocument(d->mDocument); | ||
457 | d->updateCaption(); | 544 | d->updateCaption(); | ||
545 | | ||||
546 | d->generateDragThumbnail(); | ||||
458 | } | 547 | } | ||
459 | 548 | | |||
460 | void DocumentView::loadAdapterConfig() | 549 | void DocumentView::loadAdapterConfig() | ||
461 | { | 550 | { | ||
462 | d->mAdapter->loadConfig(); | 551 | d->mAdapter->loadConfig(); | ||
463 | } | 552 | } | ||
464 | 553 | | |||
465 | RasterImageView* DocumentView::imageView() const | 554 | RasterImageView* DocumentView::imageView() const | ||
▲ Show 20 Lines • Show All 325 Lines • ▼ Show 20 Line(s) | |||||
791 | bool DocumentView::isAnimated() const | 880 | bool DocumentView::isAnimated() const | ||
792 | { | 881 | { | ||
793 | return d->mMoveAnimation || d->mFadeAnimation; | 882 | return d->mMoveAnimation || d->mFadeAnimation; | ||
794 | } | 883 | } | ||
795 | 884 | | |||
796 | bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) | 885 | bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) | ||
797 | { | 886 | { | ||
798 | if (event->type() == QEvent::GraphicsSceneMousePress) { | 887 | if (event->type() == QEvent::GraphicsSceneMousePress) { | ||
888 | QGraphicsSceneMouseEvent* mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event); | ||||
rkflx: `const` | |||||
889 | if (mouseEvent->button() == Qt::LeftButton) { | ||||
890 | d->mDragStartPosition = mouseEvent->pos(); | ||||
891 | } | ||||
799 | QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection); | 892 | QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection); | ||
800 | } else if (event->type() == QEvent::GraphicsSceneHoverMove) { | 893 | } else if (event->type() == QEvent::GraphicsSceneHoverMove) { | ||
801 | if (d->mBirdEyeView) { | 894 | if (d->mBirdEyeView) { | ||
802 | d->mBirdEyeView->onMouseMoved(); | 895 | d->mBirdEyeView->onMouseMoved(); | ||
803 | } | 896 | } | ||
897 | } else if (event->type() == QEvent::GraphicsSceneMouseMove) { | ||||
898 | QGraphicsSceneMouseEvent* mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event); | ||||
rkflx: `const` | |||||
899 | const qreal dragDistance = (mouseEvent->pos() - d->mDragStartPosition).manhattanLength(); | ||||
900 | if (!d->canPan() && dragDistance >= QGuiApplication::styleHints()->startDragDistance()) { | ||||
901 | d->startDragIfSensible(); | ||||
902 | } | ||||
804 | } | 903 | } | ||
805 | return false; | 904 | return false; | ||
806 | } | 905 | } | ||
807 | 906 | | |||
808 | AbstractRasterImageViewTool* DocumentView::currentTool() const | 907 | AbstractRasterImageViewTool* DocumentView::currentTool() const | ||
809 | { | 908 | { | ||
810 | return imageView() ? imageView()->currentTool() : 0; | 909 | return imageView() ? imageView()->currentTool() : 0; | ||
811 | } | 910 | } | ||
Show All 32 Lines | 940 | { | |||
844 | const QUrl url = event->mimeData()->urls().first(); | 943 | const QUrl url = event->mimeData()->urls().first(); | ||
845 | if (UrlUtils::urlIsDirectory(url)) { | 944 | if (UrlUtils::urlIsDirectory(url)) { | ||
846 | emit openDirUrlRequested(url); | 945 | emit openDirUrlRequested(url); | ||
847 | } else { | 946 | } else { | ||
848 | emit openUrlRequested(url); | 947 | emit openUrlRequested(url); | ||
849 | } | 948 | } | ||
850 | } | 949 | } | ||
851 | 950 | | |||
852 | } // namespace | 951 | } // namespace | ||
rkflx: removeItems(KFileItemList({item})) | |||||
rkflx: removeItems(KFileItemList({item})) | |||||
rkflx: Omitting the last two parameters also seems to work for me. | |||||
Wouldn't it be safer to check mDrag where it is accessed, i.e. in executeDrag() and in setDragPixmap? rkflx: Wouldn't it be safer to check `mDrag` where it is accessed, i.e. in `executeDrag()` and in… | |||||
rkflx: (Here too.) |
Not the most elegant solution to the cursor issue, but it's not too bad.