1197 lines
37 KiB
C++
1197 lines
37 KiB
C++
// vim: set tabstop=4 shiftwidth=4 expandtab:
|
|
/*
|
|
Gwenview: an image viewer
|
|
Copyright 2008 Aurélien Gâteau <agateau@kde.org>
|
|
|
|
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 <cmath>
|
|
|
|
// Qt
|
|
#include <QApplication>
|
|
#include <QDrag>
|
|
#include <QGestureEvent>
|
|
#include <QGraphicsLinearLayout>
|
|
#include <QGraphicsOpacityEffect>
|
|
#include <QGraphicsProxyWidget>
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QGraphicsSceneWheelEvent>
|
|
#include <QGraphicsView>
|
|
#include <QIcon>
|
|
#include <QLibraryInfo>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QPointer>
|
|
#include <QPropertyAnimation>
|
|
#include <QStyleHints>
|
|
#include <QTimer>
|
|
#include <QUrl>
|
|
|
|
// KF
|
|
#include <KFileItem>
|
|
#include <KLocalizedString>
|
|
#include <KUrlMimeData>
|
|
|
|
// Local
|
|
#include "gwenview_lib_debug.h"
|
|
#include <lib/document/documentfactory.h>
|
|
#include <lib/documentview/abstractrasterimageviewtool.h>
|
|
#include <lib/documentview/birdeyeview.h>
|
|
#include <lib/documentview/loadingindicator.h>
|
|
#include <lib/documentview/messageviewadapter.h>
|
|
#include <lib/documentview/rasterimageview.h>
|
|
#include <lib/documentview/rasterimageviewadapter.h>
|
|
#include <lib/documentview/svgviewadapter.h>
|
|
#include <lib/documentview/videoviewadapter.h>
|
|
#include <lib/graphicswidgetfloater.h>
|
|
#include <lib/gvdebug.h>
|
|
#include <lib/gwenviewconfig.h>
|
|
#include <lib/hud/hudbutton.h>
|
|
#include <lib/hud/hudwidget.h>
|
|
#include <lib/mimetypeutils.h>
|
|
#include <lib/thumbnailprovider/thumbnailprovider.h>
|
|
#include <lib/thumbnailview/dragpixmapgenerator.h>
|
|
#include <lib/touch/touch.h>
|
|
#ifndef GWENVIEW_NO_WAYLAND_GESTURES
|
|
#include <lib/waylandgestures/waylandgestures.h>
|
|
#endif
|
|
#include <lib/urlutils.h>
|
|
#include <transformimageoperation.h>
|
|
|
|
namespace Gwenview
|
|
{
|
|
#undef ENABLE_LOG
|
|
#undef LOG
|
|
// #define ENABLE_LOG
|
|
#ifdef ENABLE_LOG
|
|
#define LOG(x) // qCDebug(GWENVIEW_LIB_LOG) << 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 = nullptr;
|
|
int mSortKey; // Used to sort views when displayed in compare mode
|
|
HudWidget *mHud = nullptr;
|
|
BirdEyeView *mBirdEyeView = nullptr;
|
|
QPointer<QPropertyAnimation> mMoveAnimation;
|
|
QPointer<QPropertyAnimation> mFadeAnimation;
|
|
QGraphicsOpacityEffect *mOpacityEffect = nullptr;
|
|
|
|
LoadingIndicator *mLoadingIndicator = nullptr;
|
|
/** Delays showing the loading indicator. This is to avoid that we show a few annoying frames of
|
|
* a loading indicator even though the loading might be pretty much instantaneous. */
|
|
QTimer *mLoadingIndicatorDelay = nullptr;
|
|
|
|
QScopedPointer<AbstractDocumentViewAdapter> mAdapter;
|
|
QList<qreal> mZoomSnapValues;
|
|
Document::Ptr mDocument;
|
|
DocumentView::Setup mSetup;
|
|
bool mCurrent;
|
|
bool mCompareMode;
|
|
int controlWheelAccumulatedDelta;
|
|
|
|
QPointF mDragStartPosition;
|
|
QPointer<ThumbnailProvider> mDragThumbnailProvider;
|
|
QPointer<QDrag> mDrag;
|
|
|
|
Touch *mTouch = nullptr;
|
|
#ifndef GWENVIEW_NO_WAYLAND_GESTURES
|
|
WaylandGestures *mWaylandGestures = nullptr;
|
|
#endif
|
|
int mMinTimeBetweenPinch;
|
|
|
|
void setCurrentAdapter(AbstractDocumentViewAdapter *adapter)
|
|
{
|
|
Q_ASSERT(adapter);
|
|
mAdapter.reset(adapter);
|
|
|
|
adapter->widget()->setParentItem(q);
|
|
resizeAdapterWidget();
|
|
|
|
if (adapter->canZoom()) {
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::zoomChanged, q, &DocumentView::slotZoomChanged);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::zoomInRequested, q, &DocumentView::zoomIn);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::zoomOutRequested, q, &DocumentView::zoomOut);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::zoomToFitChanged, q, &DocumentView::zoomToFitChanged);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::zoomToFillChanged, q, &DocumentView::zoomToFillChanged);
|
|
}
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::scrollPosChanged, q, &DocumentView::positionChanged);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::previousImageRequested, q, &DocumentView::previousImageRequested);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::nextImageRequested, q, &DocumentView::nextImageRequested);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::toggleFullScreenRequested, q, &DocumentView::toggleFullScreenRequested);
|
|
QObject::connect(adapter, &AbstractDocumentViewAdapter::completed, q, &DocumentView::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);
|
|
}
|
|
}
|
|
Q_EMIT q->adapterChanged();
|
|
Q_EMIT q->positionChanged();
|
|
if (adapter->canZoom()) {
|
|
if (adapter->zoomToFit()) {
|
|
Q_EMIT q->zoomToFitChanged(true);
|
|
} else if (adapter->zoomToFill()) {
|
|
Q_EMIT q->zoomToFillChanged(true);
|
|
} else {
|
|
Q_EMIT q->zoomChanged(adapter->zoom());
|
|
}
|
|
}
|
|
if (adapter->rasterImageView()) {
|
|
QObject::connect(adapter->rasterImageView(), &RasterImageView::currentToolChanged, q, &DocumentView::currentToolChanged);
|
|
}
|
|
}
|
|
|
|
void setupLoadingIndicator()
|
|
{
|
|
mLoadingIndicator = new LoadingIndicator(q);
|
|
auto floater = new GraphicsWidgetFloater(q);
|
|
floater->setChildWidget(mLoadingIndicator);
|
|
mLoadingIndicator->setZValue(1);
|
|
mLoadingIndicator->hide();
|
|
|
|
mLoadingIndicatorDelay = new QTimer(q);
|
|
mLoadingIndicatorDelay->setSingleShot(true);
|
|
QObject::connect(mLoadingIndicatorDelay, &QTimer::timeout, mLoadingIndicator, [this]() {
|
|
mLoadingIndicator->show();
|
|
Q_EMIT q->indicateLoadingToUser();
|
|
});
|
|
}
|
|
|
|
HudButton *createHudButton(const QString &text, const QString &iconName, bool showText)
|
|
{
|
|
auto 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);
|
|
|
|
auto content = new QGraphicsWidget;
|
|
auto layout = new QGraphicsLinearLayout(content);
|
|
layout->addItem(trashButton);
|
|
layout->addItem(deselectButton);
|
|
|
|
mHud = new HudWidget(q);
|
|
mHud->init(content, HudWidget::OptionNone);
|
|
auto floater = new GraphicsWidgetFloater(q);
|
|
floater->setChildWidget(mHud);
|
|
floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
|
|
|
|
QObject::connect(trashButton, &HudButton::clicked, q, &DocumentView::emitHudTrashClicked);
|
|
QObject::connect(deselectButton, &HudButton::clicked, q, &DocumentView::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) {
|
|
Q_EMIT q->captionUpdateRequested(caption);
|
|
return;
|
|
}
|
|
|
|
caption = doc->url().fileName();
|
|
QSize size = doc->size();
|
|
if (size.isValid()) {
|
|
caption += QStringLiteral(" - ");
|
|
caption += i18nc("@item:intable %1 is image width, %2 is image height", "%1x%2", size.width(), size.height());
|
|
if (mAdapter->canZoom()) {
|
|
int intZoom = qRound(mAdapter->zoom() * 100);
|
|
caption += QStringLiteral(" - ");
|
|
caption += i18nc("Percent value", "%1%", intZoom);
|
|
}
|
|
}
|
|
Q_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 ¢er = QPointF(-1, -1))
|
|
{
|
|
uncheckZoomToFit();
|
|
uncheckZoomToFill();
|
|
zoom = qBound(q->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE);
|
|
mAdapter->setZoom(zoom, center);
|
|
}
|
|
|
|
void updateZoomSnapValues()
|
|
{
|
|
const 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;
|
|
|
|
Q_EMIT q->minimumZoomChanged(min);
|
|
}
|
|
|
|
void showLoadingIndicator()
|
|
{
|
|
if (!mLoadingIndicator) {
|
|
setupLoadingIndicator();
|
|
}
|
|
mLoadingIndicatorDelay->start(400);
|
|
}
|
|
|
|
void hideLoadingIndicator()
|
|
{
|
|
if (!mLoadingIndicator) {
|
|
return;
|
|
}
|
|
mLoadingIndicatorDelay->stop();
|
|
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
|
|
auto anim = new QPropertyAnimation(mOpacityEffect, "opacity");
|
|
anim->setStartValue(mOpacityEffect->opacity());
|
|
anim->setEndValue(value);
|
|
if (qFuzzyCompare(value, 1)) {
|
|
QObject::connect(anim, &QAbstractAnimation::finished, q, &DocumentView::slotFadeInFinished);
|
|
}
|
|
QObject::connect(anim, &QAbstractAnimation::finished, q, &DocumentView::isAnimatedChanged);
|
|
anim->setDuration(DocumentView::AnimDuration);
|
|
mFadeAnimation = anim;
|
|
Q_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({KFileItem(q->document()->url())});
|
|
auto *mimeData = MimeTypeUtils::selectionMimeData(itemList, MimeTypeUtils::DropTarget);
|
|
KUrlMimeData::exportUrlsToPortal(mimeData);
|
|
mDrag->setMimeData(mimeData);
|
|
|
|
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;
|
|
|
|
#ifndef GWENVIEW_NO_WAYLAND_GESTURES
|
|
if (QApplication::platformName() == QStringLiteral("wayland")) {
|
|
d->mWaylandGestures = new WaylandGestures();
|
|
connect(d->mWaylandGestures, &WaylandGestures::pinchGestureStarted, [this]() {
|
|
d->mWaylandGestures->setStartZoom(zoom());
|
|
});
|
|
connect(d->mWaylandGestures, &WaylandGestures::pinchZoomChanged, [this](double zoom) {
|
|
d->setZoom(zoom, d->cursorPosition());
|
|
});
|
|
}
|
|
#endif
|
|
|
|
d->mTouch = new Touch(this);
|
|
setAcceptTouchEvents(true);
|
|
connect(d->mTouch, &Touch::doubleTapTriggered, this, &DocumentView::toggleFullScreenRequested);
|
|
connect(d->mTouch, &Touch::twoFingerTapTriggered, this, &DocumentView::contextMenuRequested);
|
|
connect(d->mTouch, &Touch::pinchGestureStarted, this, &DocumentView::setPinchParameter);
|
|
connect(d->mTouch, &Touch::pinchZoomTriggered, this, &DocumentView::zoomGesture);
|
|
connect(d->mTouch, &Touch::pinchRotateTriggered, this, &DocumentView::rotationsGesture);
|
|
connect(d->mTouch, &Touch::swipeRightTriggered, this, &DocumentView::swipeRight);
|
|
connect(d->mTouch, &Touch::swipeLeftTriggered, this, &DocumentView::swipeLeft);
|
|
connect(d->mTouch, &Touch::PanTriggered, this, &DocumentView::panGesture);
|
|
connect(d->mTouch, &Touch::tapHoldAndMovingTriggered, this, &DocumentView::startDragFromTouch);
|
|
|
|
// 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);
|
|
|
|
// QTBUG-74963. QGraphicsOpacityEffect cause painting an image as non-highdpi.
|
|
if (qFuzzyCompare(qApp->devicePixelRatio(), 1.0) || QLibraryInfo::version() >= QVersionNumber(5, 12, 4))
|
|
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->mTouch;
|
|
#ifndef GWENVIEW_NO_WAYLAND_GESTURES
|
|
delete d->mWaylandGestures;
|
|
#endif
|
|
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<MessageViewAdapter *>(adapter)->setErrorMessage(i18n("Gwenview does not know how to display this kind of document"));
|
|
break;
|
|
default:
|
|
qCWarning(GWENVIEW_LIB_LOG) << "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);
|
|
}
|
|
|
|
// because some loading will be going on right now, also display the indicator after a small delay
|
|
// it will be hidden again in slotBusyChanged()
|
|
d->showLoadingIndicator();
|
|
|
|
d->mSetup = setup;
|
|
d->mDocument = DocumentFactory::instance()->load(url);
|
|
connect(d->mDocument.data(), &Document::busyChanged, this, &DocumentView::slotBusyChanged);
|
|
connect(d->mDocument.data(), &Document::modified, this, [this]() {
|
|
d->updateZoomSnapValues();
|
|
});
|
|
|
|
if (d->mDocument->loadingState() < Document::KindDetermined) {
|
|
auto messageViewAdapter = qobject_cast<MessageViewAdapter *>(d->mAdapter.data());
|
|
if (messageViewAdapter) {
|
|
messageViewAdapter->setInfoMessage(QString());
|
|
}
|
|
connect(d->mDocument.data(), &Document::kindDetermined, this, &DocumentView::finishOpenUrl);
|
|
} else {
|
|
QMetaObject::invokeMethod(this, &DocumentView::finishOpenUrl, Qt::QueuedConnection);
|
|
}
|
|
|
|
if (GwenviewConfig::birdEyeViewEnabled()) {
|
|
d->setupBirdEyeView();
|
|
}
|
|
}
|
|
|
|
void DocumentView::finishOpenUrl()
|
|
{
|
|
disconnect(d->mDocument.data(), &Document::kindDetermined, this, &DocumentView::finishOpenUrl);
|
|
GV_RETURN_IF_FAIL(d->mDocument->loadingState() >= Document::KindDetermined);
|
|
|
|
if (d->mDocument->loadingState() == Document::LoadingFailed) {
|
|
slotLoadingFailed();
|
|
return;
|
|
}
|
|
createAdapterForDocument();
|
|
|
|
connect(d->mDocument.data(), &Document::loadingFailed, this, &DocumentView::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);
|
|
}
|
|
}
|
|
Q_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();
|
|
auto adapter = new MessageViewAdapter;
|
|
adapter->setDocument(d->mDocument);
|
|
QString message = xi18n("Loading <filename>%1</filename> failed", d->mDocument->url().fileName());
|
|
adapter->setErrorMessage(message, d->mDocument->errorString());
|
|
d->setCurrentAdapter(adapter);
|
|
Q_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());
|
|
}
|
|
}
|
|
|
|
void DocumentView::toggleBirdEyeView()
|
|
{
|
|
if (d->mBirdEyeView) {
|
|
BirdEyeView *tmp = d->mBirdEyeView;
|
|
d->mBirdEyeView = nullptr;
|
|
delete tmp;
|
|
} else {
|
|
d->setupBirdEyeView();
|
|
}
|
|
|
|
GwenviewConfig::setBirdEyeViewEnabled(!GwenviewConfig::birdEyeViewEnabled());
|
|
}
|
|
|
|
void DocumentView::setBackgroundColorMode(BackgroundColorMode colorMode)
|
|
{
|
|
GwenviewConfig::setBackgroundColorMode(colorMode);
|
|
Q_EMIT backgroundColorModeChanged(colorMode);
|
|
}
|
|
|
|
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();
|
|
|
|
for (qreal zoom : qAsConst(d->mZoomSnapValues)) {
|
|
if (zoom > currentZoom + REAL_DELTA) {
|
|
d->setZoom(zoom, center);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DocumentView::zoomContinuous(int delta, QPointF center)
|
|
{
|
|
if (center == QPointF(-1, -1)) {
|
|
center = d->cursorPosition();
|
|
}
|
|
const qreal currentZoom = d->mAdapter->zoom();
|
|
|
|
// multiplies by sqrt(2) for every mouse wheel step
|
|
const qreal newZoom = currentZoom * pow(2, 0.5 * float(delta) / QWheelEvent::DefaultDeltasPerStep);
|
|
d->setZoom(newZoom, center);
|
|
return;
|
|
}
|
|
|
|
void DocumentView::zoomOut(QPointF center)
|
|
{
|
|
if (center == QPointF(-1, -1)) {
|
|
center = d->cursorPosition();
|
|
}
|
|
qreal currentZoom = d->mAdapter->zoom();
|
|
|
|
QListIterator<qreal> 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();
|
|
Q_EMIT zoomChanged(zoom);
|
|
}
|
|
|
|
void DocumentView::setZoom(qreal zoom)
|
|
{
|
|
d->setZoom(zoom);
|
|
}
|
|
|
|
qreal DocumentView::zoom() const
|
|
{
|
|
return d->mAdapter->zoom();
|
|
}
|
|
|
|
void DocumentView::setPinchParameter(qint64 timeStamp)
|
|
{
|
|
Q_UNUSED(timeStamp);
|
|
const qreal sensitivityModifier = 0.85;
|
|
const qreal rotationThreshold = 40;
|
|
d->mTouch->setZoomParameter(sensitivityModifier, zoom());
|
|
d->mTouch->setRotationThreshold(rotationThreshold);
|
|
d->mMinTimeBetweenPinch = 0;
|
|
}
|
|
|
|
void DocumentView::zoomGesture(qreal zoom, const QPoint &zoomCenter, qint64 timeStamp)
|
|
{
|
|
qint64 now = QDateTime::currentMSecsSinceEpoch();
|
|
const qint64 diff = now - timeStamp;
|
|
|
|
// in Wayland we can get the gesture event more frequently, to reduce CPU power we don't use every event
|
|
// to calculate and paint a new image (mMinTimeBetweenPinch).To determine the exact minimum waiting time between two
|
|
// pinch events, we use the difference between the time stamps. If the difference is too high we increase the minimum waiting time.
|
|
// The maximal waiting time is 40 milliseconds, this is equal to 25 frames per second.
|
|
if (diff > 40) {
|
|
d->mMinTimeBetweenPinch = (d->mMinTimeBetweenPinch * 2) + 1;
|
|
if (d->mMinTimeBetweenPinch > 40) {
|
|
d->mMinTimeBetweenPinch = 40;
|
|
}
|
|
}
|
|
|
|
if (diff > d->mMinTimeBetweenPinch) {
|
|
if (zoom >= 0.0 && d->mAdapter->canZoom()) {
|
|
d->setZoom(zoom, zoomCenter);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DocumentView::rotationsGesture(qreal rotation)
|
|
{
|
|
if (rotation > 0.0) {
|
|
auto op = new TransformImageOperation(ROT_90);
|
|
op->applyToDocument(d->mDocument);
|
|
} else if (rotation < 0.0) {
|
|
auto op = new TransformImageOperation(ROT_270);
|
|
op->applyToDocument(d->mDocument);
|
|
}
|
|
}
|
|
|
|
void DocumentView::swipeRight()
|
|
{
|
|
const QPoint scrollPos = d->mAdapter->scrollPos().toPoint();
|
|
if (scrollPos.x() <= 1) {
|
|
Q_EMIT d->mAdapter->previousImageRequested();
|
|
}
|
|
}
|
|
|
|
void DocumentView::swipeLeft()
|
|
{
|
|
const QSizeF dipSize = d->mAdapter->imageView()->dipDocumentSize();
|
|
const QPoint scrollPos = d->mAdapter->scrollPos().toPoint();
|
|
const int width = dipSize.width() * d->mAdapter->zoom();
|
|
const QRect visibleRect = d->mAdapter->visibleDocumentRect().toRect();
|
|
const int x = scrollPos.x() + visibleRect.width();
|
|
if (x >= (width - 1)) {
|
|
Q_EMIT d->mAdapter->nextImageRequested();
|
|
}
|
|
}
|
|
|
|
void DocumentView::panGesture(const QPointF &delta)
|
|
{
|
|
d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + delta);
|
|
}
|
|
|
|
void DocumentView::startDragFromTouch(const QPoint &)
|
|
{
|
|
d->startDragIfSensible();
|
|
}
|
|
|
|
void DocumentView::resizeEvent(QGraphicsSceneResizeEvent *event)
|
|
{
|
|
d->resizeAdapterWidget();
|
|
d->updateZoomSnapValues();
|
|
QGraphicsWidget::resizeEvent(event);
|
|
}
|
|
|
|
void DocumentView::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
// Don't let (presumably double click handling in) the superclass swallow the second of two
|
|
// quickly following middle/side clicks, preventing fast toggle & navigation. We wouldn't even
|
|
// get a double click event - handling that could be somewhat cleaner.
|
|
if (d->mAdapter->canZoom() && event->button() == Qt::MiddleButton) {
|
|
if (event->modifiers() == Qt::NoModifier) {
|
|
event->accept();
|
|
toggleZoomToFit();
|
|
return;
|
|
} else if (event->modifiers() == Qt::SHIFT) {
|
|
event->accept();
|
|
toggleZoomToFill();
|
|
return;
|
|
}
|
|
}
|
|
else if (event->button() == Qt::BackButton) {
|
|
event->accept();
|
|
Q_EMIT previousImageRequested();
|
|
return;
|
|
}
|
|
else if (event->button() == Qt::ForwardButton) {
|
|
event->accept();
|
|
Q_EMIT nextImageRequested();
|
|
return;
|
|
}
|
|
|
|
QGraphicsWidget::mousePressEvent(event);
|
|
}
|
|
|
|
void DocumentView::wheelEvent(QGraphicsSceneWheelEvent *event)
|
|
{
|
|
if (d->mAdapter->canZoom()) {
|
|
if ((event->modifiers() & Qt::ControlModifier)
|
|
|| (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Zoom && event->modifiers() == Qt::NoModifier)) {
|
|
zoomContinuous(event->delta(), event->pos());
|
|
// Ctrl + wheel => zoom in or out
|
|
return;
|
|
}
|
|
}
|
|
if (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse && event->modifiers() == Qt::NoModifier) {
|
|
d->controlWheelAccumulatedDelta += event->delta();
|
|
// Browse with mouse wheel
|
|
if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) {
|
|
Q_EMIT previousImageRequested();
|
|
d->controlWheelAccumulatedDelta = 0;
|
|
} else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) {
|
|
Q_EMIT nextImageRequested();
|
|
d->controlWheelAccumulatedDelta = 0;
|
|
}
|
|
return;
|
|
}
|
|
// Scroll
|
|
qreal dx = 0;
|
|
// 16 = pixels for one line
|
|
// 120: see QWheelEvent::angleDelta().y() doc
|
|
qreal dy = -qApp->wheelScrollLines() * 16 * event->delta() / 120;
|
|
if (event->orientation() == Qt::Horizontal) {
|
|
std::swap(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) {
|
|
Q_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()
|
|
{
|
|
Q_EMIT hudDeselectClicked(this);
|
|
}
|
|
|
|
void DocumentView::emitHudTrashClicked()
|
|
{
|
|
Q_EMIT hudTrashClicked(this);
|
|
}
|
|
|
|
void DocumentView::emitFocused()
|
|
{
|
|
Q_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)
|
|
{
|
|
auto anim = new QPropertyAnimation(this, "geometry");
|
|
anim->setStartValue(geometry());
|
|
anim->setEndValue(rect);
|
|
anim->setDuration(DocumentView::AnimDuration);
|
|
connect(anim, &QAbstractAnimation::finished, this, &DocumentView::isAnimatedChanged);
|
|
d->mMoveAnimation = anim;
|
|
Q_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()
|
|
{
|
|
Q_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<QGraphicsSceneMouseEvent *>(event);
|
|
if (mouseEvent->button() == Qt::LeftButton) {
|
|
d->mDragStartPosition = mouseEvent->pos();
|
|
}
|
|
QMetaObject::invokeMethod(this, &DocumentView::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<QGraphicsSceneMouseEvent *>(event);
|
|
// in some older version of Qt, Qt synthesize a mouse event from the touch event
|
|
// we need to suppress this.
|
|
// I need this for my working system (OpenSUSE Leap 15.0, Qt 5.9.4)
|
|
if (mouseEvent->source() == Qt::MouseEventSynthesizedByQt) {
|
|
return true;
|
|
}
|
|
// We need to check if the Left mouse button is pressed, otherwise this can lead
|
|
// to starting a drag & drop sequence using the Forward/Backward mouse buttons
|
|
if (!mouseEvent->buttons().testFlag(Qt::LeftButton)) {
|
|
return false;
|
|
}
|
|
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 = KUrlMimeData::urlsFromMimeData(event->mimeData()).first();
|
|
if (UrlUtils::urlIsDirectory(url)) {
|
|
Q_EMIT openDirUrlRequested(url);
|
|
} else {
|
|
Q_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
|
|
|
|
#include "moc_documentview.cpp"
|