diff --git a/importer/importer.cpp b/importer/importer.cpp index e479e097..fecf2e1e 100644 --- a/importer/importer.cpp +++ b/importer/importer.cpp @@ -1,243 +1,238 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 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 "importer.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include // stdc++ #include // Local #include #include #include #include namespace Gwenview { struct ImporterPrivate { Importer* q; QWidget* mAuthWindow; std::unique_ptr mFileNameFormater; QUrl mTempImportDirUrl; /* @defgroup reset Should be reset in start() * @{ */ QList mUrlList; QList mImportedUrlList; QList mSkippedUrlList; int mRenamedCount; int mProgress; int mJobProgress; /* @} */ QUrl mCurrentUrl; - void emitError(const QString& message) - { - QMetaObject::invokeMethod(q, "error", Q_ARG(QString, message)); - } - bool createImportDir(const QUrl& url) { Q_ASSERT(url.isLocalFile()); // FIXME: Support remote urls if (!QDir().mkpath(url.toLocalFile())) { - emitError(i18n("Could not create destination folder.")); + emit q->error(i18n("Could not create destination folder.")); return false; } QString message; QString dir = FileUtils::createTempDir(url.toLocalFile(), ".gwenview_importer-", &message); mTempImportDirUrl = QUrl::fromLocalFile(dir); if (mTempImportDirUrl.isEmpty()) { - emitError(i18n("Could not create temporary upload folder:\n%1", message)); + emit q->error(i18n("Could not create temporary upload folder:\n%1", message)); return false; } return true; } void importNext() { if (mUrlList.empty()) { q->finalizeImport(); return; } mCurrentUrl = mUrlList.takeFirst(); QUrl dst = mTempImportDirUrl; dst.setPath(dst.path() + mCurrentUrl.fileName()); KIO::Job* job = KIO::copy(mCurrentUrl, dst, KIO::HideProgressInfo); KJobWidgets::setWindow(job, mAuthWindow); QObject::connect(job, SIGNAL(result(KJob*)), q, SLOT(slotCopyDone(KJob*))); QObject::connect(job, SIGNAL(percent(KJob*,ulong)), q, SLOT(slotPercent(KJob*,ulong))); } void renameImportedUrl(const QUrl& src) { QUrl dst = src.resolved(QUrl("..")); QString fileName; if (mFileNameFormater.get()) { KFileItem item(src); item.setDelayedMimeTypes(true); // Get the document time, but do not cache the result because the // 'src' url is temporary: if we import "foo/image.jpg" and // "bar/image.jpg", both images will be temporarily saved in the // 'src' url. QDateTime dateTime = TimeUtils::dateTimeForFileItem(item, TimeUtils::SkipCache); fileName = mFileNameFormater->format(src, dateTime); } else { fileName = src.fileName(); } dst.setPath(dst.path() + fileName); FileUtils::RenameResult result = FileUtils::rename(src, dst, mAuthWindow); switch (result) { case FileUtils::RenamedOK: mImportedUrlList << mCurrentUrl; break; case FileUtils::RenamedUnderNewName: mRenamedCount++; mImportedUrlList << mCurrentUrl; break; case FileUtils::Skipped: mSkippedUrlList << mCurrentUrl; break; case FileUtils::RenameFailed: qWarning() << "Rename failed for" << mCurrentUrl; } q->advance(); importNext(); } }; Importer::Importer(QWidget* parent) : QObject(parent) , d(new ImporterPrivate) { d->q = this; d->mAuthWindow = parent; } Importer::~Importer() { delete d; } void Importer::setAutoRenameFormat(const QString& format) { if (format.isEmpty()) { d->mFileNameFormater.reset(nullptr); } else { d->mFileNameFormater.reset(new FileNameFormater(format)); } } void Importer::start(const QList& list, const QUrl& destination) { d->mUrlList = list; d->mImportedUrlList.clear(); d->mSkippedUrlList.clear(); d->mRenamedCount = 0; d->mProgress = 0; d->mJobProgress = 0; emitProgressChanged(); emit maximumChanged(d->mUrlList.count() * 100); if (!d->createImportDir(destination)) { qWarning() << "Could not create import dir"; return; } d->importNext(); } void Importer::slotCopyDone(KJob* _job) { KIO::CopyJob* job = static_cast(_job); QUrl url = job->destUrl(); if (job->error()) { qWarning() << "FIXME: What do we do with failed urls?"; advance(); d->importNext(); return; } d->renameImportedUrl(url); } void Importer::finalizeImport() { KIO::Job* job = KIO::del(d->mTempImportDirUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(job, d->mAuthWindow); emit importFinished(); } void Importer::advance() { ++d->mProgress; d->mJobProgress = 0; emitProgressChanged(); } void Importer::slotPercent(KJob*, unsigned long percent) { d->mJobProgress = percent; emitProgressChanged(); } void Importer::emitProgressChanged() { emit progressChanged(d->mProgress * 100 + d->mJobProgress); } QList Importer::importedUrlList() const { return d->mImportedUrlList; } QList Importer::skippedUrlList() const { return d->mSkippedUrlList; } int Importer::renamedCount() const { return d->mRenamedCount; } } // namespace diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp index eaea94b0..bcd77c21 100644 --- a/lib/documentview/abstractimageview.cpp +++ b/lib/documentview/abstractimageview.cpp @@ -1,620 +1,620 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2011 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 "abstractimageview.h" // Local // KDE // Qt #include #include #include #include #include #include namespace Gwenview { static const int UNIT_STEP = 16; struct AbstractImageViewPrivate { enum Verbosity { Silent, Notify }; AbstractImageView* q; QCursor mZoomCursor; Document::Ptr mDocument; bool mControlKeyIsDown; bool mEnlargeSmallerImages; qreal mZoom; bool mZoomToFit; bool mZoomToFill; QPointF mImageOffset; QPointF mScrollPos; QPointF mLastDragPos; QSizeF mDocumentSize; const QPixmap mAlphaBackgroundTexture = createAlphaBackgroundTexture(); void adjustImageOffset(Verbosity verbosity = Notify) { QSizeF zoomedDocSize = q->documentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF offset( qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)), qMax((viewSize.height() - zoomedDocSize.height()) / 2, qreal(0.)) ); if (offset != mImageOffset) { mImageOffset = offset; if (verbosity == Notify) { q->onImageOffsetChanged(); } } } void adjustScrollPos(Verbosity verbosity = Notify) { setScrollPos(mScrollPos, verbosity); } void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify) { if (!mDocument) { mScrollPos = _newPos; return; } QSizeF zoomedDocSize = q->documentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF newPos( qBound(qreal(0.), _newPos.x(), zoomedDocSize.width() - viewSize.width()), qBound(qreal(0.), _newPos.y(), zoomedDocSize.height() - viewSize.height()) ); if (newPos != mScrollPos) { QPointF oldPos = mScrollPos; mScrollPos = newPos; if (verbosity == Notify) { q->onScrollPosChanged(oldPos); } // No verbosity test: we always notify the outside world about // scrollPos changes - QMetaObject::invokeMethod(q, "scrollPosChanged"); + emit q->scrollPosChanged(); } } void setupZoomCursor() { // We do not use "appdata" here because that does not work when this // code is called from a KPart. const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("gwenview/cursors/zoom.png")); QPixmap cursorPixmap = QPixmap(path); mZoomCursor = QCursor(cursorPixmap, 11, 11); } QPixmap createAlphaBackgroundTexture() { QPixmap pix = QPixmap(32, 32); QPainter painter(&pix); painter.fillRect(pix.rect(), QColor(128, 128, 128)); const QColor light = QColor(192, 192, 192); painter.fillRect(0, 0, 16, 16, light); painter.fillRect(16, 16, 16, 16, light); return pix; } void checkAndRequestZoomAction(const QGraphicsSceneMouseEvent* event) { if (event->modifiers() & Qt::ControlModifier) { if (event->button() == Qt::LeftButton) { emit q->zoomInRequested(event->pos()); } else if (event->button() == Qt::RightButton) { emit q->zoomOutRequested(event->pos()); } } } }; AbstractImageView::AbstractImageView(QGraphicsItem* parent) : QGraphicsWidget(parent) , d(new AbstractImageViewPrivate) { d->q = this; d->mControlKeyIsDown = false; d->mEnlargeSmallerImages = false; d->mZoom = 1; d->mZoomToFit = true; d->mZoomToFill = false; d->mImageOffset = QPointF(0, 0); d->mScrollPos = QPointF(0, 0); setFocusPolicy(Qt::WheelFocus); setFlag(ItemIsSelectable); setFlag(ItemClipsChildrenToShape); setAcceptHoverEvents(true); d->setupZoomCursor(); updateCursor(); } AbstractImageView::~AbstractImageView() { if (d->mDocument) { d->mDocument->stopAnimation(); } delete d; } Document::Ptr AbstractImageView::document() const { return d->mDocument; } void AbstractImageView::setDocument(Document::Ptr doc) { if (d->mDocument) { disconnect(d->mDocument.data(), nullptr, this, nullptr); } d->mDocument = doc; loadFromDocument(); } QSizeF AbstractImageView::documentSize() const { return d->mDocument ? d->mDocument->size() : QSizeF(); } qreal AbstractImageView::zoom() const { return d->mZoom; } void AbstractImageView::setZoom(qreal zoom, const QPointF& _center, AbstractImageView::UpdateType updateType) { if (!d->mDocument) { d->mZoom = zoom; return; } if (updateType == UpdateIfNecessary && qFuzzyCompare(zoom, d->mZoom) && documentSize() == d->mDocumentSize) { return; } qreal oldZoom = d->mZoom; d->mZoom = zoom; d->mDocumentSize = documentSize(); QPointF center; if (_center == QPointF(-1, -1)) { center = boundingRect().center(); } else { center = _center; } /* We want to keep the point at viewport coordinates "center" at the same position after zooming. The coordinates of this point in image coordinates can be expressed like this: oldScroll + center imagePointAtOldZoom = ------------------ oldZoom scroll + center imagePointAtZoom = --------------- zoom So we want: imagePointAtOldZoom = imagePointAtZoom oldScroll + center scroll + center <=> ------------------ = --------------- oldZoom zoom zoom <=> scroll = ------- (oldScroll + center) - center oldZoom */ /* Compute oldScroll It's useless to take the new offset in consideration because if a direction of the new offset is not 0, we won't be able to center on a specific point in that direction. */ QPointF oldScroll = scrollPos() - imageOffset(); QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center; d->adjustImageOffset(AbstractImageViewPrivate::Silent); d->setScrollPos(scroll, AbstractImageViewPrivate::Silent); onZoomChanged(); emit zoomChanged(d->mZoom); } bool AbstractImageView::zoomToFit() const { return d->mZoomToFit; } bool AbstractImageView::zoomToFill() const { return d->mZoomToFill; } void AbstractImageView::setZoomToFit(bool on) { d->mZoomToFit = on; if (on) { d->mZoomToFill = false; setZoom(computeZoomToFit()); } // We do not set zoom to 1 if zoomToFit is off, this is up to the code // calling us. It may went to zoom to some other level and/or to zoom on // a particular position emit zoomToFitChanged(d->mZoomToFit); } void AbstractImageView::setZoomToFill(bool on, const QPointF& center) { d->mZoomToFill = on; if (on) { d->mZoomToFit = false; setZoom(computeZoomToFill(), center); } // We do not set zoom to 1 if zoomToFit is off, this is up to the code // calling us. It may went to zoom to some other level and/or to zoom on // a particular position emit zoomToFillChanged(d->mZoomToFill); } const QPixmap& AbstractImageView::alphaBackgroundTexture() const { return d->mAlphaBackgroundTexture; } void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); if (d->mZoomToFit) { // setZoom() calls adjustImageOffset(), but only if the zoom changes. // If the view is resized but does not cause a zoom change, we call // adjustImageOffset() ourself. const qreal newZoom = computeZoomToFit(); if (qFuzzyCompare(zoom(), newZoom)) { d->adjustImageOffset(AbstractImageViewPrivate::Notify); } else { setZoom(newZoom); } } else if (d->mZoomToFill) { const qreal newZoom = computeZoomToFill(); if (qFuzzyCompare(zoom(), newZoom)) { d->adjustImageOffset(AbstractImageViewPrivate::Notify); } else { setZoom(newZoom); } } else { d->adjustImageOffset(); d->adjustScrollPos(); } } void AbstractImageView::focusInEvent(QFocusEvent* event) { QGraphicsWidget::focusInEvent(event); // We might have missed a keyReleaseEvent for the control key, e.g. for Ctrl+O const bool controlKeyIsCurrentlyDown = QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier; if (d->mControlKeyIsDown != controlKeyIsCurrentlyDown) { d->mControlKeyIsDown = controlKeyIsCurrentlyDown; updateCursor(); } } qreal AbstractImageView::computeZoomToFit() const { QSizeF docSize = documentSize(); if (docSize.isEmpty()) { return 1; } QSizeF viewSize = boundingRect().size(); qreal fitWidth = viewSize.width() / docSize.width(); qreal fitHeight = viewSize.height() / docSize.height(); qreal fit = qMin(fitWidth, fitHeight); if (!d->mEnlargeSmallerImages) { fit = qMin(fit, qreal(1.)); } return fit; } qreal AbstractImageView::computeZoomToFill() const { QSizeF docSize = documentSize(); if (docSize.isEmpty()) { return 1; } QSizeF viewSize = boundingRect().size(); qreal fitWidth = viewSize.width() / docSize.width(); qreal fitHeight = viewSize.height() / docSize.height(); qreal fill = qMax(fitWidth, fitHeight); if (!d->mEnlargeSmallerImages) { fill = qMin(fill, qreal(1.)); } return fill; } void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mousePressEvent(event); d->checkAndRequestZoomAction(event); // Prepare for panning or dragging if (event->button() == Qt::LeftButton) { d->mLastDragPos = event->pos(); updateCursor(); } } void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseMoveEvent(event); QPointF mousePos = event->pos(); QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos; #if 0 // commented out due to mouse pointer warping around, bug in Qt? // Wrap mouse pos qreal maxWidth = boundingRect().width(); qreal maxHeight = boundingRect().height(); // We need a margin because if the window is maximized, the mouse may not // be able to go past the bounding rect. // The mouse get placed 1 pixel before/after the margin to avoid getting // considered as needing to wrap the other way in next mouseMoveEvent // (because we don't check the move vector) const int margin = 5; if (mousePos.x() <= margin) { mousePos.setX(maxWidth - margin - 1); } else if (mousePos.x() >= maxWidth - margin) { mousePos.setX(margin + 1); } if (mousePos.y() <= margin) { mousePos.setY(maxHeight - margin - 1); } else if (mousePos.y() >= maxHeight - margin) { mousePos.setY(margin + 1); } // Set mouse pos (Hackish translation to screen coords!) QPointF screenDelta = event->screenPos() - event->pos(); QCursor::setPos((mousePos + screenDelta).toPoint()); #endif d->mLastDragPos = mousePos; d->setScrollPos(newScrollPos); } void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseReleaseEvent(event); if (!d->mLastDragPos.isNull()) { d->mLastDragPos = QPointF(); } updateCursor(); } void AbstractImageView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Control) { d->mControlKeyIsDown = true; updateCursor(); return; } if (zoomToFit() || qFuzzyCompare(computeZoomToFit(), zoom())) { if (event->modifiers() != Qt::NoModifier) { return; } switch (event->key()) { case Qt::Key_Left: case Qt::Key_Up: emit previousImageRequested(); break; case Qt::Key_Right: case Qt::Key_Down: emit nextImageRequested(); break; default: break; } return; } QPointF delta(0, 0); qreal pageStep = boundingRect().height(); qreal unitStep; if (event->modifiers() & Qt::ShiftModifier) { unitStep = pageStep / 2; } else { unitStep = UNIT_STEP; } switch (event->key()) { case Qt::Key_Left: delta.setX(-unitStep); break; case Qt::Key_Right: delta.setX(unitStep); break; case Qt::Key_Up: delta.setY(-unitStep); break; case Qt::Key_Down: delta.setY(unitStep); break; case Qt::Key_PageUp: delta.setY(-pageStep); break; case Qt::Key_PageDown: delta.setY(pageStep); break; case Qt::Key_Home: d->setScrollPos(QPointF(d->mScrollPos.x(), 0)); return; case Qt::Key_End: d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom())); return; default: return; } d->setScrollPos(d->mScrollPos + delta); } void AbstractImageView::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Control) { d->mControlKeyIsDown = false; updateCursor(); } } void AbstractImageView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { if (event->modifiers() == Qt::NoModifier && event->button() == Qt::LeftButton) { emit toggleFullScreenRequested(); } d->checkAndRequestZoomAction(event); } QPointF AbstractImageView::imageOffset() const { return d->mImageOffset; } QPointF AbstractImageView::scrollPos() const { return d->mScrollPos; } void AbstractImageView::setScrollPos(const QPointF& pos) { d->setScrollPos(pos); } QPointF AbstractImageView::mapToView(const QPointF& imagePos) const { return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos; } QPoint AbstractImageView::mapToView(const QPoint& imagePos) const { return mapToView(QPointF(imagePos)).toPoint(); } QRectF AbstractImageView::mapToView(const QRectF& imageRect) const { return QRectF( mapToView(imageRect.topLeft()), imageRect.size() * zoom() ); } QRect AbstractImageView::mapToView(const QRect& imageRect) const { return QRect( mapToView(imageRect.topLeft()), imageRect.size() * zoom() ); } QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const { return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom; } QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const { return mapToImage(QPointF(viewPos)).toPoint(); } QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const { return QRectF( mapToImage(viewRect.topLeft()), viewRect.size() / zoom() ); } QRect AbstractImageView::mapToImage(const QRect& viewRect) const { return QRect( mapToImage(viewRect.topLeft()), viewRect.size() / zoom() ); } void AbstractImageView::setEnlargeSmallerImages(bool value) { d->mEnlargeSmallerImages = value; if (zoomToFit()) { setZoom(computeZoomToFit()); } } void AbstractImageView::updateCursor() { if (d->mControlKeyIsDown) { setCursor(d->mZoomCursor); } else { if (d->mLastDragPos.isNull()) { setCursor(Qt::OpenHandCursor); } else { setCursor(Qt::ClosedHandCursor); } } } QSizeF AbstractImageView::visibleImageSize() const { if (!document()) { return QSizeF(); } QSizeF size = documentSize() * zoom(); return size.boundedTo(boundingRect().size()); } void AbstractImageView::applyPendingScrollPos() { d->adjustImageOffset(); d->adjustScrollPos(); } void AbstractImageView::resetDragCursor() { d->mLastDragPos = QPointF(); updateCursor(); } } // namespace