feat: auto long image mode

This commit is contained in:
2025-07-26 15:33:02 +08:00
parent f0ed9d0ca1
commit f8d3dcc899
8 changed files with 136 additions and 8 deletions

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2022 Gary Wang <wzc782970009@gmail.com> // SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@ -124,6 +124,7 @@ void GraphicsView::resetTransform()
void GraphicsView::zoomView(qreal scaleFactor) void GraphicsView::zoomView(qreal scaleFactor)
{ {
m_enableFitInView = false; m_enableFitInView = false;
m_longImageMode = false;
scale(scaleFactor, scaleFactor); scale(scaleFactor, scaleFactor);
applyTransformationModeByScaleFactor(); applyTransformationModeByScaleFactor();
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform());
@ -140,6 +141,10 @@ void GraphicsView::rotateView(bool clockwise)
0, 0, 1); 0, 0, 1);
tf = transform() * tf; tf = transform() * tf;
setTransform(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) void GraphicsView::flipView(bool horizontal)
@ -261,6 +266,69 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
emit navigatorViewRequired(!isThingSmallerThanWindowWith(transform()), transform()); 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() void GraphicsView::displayScene()
{ {
if (shouldAvoidTransform()) { if (shouldAvoidTransform()) {
@ -268,8 +336,23 @@ void GraphicsView::displayScene()
return; 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()) { if (isSceneBiggerThanView()) {
fitInView(sceneRect(), Qt::KeepAspectRatio); 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; m_enableFitInView = true;
@ -291,6 +374,11 @@ void GraphicsView::setEnableAutoFitInView(bool enable)
m_enableFitInView = enable; m_enableFitInView = enable;
} }
void GraphicsView::setLongImageMode(bool enable)
{
m_longImageMode = enable;
}
bool GraphicsView::avoidResetTransform() const bool GraphicsView::avoidResetTransform() const
{ {
return m_avoidResetTransform; return m_avoidResetTransform;
@ -363,7 +451,12 @@ void GraphicsView::wheelEvent(QWheelEvent *event)
void GraphicsView::resizeEvent(QResizeEvent *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())); bool originalSizeSmallerThanWindow = isThingSmallerThanWindowWith(resetScale(transform()));
if (originalSizeSmallerThanWindow && scaleFactor() >= 1) { if (originalSizeSmallerThanWindow && scaleFactor() >= 1) {
// no longer need to do fitInView() // no longer need to do fitInView()

View File

@ -39,12 +39,20 @@ public:
void displayScene(); void displayScene();
bool isSceneBiggerThanView() const; bool isSceneBiggerThanView() const;
void setEnableAutoFitInView(bool enable = true); void setEnableAutoFitInView(bool enable = true);
void setLongImageMode(bool enable = true);
bool avoidResetTransform() const; bool avoidResetTransform() const;
void setAvoidResetTransform(bool avoidReset); void setAvoidResetTransform(bool avoidReset);
static QTransform resetScale(const QTransform & orig); static QTransform resetScale(const QTransform & orig);
// Long image mode support
bool isLongImage() const;
bool shouldEnterLongImageMode() const;
void applyLongImageMode();
void applyLongImageModeDirect();
bool isInLongImageMode() const;
signals: signals:
void navigatorViewRequired(bool required, QTransform transform); void navigatorViewRequired(bool required, QTransform transform);
void viewportRectChanged(); void viewportRectChanged();
@ -70,6 +78,7 @@ private:
// ... or even more? e.g. "fit/snap width" things... // ... 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. // 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_enableFitInView = false;
bool m_longImageMode = false;
bool m_avoidResetTransform = false; bool m_avoidResetTransform = false;
bool m_checkerboardEnabled = false; bool m_checkerboardEnabled = false;
bool m_useLightCheckerboard = false; bool m_useLightCheckerboard = false;

View File

@ -700,6 +700,7 @@ void MainWindow::on_actionActualSize_triggered()
{ {
m_graphicsView->resetScale(); m_graphicsView->resetScale();
m_graphicsView->setEnableAutoFitInView(false); m_graphicsView->setEnableAutoFitInView(false);
m_graphicsView->setLongImageMode(false);
} }
void MainWindow::on_actionToggleMaximize_triggered() void MainWindow::on_actionToggleMaximize_triggered()
@ -728,6 +729,7 @@ void MainWindow::on_actionFitInView_triggered()
{ {
m_graphicsView->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio); m_graphicsView->fitInView(m_gv->sceneRect(), Qt::KeepAspectRatio);
m_graphicsView->setEnableAutoFitInView(m_graphicsView->scaleFactor() <= 1); m_graphicsView->setEnableAutoFitInView(m_graphicsView->scaleFactor() <= 1);
m_graphicsView->setLongImageMode(false);
} }
void MainWindow::on_actionFitByWidth_triggered() void MainWindow::on_actionFitByWidth_triggered()
@ -813,14 +815,12 @@ void MainWindow::on_actionRotateClockwise_triggered()
{ {
m_graphicsView->rotateView(); m_graphicsView->rotateView();
m_graphicsView->displayScene(); m_graphicsView->displayScene();
m_gv->setVisible(false);
} }
void MainWindow::on_actionRotateCounterClockwise_triggered() void MainWindow::on_actionRotateCounterClockwise_triggered()
{ {
m_graphicsView->rotateView(false); m_graphicsView->rotateView(false);
m_graphicsView->displayScene(); m_graphicsView->displayScene();
m_gv->setVisible(false);
} }
void MainWindow::on_actionPrevPicture_triggered() void MainWindow::on_actionPrevPicture_triggered()

View File

@ -9,6 +9,7 @@
#include <QMouseEvent> #include <QMouseEvent>
#include <QDebug> #include <QDebug>
#include <QTimer>
NavigatorView::NavigatorView(QWidget *parent) NavigatorView::NavigatorView(QWidget *parent)
: QGraphicsView (parent) : QGraphicsView (parent)
@ -34,10 +35,14 @@ void NavigatorView::setOpacity(qreal opacity, bool animated)
void NavigatorView::updateMainViewportRegion() void NavigatorView::updateMainViewportRegion()
{ {
if (m_mainView != nullptr) { // Use QTimer::singleShot with lambda to delay the update
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect())); // This ensures all geometry updates are complete before calculating viewport region
update(); QTimer::singleShot(0, [this]() {
} if (m_mainView != nullptr) {
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect()));
update();
}
});
} }
void NavigatorView::mousePressEvent(QMouseEvent *event) void NavigatorView::mousePressEvent(QMouseEvent *event)

View File

@ -65,6 +65,11 @@ bool Settings::loopGallery() const
return m_qsettings->value("loop_gallery", true).toBool(); 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 Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
{ {
QString result = m_qsettings->value("double_click_behavior", "Close").toString(); QString result = m_qsettings->value("double_click_behavior", "Close").toString();
@ -117,6 +122,12 @@ void Settings::setLoopGallery(bool on)
m_qsettings->sync(); m_qsettings->sync();
} }
void Settings::setAutoLongImageMode(bool on)
{
m_qsettings->setValue("auto_long_image_mode", on);
m_qsettings->sync();
}
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb) void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
{ {
m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb)); m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb));

