diff --git a/app/actionmanager.cpp b/app/actionmanager.cpp index 253f27d..9f455f5 100644 --- a/app/actionmanager.cpp +++ b/app/actionmanager.cpp @@ -4,7 +4,11 @@ #include "actionmanager.h" +#include "settings.h" #include "mainwindow.h" +#include "playlistmanager.h" +#include "graphicsview.h" +#include "graphicsscene.h" #include #include @@ -25,6 +29,8 @@ void ActionManager::setupAction(MainWindow *mainWindow) { // region: Action and Its Icon Creation + // Declare some convenient macros and lambdas. + #define ICON_NAME(name)\ QStringLiteral(":/icons/" #name ".svg") @@ -39,40 +45,51 @@ void ActionManager::setupAction(MainWindow *mainWindow) w->addAction(*a); }; - #define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a)) - CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original); - CREATE_NEW_ICON_ACTION(mainWindow, actionToggleMaximize, view-fullscreen); - CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in); - CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out); - CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard); - CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right); + /// This macro for register normal action. + #define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a)) + /// This macro for register Qt theme action. + #define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true) + /// This macro for register action with our custom icon. + #define CREATE_NEW_ICON_ACTION(w, a, i) create_action(w, &a, ICON_NAME(i), ACTION_NAME(a), false) + + // TODO: We may replace all Qt theme icons to our custom icon for visual consistency. + // TODO: Some icons are invalid. + + CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy); + CREATE_NEW_ACTION(mainWindow, actionCopyFilePath); + CREATE_NEW_ICON_ACTION(mainWindow, actionPrevPicture, go-previous); CREATE_NEW_ICON_ACTION(mainWindow, actionNextPicture, go-next); - #undef CREATE_NEW_ICON_ACTION - #define CREATE_NEW_ACTION(w, a) create_action(w, &a, QString(), ACTION_NAME(a)) - #define CREATE_NEW_THEMEICON_ACTION(w, a, i) create_action(w, &a, QLatin1String(STRIFY(i)), ACTION_NAME(a), true) - CREATE_NEW_ACTION(mainWindow, actionRotateCounterClockwise); + CREATE_NEW_ICON_ACTION(mainWindow, actionActualSize, zoom-original); + CREATE_NEW_ICON_ACTION(mainWindow, actionFitToScreen, app-icon); + CREATE_NEW_ICON_ACTION(mainWindow, actionFitByWidth, app-icon); + CREATE_NEW_ICON_ACTION(mainWindow, actionFitByHeight, app-icon); + CREATE_NEW_ICON_ACTION(mainWindow, actionZoomIn, zoom-in); + CREATE_NEW_ICON_ACTION(mainWindow, actionZoomOut, zoom-out); + CREATE_NEW_ICON_ACTION(mainWindow, actionFlipHorizontal, app-icon); + CREATE_NEW_ICON_ACTION(mainWindow, actionFlipVertical, app-icon); + CREATE_NEW_ICON_ACTION(mainWindow, actionRotateClockwise, object-rotate-right); + CREATE_NEW_ICON_ACTION(mainWindow, actionRotateCounterClockwise, app-icon); + + CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform); + CREATE_NEW_ICON_ACTION(mainWindow, actionToggleCheckerboard, view-background-checkerboard); CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation); CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame); - CREATE_NEW_THEMEICON_ACTION(mainWindow, actionOpen, document-open); - CREATE_NEW_ACTION(mainWindow, actionHorizontalFlip); - CREATE_NEW_ACTION(mainWindow, actionFitInView); - CREATE_NEW_ACTION(mainWindow, actionFitByWidth); - CREATE_NEW_THEMEICON_ACTION(mainWindow, actionCopyPixmap, edit-copy); - CREATE_NEW_ACTION(mainWindow, actionCopyFilePath); - CREATE_NEW_THEMEICON_ACTION(mainWindow, actionPaste, edit-paste); CREATE_NEW_THEMEICON_ACTION(mainWindow, actionTrash, edit-delete); - CREATE_NEW_ACTION(mainWindow, actionToggleAvoidResetTransform); - CREATE_NEW_ACTION(mainWindow, actionSettings); - CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help); CREATE_NEW_THEMEICON_ACTION(mainWindow, actionLocateInFileManager, system-file-manager); CREATE_NEW_THEMEICON_ACTION(mainWindow, actionProperties, document-properties); - CREATE_NEW_ACTION(mainWindow, actionQuitApp); - #undef CREATE_NEW_ACTION + + CREATE_NEW_ACTION(mainWindow, actionSettings); + CREATE_NEW_THEMEICON_ACTION(mainWindow, actionHelp, system-help); + + // Remove convenient macros + + #undef CREATE_NEW_ICON_ACTION #undef CREATE_NEW_THEMEICON_ACTION + #undef CREATE_NEW_ACTION #undef STRIFY #undef ACTION_NAME @@ -80,34 +97,40 @@ void ActionManager::setupAction(MainWindow *mainWindow) // endregion + // region: Setup Checkable Action + + actionToggleAvoidResetTransform->setCheckable(true); + actionToggleCheckerboard->setCheckable(true); + actionTogglePauseAnimation->setCheckable(true); + + // endregion + // region: Action Translatable Text Setup - actionOpen->setText(QCoreApplication::translate("MainWindow", "&Open...", nullptr)); - - actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr)); - actionToggleMaximize->setText(QCoreApplication::translate("MainWindow", "Toggle maximize", nullptr)); - actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr)); - actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr)); - actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr)); - actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr)); - actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr)); + actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr)); + actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr)); actionPrevPicture->setText(QCoreApplication::translate("MainWindow", "Previous image", nullptr)); actionNextPicture->setText(QCoreApplication::translate("MainWindow", "Next image", nullptr)); + actionActualSize->setText(QCoreApplication::translate("MainWindow", "Actual size", nullptr)); + actionFitToScreen->setText(QCoreApplication::translate("MainWindow", "Fit to screen", nullptr)); + actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit by width", nullptr)); + actionFitByHeight->setText(QCoreApplication::translate("MainWindow", "Fit by height", nullptr)); + actionZoomIn->setText(QCoreApplication::translate("MainWindow", "Zoom in", nullptr)); + actionZoomOut->setText(QCoreApplication::translate("MainWindow", "Zoom out", nullptr)); + + actionFlipHorizontal->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr)); + actionFlipVertical->setText(QCoreApplication::translate("MainWindow", "Flip &Vertically", nullptr)); + actionRotateClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate right", nullptr)); + actionRotateCounterClockwise->setText(QCoreApplication::translate("MainWindow", "Rotate left", nullptr)); + + actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view")); + actionToggleCheckerboard->setText(QCoreApplication::translate("MainWindow", "Toggle Checkerboard", nullptr)); actionTogglePauseAnimation->setText(QCoreApplication::translate("MainWindow", "Pause/Resume Animation", nullptr)); actionAnimationNextFrame->setText(QCoreApplication::translate("MainWindow", "Animation Go to Next Frame", nullptr)); - actionHorizontalFlip->setText(QCoreApplication::translate("MainWindow", "Flip &Horizontally", nullptr)); - actionFitInView->setText(QCoreApplication::translate("MainWindow", "Fit to view", nullptr)); - actionFitByWidth->setText(QCoreApplication::translate("MainWindow", "Fit to width", nullptr)); - actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr)); - actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr)); - actionPaste->setText(QCoreApplication::translate("MainWindow", "&Paste", nullptr)); actionTrash->setText(QCoreApplication::translate("MainWindow", "Move to Trash", nullptr)); - actionToggleAvoidResetTransform->setText(QCoreApplication::translate("MainWindow", "Keep transformation", "The 'transformation' means the flip/rotation status that currently applied to the image view")); - actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr)); - actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr)); #ifdef Q_OS_WIN actionLocateInFileManager->setText( QCoreApplication::translate( @@ -119,8 +142,9 @@ void ActionManager::setupAction(MainWindow *mainWindow) actionLocateInFileManager->setText(QCoreApplication::translate("MainWindow", "Show in directory", nullptr)); #endif // Q_OS_WIN actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr)); - actionQuitApp->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr)); + actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr)); + actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr)); // endregion QMetaObject::connectSlotsByName(mainWindow); @@ -128,34 +152,57 @@ void ActionManager::setupAction(MainWindow *mainWindow) void ActionManager::setupShortcuts() { - actionOpen->setShortcut(QKeySequence::Open); + actionCopyPixmap->setShortcut(QKeySequence::Copy); + + actionPrevPicture->setShortcuts({ + QKeySequence(Qt::Key_Left), + QKeySequence(Qt::Key_PageUp), + }); + actionNextPicture->setShortcuts({ + QKeySequence(Qt::Key_Right), + QKeySequence(Qt::Key_PageDown), + }); + actionActualSize->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_0)); actionZoomIn->setShortcut(QKeySequence::ZoomIn); actionZoomOut->setShortcut(QKeySequence::ZoomOut); - actionPrevPicture->setShortcuts({ - QKeySequence(Qt::Key_PageUp), - QKeySequence(Qt::Key_Left), - }); - actionNextPicture->setShortcuts({ - QKeySequence(Qt::Key_PageDown), - QKeySequence(Qt::Key_Right), - }); - actionHorizontalFlip->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_R)); - actionCopyPixmap->setShortcut(QKeySequence::Copy); - actionPaste->setShortcut(QKeySequence::Paste); + actionTrash->setShortcut(QKeySequence::Delete); - actionHelp->setShortcut(QKeySequence::HelpContents); - actionSettings->setShortcut(QKeySequence::Preferences); actionProperties->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_I)); - actionQuitApp->setShortcuts({ - QKeySequence(Qt::Key_Space), - QKeySequence(Qt::Key_Escape) - }); + + actionSettings->setShortcut(QKeySequence::Preferences); + actionHelp->setShortcut(QKeySequence::HelpContents); } -void ActionManager::enablePrevNextPictureAction(bool enablePrevPictureAction, bool enableNextPictureAction) +void ActionManager::updateActionState(PlaylistManager* pm, GraphicsView* gv) { - actionPrevPicture->setEnabled(enablePrevPictureAction); - actionNextPicture->setEnabled(enableNextPictureAction); + const auto* gs = gv->scene(); + const auto* settings = Settings::instance(); + + // Update Prev/Next Picture State + const int galleryFileCount = pm->totalCount(); + const bool loopGallery = settings->loopGallery(); + actionPrevPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isFirstIndex())); + actionNextPicture->setEnabled(galleryFileCount > 1 && (loopGallery || !pm->isLastIndex())); + + // Update Avoid Reset Transform + actionToggleAvoidResetTransform->setCheckable(gv->avoidResetTransform()); + + // Update Checkerboard + actionToggleCheckerboard->setChecked(gv->checkerboard()); + + // Update Animation Graphics Action + bool canPauseAnimation = gs->canPauseAnimation(); + actionTogglePauseAnimation->setEnabled(canPauseAnimation); + if (canPauseAnimation) { + actionTogglePauseAnimation->setChecked(gs->isPauseAnimation()); + } else { + actionTogglePauseAnimation->setChecked(false); + } + bool canSkipAnimation = gs->canSkipAnimationFrame(); + actionAnimationNextFrame->setEnabled(canSkipAnimation); + + // TODO: Add more... + } diff --git a/app/actionmanager.h b/app/actionmanager.h index 69c7dd0..cbc4456 100644 --- a/app/actionmanager.h +++ b/app/actionmanager.h @@ -8,6 +8,8 @@ #include class MainWindow; +class PlaylistManager; +class GraphicsView; /// Action Manager is separated from Main Window /// to manage all actions specifically. @@ -19,38 +21,40 @@ public: void setupAction(MainWindow * mainWindow); void setupShortcuts(); - void enablePrevNextPictureAction(bool enablePrevPictureAction, bool enableNextPictureAction); + +public slots: + void updateActionState(PlaylistManager* pm, GraphicsView* gv); public: - QAction *actionOpen; - - QAction *actionActualSize; - QAction *actionToggleMaximize; - QAction *actionZoomIn; - QAction *actionZoomOut; - QAction *actionToggleCheckerboard; - QAction *actionRotateClockwise; - QAction *actionRotateCounterClockwise; + QAction *actionCopyPixmap; + QAction *actionCopyFilePath; QAction *actionPrevPicture; QAction *actionNextPicture; + QAction *actionActualSize; + QAction *actionFitToScreen; + QAction *actionFitByWidth; + QAction *actionFitByHeight; + QAction *actionZoomIn; + QAction *actionZoomOut; + + QAction *actionFlipHorizontal; + QAction *actionFlipVertical; + QAction *actionRotateClockwise; + QAction *actionRotateCounterClockwise; + + QAction *actionToggleAvoidResetTransform; + QAction *actionToggleCheckerboard; QAction *actionTogglePauseAnimation; QAction *actionAnimationNextFrame; - QAction *actionHorizontalFlip; - QAction *actionFitInView; - QAction *actionFitByWidth; - QAction *actionCopyPixmap; - QAction *actionCopyFilePath; - QAction *actionPaste; QAction *actionTrash; - QAction *actionToggleAvoidResetTransform; - QAction *actionSettings; - QAction *actionHelp; QAction *actionLocateInFileManager; QAction *actionProperties; - QAction *actionQuitApp; + + QAction *actionSettings; + QAction *actionHelp; }; #endif // ACTIONMANAGER_H diff --git a/app/graphicsscene.cpp b/app/graphicsscene.cpp index 50df449..937e646 100644 --- a/app/graphicsscene.cpp +++ b/app/graphicsscene.cpp @@ -200,27 +200,57 @@ bool GraphicsScene::trySetTransformationMode(Qt::TransformationMode mode, float return false; } +/// Fetch graphics item as our custom animated item. +/// Return nullptr means this item is not animated item. +static PGraphicsMovieItem* fetchAnimatedItem(QGraphicsItem *item) { + return qgraphicsitem_cast(item); +} + +bool GraphicsScene::canPauseAnimation() const { + return fetchAnimatedItem(m_theThing) != nullptr; +} + +bool GraphicsScene::isPauseAnimation() const { + auto* animatedItem = fetchAnimatedItem(m_theThing); + if (animatedItem != nullptr) { + return animatedItem->movie()->state() == QMovie::Paused; + } else { + return false; + } +} + bool GraphicsScene::togglePauseAnimation() { - PGraphicsMovieItem * animatedItem = qgraphicsitem_cast(m_theThing); - if (animatedItem) { + auto* animatedItem = fetchAnimatedItem(m_theThing); + if (animatedItem != nullptr) { animatedItem->movie()->setPaused(animatedItem->movie()->state() != QMovie::Paused); return true; + } else { + return false; } - return false; +} + +bool GraphicsScene::canSkipAnimationFrame() const { + return isPauseAnimation(); } bool GraphicsScene::skipAnimationFrame(int delta) { - PGraphicsMovieItem * animatedItem = qgraphicsitem_cast(m_theThing); - if (animatedItem) { - const int frameCount = animatedItem->movie()->frameCount(); - const int currentFrame = animatedItem->movie()->currentFrameNumber(); - const int targetFrame = (currentFrame + delta) % frameCount; - animatedItem->movie()->setPaused(true); - return animatedItem->movie()->jumpToFrame(targetFrame); + auto* animatedItem = fetchAnimatedItem(m_theThing); + + // Only paused animated item can skip frame + if (animatedItem == nullptr) { + return false; } - return false; + if (animatedItem->movie()->state() != QMovie::Paused) { + return false; + } + + // Perform skip + const int frameCount = animatedItem->movie()->frameCount(); + const int currentFrame = animatedItem->movie()->currentFrameNumber(); + const int targetFrame = (currentFrame + delta) % frameCount; + return animatedItem->movie()->jumpToFrame(targetFrame); } QPixmap GraphicsScene::renderToPixmap() diff --git a/app/graphicsscene.h b/app/graphicsscene.h index 0716141..8baa27d 100644 --- a/app/graphicsscene.h +++ b/app/graphicsscene.h @@ -21,7 +21,19 @@ public: bool trySetTransformationMode(Qt::TransformationMode mode, float scaleHint); + /// Check whether current thing is animation. + /// Only animation thing can be paused. + bool canPauseAnimation() const; + /// Check whether we are pasuing an animation graphics. + bool isPauseAnimation() const; + /// Toggle animation graphics pause state. + /// Return true for successfully toggle, otherwise false. bool togglePauseAnimation(); + /// Check whether we can skip animation frame. + /// Only paused animation graphics can skip frame. + bool canSkipAnimationFrame() const; + /// Skip paused animation graphics with given frame. + /// Return true for successfully skip, otherwise false. bool skipAnimationFrame(int delta = 1); QPixmap renderToPixmap(); diff --git a/app/graphicsview.cpp b/app/graphicsview.cpp index 91c1ebc..e6d2ff8 100644 --- a/app/graphicsview.cpp +++ b/app/graphicsview.cpp @@ -140,7 +140,7 @@ void GraphicsView::resetTransform() void GraphicsView::zoomView(qreal scaleFactor) { - m_enableFitInView = false; + m_enableFitToScreen = false; scale(scaleFactor, scaleFactor); applyTransformationModeByScaleFactor(); emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); @@ -223,6 +223,8 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly) // Position the image correctly based on orientation with rotation consideration QRectF originalScene = sceneRect(); QTransform currentTransform = transform(); + + // TODO: May be optimized by matrix arithmetic operation? if (ori == Qt::Horizontal) { // For horizontal fit (fit by width), position at top (for tall images) @@ -276,7 +278,7 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly) centerOn(sceneLeftCenter); } - m_enableFitInView = false; + m_enableFitToScreen = false; applyTransformationModeByScaleFactor(); emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); @@ -299,7 +301,7 @@ void GraphicsView::displayScene() emit navigatorViewRequired(false, transform()); } - m_enableFitInView = true; + m_enableFitToScreen = true; m_firstUserMediaLoaded = true; } @@ -313,9 +315,9 @@ bool GraphicsView::isSceneBiggerThanView() const } // Automately do fit in view when viewport(window) smaller than image original size. -void GraphicsView::setEnableAutoFitInView(bool enable) +void GraphicsView::setEnableAutoFitToScreen(bool enable) { - m_enableFitInView = enable; + m_enableFitToScreen = enable; } bool GraphicsView::avoidResetTransform() const @@ -328,6 +330,15 @@ void GraphicsView::setAvoidResetTransform(bool avoidReset) m_avoidResetTransform = avoidReset; } +bool GraphicsView::checkerboard() const { + return m_checkerboardEnabled; +} + +void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor) +{ + setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor); +} + inline double zeroOrOne(double number) { return qFuzzyIsNull(number) ? 0 : (number > 0 ? 1 : -1); @@ -342,11 +353,6 @@ QTransform GraphicsView::resetScale(const QTransform & orig) orig.dx(), orig.dy()); } -void GraphicsView::toggleCheckerboard(bool invertCheckerboardColor) -{ - setCheckerboardEnabled(!m_checkerboardEnabled, invertCheckerboardColor); -} - void GraphicsView::updateStyle(Qt::ColorScheme colorScheme) { // YYC MARK: // If we are using Windows 10 and lower system or Qt which do not support "Windows 11" @@ -423,11 +429,11 @@ void GraphicsView::wheelEvent(QWheelEvent *event) void GraphicsView::resizeEvent(QResizeEvent *event) { - if (m_enableFitInView) { + if (m_enableFitToScreen) { bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform())); if (originalSizeSmallerThanWindow && scaleFactor() >= 1) { // no longer need to do fitInView() - // but we leave the m_enableFitInView value unchanged in case + // but we leave the m_enableToScreen value unchanged in case // user resize down the window again. } else if (originalSizeSmallerThanWindow && scaleFactor() < 1) { resetScale(); diff --git a/app/graphicsview.h b/app/graphicsview.h index d314385..b1b184e 100644 --- a/app/graphicsview.h +++ b/app/graphicsview.h @@ -38,11 +38,14 @@ public: void displayScene(); bool isSceneBiggerThanView() const; - void setEnableAutoFitInView(bool enable = true); + void setEnableAutoFitToScreen(bool enable = true); bool avoidResetTransform() const; void setAvoidResetTransform(bool avoidReset); + bool checkerboard() const; + void toggleCheckerboard(bool invertCheckerboardColor = false); + static QTransform resetScale(const QTransform & orig); signals: @@ -50,7 +53,6 @@ signals: void viewportRectChanged(); public slots: - void toggleCheckerboard(bool invertCheckerboardColor = false); void updateStyle(Qt::ColorScheme colorScheme); private: @@ -70,7 +72,7 @@ private: // Consider switch to 3 state for "no fit", "always fit" and "fit when view is smaller"? // ... or even more? e.g. "fit/snap width" things... // Currently it's "no fit" when it's false and "fit when view is smaller" when it's true. - bool m_enableFitInView = false; + bool m_enableFitToScreen = false; bool m_avoidResetTransform = false; bool m_checkerboardEnabled = false; bool m_useLightCheckerboard = false; diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 865985b..f55ec34 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -92,13 +92,16 @@ MainWindow::MainWindow(QWidget *parent) m_bottomButtonGroup = new BottomButtonGroup({ m_am->actionActualSize, - m_am->actionToggleMaximize, + m_am->actionFitToScreen, m_am->actionZoomIn, m_am->actionZoomOut, m_am->actionPrevPicture, + m_am->actionProperties, m_am->actionNextPicture, + m_am->actionRotateCounterClockwise, + m_am->actionRotateClockwise, + m_am->actionFlipHorizontal, m_am->actionToggleCheckerboard, - m_am->actionRotateClockwise }, this); connect(m_pm, &PlaylistManager::totalCountChanged, this, &MainWindow::updateGalleryButtonsVisibility); @@ -358,65 +361,73 @@ void MainWindow::resizeEvent(QResizeEvent *event) void MainWindow::contextMenuEvent(QContextMenuEvent *event) { + // TODO: Update this function. QMenu * menu = new QMenu; + + // TODO: currentImageFileUrl() may be redundant. QMenu * copyMenu = new QMenu(tr("&Copy")); QUrl currentFileUrl = currentImageFileUrl(); - QAction * copyPixmap = m_am->actionCopyPixmap; - QAction * copyFilePath = m_am->actionCopyFilePath; - copyMenu->setIcon(QIcon::fromTheme(u"edit-copy"_s)); - copyMenu->addAction(copyPixmap); + copyMenu->addAction(m_am->actionCopyPixmap); if (currentFileUrl.isValid()) { - copyMenu->addAction(copyFilePath); + copyMenu->addAction(m_am->actionCopyFilePath); } - - QAction * paste = m_am->actionPaste; - - QAction * trash = m_am->actionTrash; - - QAction * avoidResetTransform = m_am->actionToggleAvoidResetTransform; - avoidResetTransform->setCheckable(true); - avoidResetTransform->setChecked(m_gv->avoidResetTransform()); - - QAction * toggleSettings = m_am->actionSettings; - QAction * helpAction = m_am->actionHelp; - QAction * propertiesAction = m_am->actionProperties; - -#if 0 - menu->addAction(m_am->actionOpen); -#endif // 0 - if (copyMenu->actions().count() == 1) { menu->addActions(copyMenu->actions()); } else { menu->addMenu(copyMenu); } - if (canPaste()) { - menu->addAction(paste); - } + menu->addSeparator(); + + menu->addAction(m_am->actionPrevPicture); + menu->addAction(m_am->actionNextPicture); menu->addSeparator(); - menu->addAction(m_am->actionHorizontalFlip); -#if 0 - menu->addAction(m_am->actionFitInView); + menu->addAction(m_am->actionActualSize); + menu->addAction(m_am->actionFitToScreen); menu->addAction(m_am->actionFitByWidth); -#endif // 0 + menu->addAction(m_am->actionFitByHeight); + menu->addAction(m_am->actionZoomIn); + menu->addAction(m_am->actionZoomOut); + menu->addSeparator(); + + menu->addAction(m_am->actionFlipHorizontal); + menu->addAction(m_am->actionFlipVertical); + menu->addAction(m_am->actionRotateClockwise); + menu->addAction(m_am->actionRotateCounterClockwise); + + menu->addSeparator(); + + // TODO: Move checked update to action manager. + QAction * avoidResetTransform = m_am->actionToggleAvoidResetTransform; + avoidResetTransform->setCheckable(true); + avoidResetTransform->setChecked(m_gv->avoidResetTransform()); menu->addAction(avoidResetTransform); - menu->addSeparator(); - menu->addAction(toggleSettings); - menu->addAction(helpAction); + menu->addAction(m_am->actionToggleCheckerboard); + QAction * pauseAnimation = m_am->actionTogglePauseAnimation; + pauseAnimation->setCheckable(true); + pauseAnimation->setChecked(m_gv->scene()->pauseAnimation()); + menu->addAction(pauseAnimation); + menu->addAction(m_am->actionAnimationNextFrame); + if (currentFileUrl.isValid()) { menu->addSeparator(); if (currentFileUrl.isLocalFile()) { - menu->addAction(trash); + menu->addAction(m_am->actionTrash); menu->addAction(m_am->actionLocateInFileManager); } - menu->addAction(propertiesAction); + menu->addAction(m_am->actionProperties); } + + menu->addSeparator(); + + menu->addAction(m_am->actionSettings); + menu->addAction(m_am->actionHelp); + menu->exec(mapToGlobal(event->pos())); menu->deleteLater(); copyMenu->deleteLater(); @@ -542,55 +553,7 @@ QSize MainWindow::sizeHint() const return QSize(710, 530); } -void MainWindow::on_actionOpen_triggered() -{ - QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); - QUrl pictureUrl = picturesLocations.isEmpty() ? QUrl::fromLocalFile(picturesLocations.first()) - : QUrl::fromLocalFile(QDir::homePath()); - QList urls(QFileDialog::getOpenFileUrls(this, QString(), pictureUrl)); - if (!urls.isEmpty()) { - showUrls(urls); - } -} - -void MainWindow::on_actionActualSize_triggered() -{ - m_gv->resetScale(); - m_gv->setEnableAutoFitInView(false); -} - -void MainWindow::on_actionToggleMaximize_triggered() -{ - toggleMaximize(); -} - -void MainWindow::on_actionZoomIn_triggered() -{ - if (m_gv->scaleFactor() < 1000) { - m_gv->zoomView(1.25); - } -} - -void MainWindow::on_actionZoomOut_triggered() -{ - m_gv->zoomView(0.8); -} - -void MainWindow::on_actionHorizontalFlip_triggered() -{ - m_gv->flipView(); -} - -void MainWindow::on_actionFitInView_triggered() -{ - m_gv->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio); - m_gv->setEnableAutoFitInView(m_gv->scaleFactor() <= 1); -} - -void MainWindow::on_actionFitByWidth_triggered() -{ - m_gv->fitByOrientation(); -} +// region: Action Trigger void MainWindow::on_actionCopyPixmap_triggered() { @@ -607,43 +570,99 @@ void MainWindow::on_actionCopyFilePath_triggered() } } -void MainWindow::on_actionPaste_triggered() +void MainWindow::on_actionPrevPicture_triggered() { - QImage clipboardImage; - QUrl clipboardFileUrl; + galleryPrev(); +} - const QMimeData * clipboardData = QApplication::clipboard()->mimeData(); - if (clipboardData->hasImage()) { - QVariant imageVariant(clipboardData->imageData()); - if (imageVariant.isValid()) { - clipboardImage = qvariant_cast(imageVariant); - } - } else if (clipboardData->hasText()) { - QString clipboardText(clipboardData->text()); - if (clipboardText.startsWith("PICTURE:")) { - QString maybeFilename(clipboardText.mid(8)); - if (QFile::exists(maybeFilename)) { - clipboardFileUrl = QUrl::fromLocalFile(maybeFilename); - } - } - } +void MainWindow::on_actionNextPicture_triggered() +{ + galleryNext(); +} - if (!clipboardImage.isNull()) { - setWindowTitle(tr("Image From Clipboard")); - m_gv->showImage(clipboardImage); - clearGallery(); - } else if (clipboardFileUrl.isValid()) { - m_gv->showFileFromPath(clipboardFileUrl.toLocalFile()); - m_pm->loadPlaylist(clipboardFileUrl); +void MainWindow::on_actionActualSize_triggered() +{ + m_gv->resetScale(); + m_gv->setEnableAutoFitToScreen(false); +} + +void MainWindow::on_actionFitToScreen_triggered() +{ + m_gv->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio); + m_gv->setEnableAutoFitToScreen(m_gv->scaleFactor() <= 1); +} + +void MainWindow::on_actionFitByWidth_triggered() +{ + m_gv->fitByOrientation(Qt::Orientation::Horizontal); +} + +void MainWindow::on_actionFitByHeight_triggered() +{ + m_gv->fitByOrientation(Qt::Orientation::Vertical); +} + +void MainWindow::on_actionZoomIn_triggered() +{ + if (m_gv->scaleFactor() < 1000) { + m_gv->zoomView(1.25); } } +void MainWindow::on_actionZoomOut_triggered() +{ + m_gv->zoomView(0.8); +} + +void MainWindow::on_actionFlipHorizontal_triggered() +{ + m_gv->flipView(true); +} + +void MainWindow::on_actionFlipVertical_triggered() +{ + m_gv->flipView(false); +} + +void MainWindow::on_actionRotateClockwise_triggered() +{ + m_gv->rotateView(true); + m_gv->displayScene(); +} + +void MainWindow::on_actionRotateCounterClockwise_triggered() +{ + m_gv->rotateView(false); + m_gv->displayScene(); +} + +void MainWindow::on_actionToggleAvoidResetTransform_triggered() +{ + toggleAvoidResetTransform(); +} + +void MainWindow::on_actionToggleCheckerboard_triggered() +{ + m_gv->toggleCheckerboard(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)); +} + +void MainWindow::on_actionTogglePauseAnimation_triggered() +{ + m_gv->scene()->togglePauseAnimation(); +} + +void MainWindow::on_actionAnimationNextFrame_triggered() +{ + m_gv->scene()->skipAnimationFrame(1); +} + void MainWindow::on_actionTrash_triggered() { - QModelIndex index = m_pm->curIndex(); - if (!m_pm->urlByIndex(index).isLocalFile()) return; + auto index = m_pm->currentIndex(); + if (!index.has_value()) return; + if (!m_pm->urlByIndex(*index).isLocalFile()) return; - QFile file(m_pm->localFileByIndex(index)); + QFile file(m_pm->localFileByIndex(*index)); QFileInfo fileInfo(file.fileName()); QMessageBox::StandardButton result = QMessageBox::question(this, tr("Move to Trash"), @@ -660,77 +679,6 @@ void MainWindow::on_actionTrash_triggered() } } -void MainWindow::on_actionToggleCheckerboard_triggered() -{ - // TODO: is that okay to do this since we plan to support custom shortcuts? - m_gv->toggleCheckerboard(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)); -} - -void MainWindow::on_actionRotateClockwise_triggered() -{ - m_gv->rotateView(); - m_gv->displayScene(); -} - -void MainWindow::on_actionRotateCounterClockwise_triggered() -{ - m_gv->rotateView(false); - m_gv->displayScene(); -} - -void MainWindow::on_actionPrevPicture_triggered() -{ - galleryPrev(); -} - -void MainWindow::on_actionNextPicture_triggered() -{ - galleryNext(); -} - -void MainWindow::on_actionTogglePauseAnimation_triggered() -{ - m_gv->scene()->togglePauseAnimation(); -} - -void MainWindow::on_actionAnimationNextFrame_triggered() -{ - m_gv->scene()->skipAnimationFrame(1); -} - -void MainWindow::on_actionToggleAvoidResetTransform_triggered() -{ - toggleAvoidResetTransform(); -} - -void MainWindow::on_actionSettings_triggered() -{ - SettingsDialog * sd = new SettingsDialog(this); - sd->exec(); - sd->deleteLater(); -} - -void MainWindow::on_actionHelp_triggered() -{ - AboutDialog * ad = new AboutDialog(this); - ad->exec(); - ad->deleteLater(); -} - -void MainWindow::on_actionProperties_triggered() -{ - QUrl currentFileUrl = currentImageFileUrl(); - if (!currentFileUrl.isValid()) return; - - MetadataModel * md = new MetadataModel(); - md->setFile(currentFileUrl.toLocalFile()); - - MetadataDialog * ad = new MetadataDialog(this); - ad->setMetadataModel(md); - ad->exec(); - ad->deleteLater(); -} - void MainWindow::on_actionLocateInFileManager_triggered() { QUrl currentFileUrl = currentImageFileUrl(); @@ -766,23 +714,39 @@ void MainWindow::on_actionLocateInFileManager_triggered() #endif // Q_OS_WIN } -void MainWindow::on_actionQuitApp_triggered() +void MainWindow::on_actionProperties_triggered() { - quitAppAction(false); + QUrl currentFileUrl = currentImageFileUrl(); + if (!currentFileUrl.isValid()) return; + + MetadataModel * md = new MetadataModel(); + md->setFile(currentFileUrl.toLocalFile()); + + MetadataDialog * ad = new MetadataDialog(this); + ad->setMetadataModel(md); + ad->exec(); + ad->deleteLater(); } +void MainWindow::on_actionSettings_triggered() +{ + SettingsDialog * sd = new SettingsDialog(this); + sd->exec(); + sd->deleteLater(); +} + +void MainWindow::on_actionHelp_triggered() +{ + AboutDialog * ad = new AboutDialog(this); + ad->exec(); + ad->deleteLater(); +} + +// endregion + bool MainWindow::updateFileWatcher(const QString &basePath) { m_fileSystemWatcher->removePaths(m_fileSystemWatcher->files()); if (!basePath.isEmpty()) return m_fileSystemWatcher->addPath(basePath); return false; } - -void MainWindow::updateGalleryButtonsVisibility() -{ - const int galleryFileCount = m_pm->totalCount(); - const bool loopGallery = Settings::instance()->loopGallery(); - m_am->enablePrevNextPictureAction( - galleryFileCount > 1 && (loopGallery || !m_pm->isFirstIndex()), - galleryFileCount > 1 && (loopGallery || !m_pm->isLastIndex())); -} diff --git a/app/mainwindow.h b/app/mainwindow.h index 3e4b85a..039c3bc 100644 --- a/app/mainwindow.h +++ b/app/mainwindow.h @@ -66,39 +66,38 @@ protected: QSize sizeHint() const override; private slots: - void on_actionOpen_triggered(); - - void on_actionActualSize_triggered(); - void on_actionToggleMaximize_triggered(); - void on_actionZoomIn_triggered(); - void on_actionZoomOut_triggered(); - void on_actionToggleCheckerboard_triggered(); - void on_actionRotateClockwise_triggered(); - void on_actionRotateCounterClockwise_triggered(); + void on_actionCopyPixmap_triggered(); + void on_actionCopyFilePath_triggered(); void on_actionPrevPicture_triggered(); void on_actionNextPicture_triggered(); + void on_actionActualSize_triggered(); + void on_actionFitToScreen_triggered(); + void on_actionFitByWidth_triggered(); + void on_actionFitByHeight_triggered(); + void on_actionZoomIn_triggered(); + void on_actionZoomOut_triggered(); + + void on_actionFlipHorizontal_triggered(); + void on_actionFlipVertical_triggered(); + void on_actionRotateClockwise_triggered(); + void on_actionRotateCounterClockwise_triggered(); + + void on_actionToggleAvoidResetTransform_triggered(); + void on_actionToggleCheckerboard_triggered(); void on_actionTogglePauseAnimation_triggered(); void on_actionAnimationNextFrame_triggered(); - void on_actionHorizontalFlip_triggered(); - void on_actionFitInView_triggered(); - void on_actionFitByWidth_triggered(); - void on_actionCopyPixmap_triggered(); - void on_actionCopyFilePath_triggered(); - void on_actionPaste_triggered(); void on_actionTrash_triggered(); - void on_actionToggleAvoidResetTransform_triggered(); + void on_actionLocateInFileManager_triggered(); + void on_actionProperties_triggered(); + void on_actionSettings_triggered(); void on_actionHelp_triggered(); - void on_actionProperties_triggered(); - void on_actionLocateInFileManager_triggered(); - void on_actionQuitApp_triggered(); private: bool updateFileWatcher(const QString & basePath = QString()); - void updateGalleryButtonsVisibility(); private: ActionManager *m_am;