diff --git a/app/graphicsview.cpp b/app/graphicsview.cpp index a5a5681..df6f0b5 100644 --- a/app/graphicsview.cpp +++ b/app/graphicsview.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Gary Wang +// SPDX-FileCopyrightText: 2025 Gary Wang // // SPDX-License-Identifier: MIT @@ -124,6 +124,7 @@ void GraphicsView::resetTransform() void GraphicsView::zoomView(qreal scaleFactor) { m_enableFitInView = false; + m_longImageMode = false; scale(scaleFactor, scaleFactor); applyTransformationModeByScaleFactor(); emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); @@ -140,6 +141,10 @@ void GraphicsView::rotateView(bool clockwise) 0, 0, 1); tf = transform() * tf; setTransform(tf); + + // Apply transformation mode but don't emit navigator signal here + // Let displayScene() handle the navigator visibility correctly + applyTransformationModeByScaleFactor(); } void GraphicsView::flipView(bool horizontal) @@ -261,6 +266,69 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly) emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); } +bool GraphicsView::isLongImage() const +{ + // Get the transformed image size (considering rotation and other transforms) + QRectF transformedRect = transform().mapRect(sceneRect()); + QSizeF imageSize = transformedRect.size(); + + if (imageSize.isEmpty()) return false; + + qreal aspectRatio = imageSize.width() / imageSize.height(); + + // Check if aspect ratio exceeds 5:2 (wide) or 2:5 (tall) + return aspectRatio > 2.5 || aspectRatio < 0.4; +} + +bool GraphicsView::shouldEnterLongImageMode() const +{ + // Check if long image mode is enabled in settings + if (!Settings::instance()->autoLongImageMode()) return false; + + // Check if image is a long image + if (!isLongImage()) return false; + + // Check if transformed image size is larger than the size of the view + QSizeF imageSize = transform().mapRect(sceneRect()).size(); + QSizeF viewSize = viewport()->size(); + + return imageSize.width() > viewSize.width() || imageSize.height() > viewSize.height(); +} + +void GraphicsView::applyLongImageMode() +{ + if (!shouldEnterLongImageMode()) { + m_longImageMode = false; + return; + } + + m_longImageMode = true; + applyLongImageModeDirect(); +} + +void GraphicsView::applyLongImageModeDirect() +{ + // Determine image orientation based on current transform + QRectF transformedRect = transform().mapRect(sceneRect()); + qreal aspectRatio = transformedRect.width() / transformedRect.height(); + bool isTallImage = aspectRatio < 0.4; + bool isWideImage = aspectRatio > 2.5; + + // Use fitByOrientation with the migrated logic + if (isTallImage) { + // Tall image (height >> width): fit by width + fitByOrientation(Qt::Horizontal, true); // scaleDownOnly = true + } else if (isWideImage) { + // Wide image (width >> height): fit by height + fitByOrientation(Qt::Vertical, true); // scaleDownOnly = true + } +} + +bool GraphicsView::isInLongImageMode() const +{ + return m_longImageMode; +} + void GraphicsView::displayScene() { if (shouldAvoidTransform()) { @@ -268,8 +336,23 @@ void GraphicsView::displayScene() return; } + // Check if should apply long image mode + if (shouldEnterLongImageMode()) { + applyLongImageMode(); + m_firstUserMediaLoaded = true; + return; + } + + // Not in long image mode + m_longImageMode = false; + if (isSceneBiggerThanView()) { fitInView(sceneRect(), Qt::KeepAspectRatio); + // After fitInView, the image should fit the window, so hide navigator + emit navigatorViewRequired(false, transform()); + } else { + // Image is already smaller than window, no navigator needed + emit navigatorViewRequired(false, transform()); } m_enableFitInView = true; @@ -291,6 +374,11 @@ void GraphicsView::setEnableAutoFitInView(bool enable) m_enableFitInView = enable; } +void GraphicsView::setLongImageMode(bool enable) +{ + m_longImageMode = enable; +} + bool GraphicsView::avoidResetTransform() const { return m_avoidResetTransform; @@ -363,7 +451,12 @@ void GraphicsView::wheelEvent(QWheelEvent *event) void GraphicsView::resizeEvent(QResizeEvent *event) { - if (m_enableFitInView) { + if (m_longImageMode) { + // In long image mode, reapply long image logic on resize + // We directly apply the long image mode logic without rechecking + // if we should enter long image mode, as the mode is already active + applyLongImageModeDirect(); + } else if (m_enableFitInView) { bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform())); if (originalSizeSmallerThanWindow && scaleFactor() >= 1) { // no longer need to do fitInView() diff --git a/app/graphicsview.h b/app/graphicsview.h index f20d0ec..c098aa9 100644 --- a/app/graphicsview.h +++ b/app/graphicsview.h @@ -39,12 +39,20 @@ public: void displayScene(); bool isSceneBiggerThanView() const; void setEnableAutoFitInView(bool enable = true); + void setLongImageMode(bool enable = true); bool avoidResetTransform() const; void setAvoidResetTransform(bool avoidReset); static QTransform resetScale(const QTransform & orig); + // Long image mode support + bool isLongImage() const; + bool shouldEnterLongImageMode() const; + void applyLongImageMode(); + void applyLongImageModeDirect(); + bool isInLongImageMode() const; + signals: void navigatorViewRequired(bool required, QTransform transform); void viewportRectChanged(); @@ -70,6 +78,7 @@ private: // ... 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_longImageMode = false; bool m_avoidResetTransform = false; bool m_checkerboardEnabled = false; bool m_useLightCheckerboard = false; diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index dcdc5ae..6bb0eb6 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -700,6 +700,7 @@ void MainWindow::on_actionActualSize_triggered() { m_graphicsView->resetScale(); m_graphicsView->setEnableAutoFitInView(false); + m_graphicsView->setLongImageMode(false); } void MainWindow::on_actionToggleMaximize_triggered() @@ -728,6 +729,7 @@ void MainWindow::on_actionFitInView_triggered() { m_graphicsView->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio); m_graphicsView->setEnableAutoFitInView(m_graphicsView->scaleFactor() <= 1); + m_graphicsView->setLongImageMode(false); } void MainWindow::on_actionFitByWidth_triggered() @@ -813,14 +815,12 @@ void MainWindow::on_actionRotateClockwise_triggered() { m_graphicsView->rotateView(); m_graphicsView->displayScene(); - m_gv->setVisible(false); } void MainWindow::on_actionRotateCounterClockwise_triggered() { m_graphicsView->rotateView(false); m_graphicsView->displayScene(); - m_gv->setVisible(false); } void MainWindow::on_actionPrevPicture_triggered() diff --git a/app/navigatorview.cpp b/app/navigatorview.cpp index bc56ce4..e0e7e12 100644 --- a/app/navigatorview.cpp +++ b/app/navigatorview.cpp @@ -9,6 +9,7 @@ #include #include +#include NavigatorView::NavigatorView(QWidget *parent) : QGraphicsView (parent) @@ -34,10 +35,14 @@ void NavigatorView::setOpacity(qreal opacity, bool animated) void NavigatorView::updateMainViewportRegion() { - if (m_mainView != nullptr) { - m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect())); - update(); - } + // Use QTimer::singleShot with lambda to delay the update + // This ensures all geometry updates are complete before calculating viewport region + QTimer::singleShot(0, [this]() { + if (m_mainView != nullptr) { + m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect())); + update(); + } + }); } void NavigatorView::mousePressEvent(QMouseEvent *event) diff --git a/app/settings.cpp b/app/settings.cpp index d8f2b43..ad13b22 100644 --- a/app/settings.cpp +++ b/app/settings.cpp @@ -65,6 +65,11 @@ bool Settings::loopGallery() const return m_qsettings->value("loop_gallery", true).toBool(); } +bool Settings::autoLongImageMode() const +{ + return m_qsettings->value("auto_long_image_mode", true).toBool(); +} + Settings::DoubleClickBehavior Settings::doubleClickBehavior() const { QString result = m_qsettings->value("double_click_behavior", "Close").toString(); @@ -117,6 +122,12 @@ void Settings::setLoopGallery(bool on) m_qsettings->sync(); } +void Settings::setAutoLongImageMode(bool on) +{ + m_qsettings->setValue("auto_long_image_mode", on); + m_qsettings->sync(); +} + void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb) { m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb)); diff --git a/app/settings.h b/app/settings.h index 2ba1efa..90cac60 100644 --- a/app/settings.h +++ b/app/settings.h @@ -38,6 +38,7 @@ public: bool useBuiltInCloseAnimation() const; bool useLightCheckerboard() const; bool loopGallery() const; + bool autoLongImageMode() const; DoubleClickBehavior doubleClickBehavior() const; MouseWheelBehavior mouseWheelBehavior() const; WindowSizeBehavior initWindowSizeBehavior() const; @@ -47,6 +48,7 @@ public: void setUseBuiltInCloseAnimation(bool on); void setUseLightCheckerboard(bool light); void setLoopGallery(bool on); + void setAutoLongImageMode(bool on); void setDoubleClickBehavior(DoubleClickBehavior dcb); void setMouseWheelBehavior(MouseWheelBehavior mwb); void setInitWindowSizeBehavior(WindowSizeBehavior wsb); diff --git a/app/settingsdialog.cpp b/app/settingsdialog.cpp index e7a2957..aa8131d 100644 --- a/app/settingsdialog.cpp +++ b/app/settingsdialog.cpp @@ -23,6 +23,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) , m_useBuiltInCloseAnimation(new QCheckBox) , m_useLightCheckerboard(new QCheckBox) , m_loopGallery(new QCheckBox) + , m_autoLongImageMode(new QCheckBox) , m_doubleClickBehavior(new QComboBox) , m_mouseWheelBehavior(new QComboBox) , m_initWindowSizeBehavior(new QComboBox) @@ -123,6 +124,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) settingsForm->addRow(tr("Use built-in close window animation"), m_useBuiltInCloseAnimation); settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard); settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery); + settingsForm->addRow(tr("Auto long image mode"), m_autoLongImageMode); settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior); settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior); settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior); @@ -132,6 +134,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation()); m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard()); m_loopGallery->setChecked(Settings::instance()->loopGallery()); + m_autoLongImageMode->setChecked(Settings::instance()->autoLongImageMode()); m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown)); Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior(); m_doubleClickBehavior->setCurrentIndex(static_cast(dcb)); @@ -174,6 +177,10 @@ SettingsDialog::SettingsDialog(QWidget *parent) Settings::instance()->setLoopGallery(state == Qt::Checked); }); + connect(m_autoLongImageMode, &QCHECKBOX_CHECKSTATECHANGED, this, [ = ](QT_CHECKSTATE state){ + Settings::instance()->setAutoLongImageMode(state == Qt::Checked); + }); + connect(m_doubleClickBehavior, QOverload::of(&QComboBox::currentIndexChanged), this, [ = ](int index){ Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first); }); diff --git a/app/settingsdialog.h b/app/settingsdialog.h index f11c650..d7443b6 100644 --- a/app/settingsdialog.h +++ b/app/settingsdialog.h @@ -26,6 +26,7 @@ private: QCheckBox * m_useBuiltInCloseAnimation = nullptr; QCheckBox * m_useLightCheckerboard = nullptr; QCheckBox * m_loopGallery = nullptr; + QCheckBox * m_autoLongImageMode = nullptr; QComboBox * m_doubleClickBehavior = nullptr; QComboBox * m_mouseWheelBehavior = nullptr; QComboBox * m_initWindowSizeBehavior = nullptr;