View File

@ -38,6 +38,7 @@ public:
bool useBuiltInCloseAnimation() const; bool useBuiltInCloseAnimation() const;
bool useLightCheckerboard() const; bool useLightCheckerboard() const;
bool loopGallery() const; bool loopGallery() const;
bool autoLongImageMode() const;
DoubleClickBehavior doubleClickBehavior() const; DoubleClickBehavior doubleClickBehavior() const;
MouseWheelBehavior mouseWheelBehavior() const; MouseWheelBehavior mouseWheelBehavior() const;
WindowSizeBehavior initWindowSizeBehavior() const; WindowSizeBehavior initWindowSizeBehavior() const;
@ -47,6 +48,7 @@ public:
void setUseBuiltInCloseAnimation(bool on); void setUseBuiltInCloseAnimation(bool on);
void setUseLightCheckerboard(bool light); void setUseLightCheckerboard(bool light);
void setLoopGallery(bool on); void setLoopGallery(bool on);
void setAutoLongImageMode(bool on);
void setDoubleClickBehavior(DoubleClickBehavior dcb); void setDoubleClickBehavior(DoubleClickBehavior dcb);
void setMouseWheelBehavior(MouseWheelBehavior mwb); void setMouseWheelBehavior(MouseWheelBehavior mwb);
void setInitWindowSizeBehavior(WindowSizeBehavior wsb); void setInitWindowSizeBehavior(WindowSizeBehavior wsb);

View File

@ -23,6 +23,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
, m_useBuiltInCloseAnimation(new QCheckBox) , m_useBuiltInCloseAnimation(new QCheckBox)
, m_useLightCheckerboard(new QCheckBox) , m_useLightCheckerboard(new QCheckBox)
, m_loopGallery(new QCheckBox) , m_loopGallery(new QCheckBox)
, m_autoLongImageMode(new QCheckBox)
, m_doubleClickBehavior(new QComboBox) , m_doubleClickBehavior(new QComboBox)
, m_mouseWheelBehavior(new QComboBox) , m_mouseWheelBehavior(new QComboBox)
, m_initWindowSizeBehavior(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 built-in close window animation"), m_useBuiltInCloseAnimation);
settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard); settingsForm->addRow(tr("Use light-color checkerboard"), m_useLightCheckerboard);
settingsForm->addRow(tr("Loop the loaded gallery"), m_loopGallery); 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("Double-click behavior"), m_doubleClickBehavior);
settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior); settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior);
settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior); settingsForm->addRow(tr("Default window size"), m_initWindowSizeBehavior);
@ -132,6 +134,7 @@ SettingsDialog::SettingsDialog(QWidget *parent)
m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation()); m_useBuiltInCloseAnimation->setChecked(Settings::instance()->useBuiltInCloseAnimation());
m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard()); m_useLightCheckerboard->setChecked(Settings::instance()->useLightCheckerboard());
m_loopGallery->setChecked(Settings::instance()->loopGallery()); m_loopGallery->setChecked(Settings::instance()->loopGallery());
m_autoLongImageMode->setChecked(Settings::instance()->autoLongImageMode());
m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown)); m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown));
Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior(); Settings::DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb)); m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
@ -174,6 +177,10 @@ SettingsDialog::SettingsDialog(QWidget *parent)
Settings::instance()->setLoopGallery(state == Qt::Checked); 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<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){ connect(m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first); Settings::instance()->setDoubleClickBehavior(_dc_options.at(index).first);
}); });

View File

@ -26,6 +26,7 @@ private:
QCheckBox * m_useBuiltInCloseAnimation = nullptr; QCheckBox * m_useBuiltInCloseAnimation = nullptr;
QCheckBox * m_useLightCheckerboard = nullptr; QCheckBox * m_useLightCheckerboard = nullptr;
QCheckBox * m_loopGallery = nullptr; QCheckBox * m_loopGallery = nullptr;
QCheckBox * m_autoLongImageMode = nullptr;
QComboBox * m_doubleClickBehavior = nullptr; QComboBox * m_doubleClickBehavior = nullptr;
QComboBox * m_mouseWheelBehavior = nullptr; QComboBox * m_mouseWheelBehavior = nullptr;
QComboBox * m_initWindowSizeBehavior = nullptr; QComboBox * m_initWindowSizeBehavior = nullptr;