feat: auto long image mode
This commit is contained in:
@ -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()
|
||||||
|
@ -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;
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
{
|
{
|
||||||
|
// 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) {
|
if (m_mainView != nullptr) {
|
||||||
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect()));
|
m_viewportRegion = mapFromScene(m_mainView->mapToScene(m_mainView->rect()));
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
void NavigatorView::mousePressEvent(QMouseEvent *event)
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user