Compare commits

..
9 Commits
Author SHA1 Message Date
yyc12345 e01718928c feat: remove checkerboard icon and change bottom toolbar 2026-07-25 12:23:00 +08:00
yyc12345 f5dbf44ec7 refactor: add possible optimization in graphics view 2026-07-25 12:15:17 +08:00
yyc12345 f55f8c731d refactor: use extensible design for fetching playlist from single file 2026-07-25 12:06:11 +08:00
yyc12345 4ed491c78a refactor: remove closeWindow because no one need it 2026-07-24 15:17:05 +08:00
yyc12345 b54cde2cbf refactor: remove "auto" in window-size behavior
- "auto" is not essential for our hard fork, so i simply remove it.
- remove all useless code after removing it.
- enlarge the min size of window because previous may trigger bad layout when showing navigation view.
2026-07-24 15:14:24 +08:00
yyc12345 b1d12cc406 refactor: remove double-click-window behavior
- remove double-click-window behavior entirely
- remove the icon of copy image action
2026-07-24 15:07:30 +08:00
yyc12345 aa13355966 refactor: use UI file for main window
- main window now use UI file for layout.
- remove all manual computed widget position code. use full Qt layout implementation instead.
- remove useless functions in main window
- change style changed slot as private
2026-07-24 14:57:41 +08:00
yyc12345 bd35a8c3e0 feat: use submenu in right click menu to reduce the height of menu panel 2026-07-15 21:59:30 +08:00
yyc12345 81413b51da feat: support explorer sort and hidden on Window
now playlist can use Windows Explorer's sort and hidden file display options to build it own for better browse experience on Windows
2026-07-14 13:22:23 +08:00
22 changed files with 781 additions and 299 deletions
+12
View File
@@ -53,6 +53,9 @@ set (PPIC_CPP_FILES
app/exiv2wrapper.cpp
app/playlistmanager.cpp
app/fileopeneventhandler.cpp
app/playlistscout.cpp
app/playlistscouts/qtscout.cpp
app/playlistscouts/explorerscout.cpp
)
set (PPIC_HEADER_FILES
@@ -70,12 +73,16 @@ set (PPIC_HEADER_FILES
app/exiv2wrapper.h
app/playlistmanager.h
app/fileopeneventhandler.h
app/playlistscout.h
app/playlistscouts/qtscout.h
app/playlistscouts/explorerscout.h
)
set (PPIC_UI_FILES
app/settingsdialog.ui
app/aboutdialog.ui
app/metadatadialog.ui
app/mainwindow.ui
)
set (PPIC_QRC_FILES
@@ -117,6 +124,11 @@ else()
qt_add_translations(${EXE_NAME} ${ADD_TRANSLATIONS_ADDITIONAL_ARGS} TS_FILES ${PPIC_TS_FILES} QM_FILES_OUTPUT_VARIABLE PPIC_QM_FILES)
endif()
# YYC MARK:
# This is essential, otherwise Qt UIC generated UI header file
# can not find our header files if there are custom widgets inside UI file.
target_include_directories(${EXE_NAME} PRIVATE app/)
target_sources(${EXE_NAME} PRIVATE ${PPIC_QM_FILES})
target_link_libraries (${EXE_NAME} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::SvgWidgets)
+2 -2
View File
@@ -55,7 +55,7 @@ void ActionManager::setupAction(MainWindow *mainWindow)
// 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, actionCopyPixmap);
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
CREATE_NEW_ICON_ACTION(mainWindow, actionPrevPicture, go-previous);
@@ -74,7 +74,7 @@ void ActionManager::setupAction(MainWindow *mainWindow)
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, actionToggleCheckerboard);
CREATE_NEW_ACTION(mainWindow, actionTogglePauseAnimation);
CREATE_NEW_ACTION(mainWindow, actionAnimationNextFrame);
+7 -1
View File
@@ -8,7 +8,7 @@
#include <QVBoxLayout>
#include <QDebug>
BottomButtonGroup::BottomButtonGroup(const std::vector<QAction *> &actionList, QWidget *parent)
BottomButtonGroup::BottomButtonGroup(QWidget *parent)
: QGroupBox (parent)
{
QHBoxLayout * mainLayout = new QHBoxLayout(this);
@@ -25,7 +25,9 @@ QToolButton {
border-style: none;
}
)#");
}
void BottomButtonGroup::addActions(const std::vector<QAction *> & actionList) {
for (QAction * action : actionList) {
// Create button
QToolButton * btn = new QToolButton(this);
@@ -37,3 +39,7 @@ QToolButton {
}
updateGeometry();
}
void BottomButtonGroup::addAction(QAction* action) {
addActions({action});
}
+4 -1
View File
@@ -14,7 +14,10 @@ class BottomButtonGroup : public QGroupBox
{
Q_OBJECT
public:
explicit BottomButtonGroup(const std::vector<QAction *> & actionList, QWidget *parent = nullptr);
explicit BottomButtonGroup(QWidget *parent = nullptr);
void addActions(const std::vector<QAction *> & actionList);
void addAction(QAction* action);
};
#endif // BOTTOMBUTTONGROUP_H
+1 -1
View File
@@ -38,7 +38,7 @@ public:
QPixmap renderToPixmap();
public slots:
private slots:
void updateStyle(Qt::ColorScheme colorScheme);
private:
+12 -1
View File
@@ -224,7 +224,18 @@ void GraphicsView::fitByOrientation(Qt::Orientation ori, bool scaleDownOnly)
QRectF originalScene = sceneRect();
QTransform currentTransform = transform();
// TODO: May be optimized by matrix arithmetic operation?
// TODO:
// I guess following code may be optimized by matrix arithmetic operation.
// There is an AI given solution. But I didn't use it.
// Because I want to keep the same behavior of upstream.
// // Horizontal fit: Let the position of top center of viewport, (cx, 0), be inversely mapped to scene.
// // And compensate the behavior of `centerOn` which put given point to the center of viewport.
// QTransform inv = currentTransform.inverted();
// QPointF viewCx = viewport()->rect().center().x();
// QPointF viewCy = viewport()->rect().center().y();
// // The target is viewport top center. Its y=0, so `centerOn` required scene point is (viewCx, -viewCy).
// centerOn(inv.map(QPointF(viewCx, -viewCy)));
if (ori == Qt::Horizontal) {
// For horizontal fit (fit by width), position at top (for tall images)
+1 -1
View File
@@ -52,7 +52,7 @@ signals:
void navigatorViewRequired(bool required, QTransform transform);
void viewportRectChanged();
public slots:
private slots:
void updateStyle(Qt::ColorScheme colorScheme);
private:
+94 -189
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "settings.h"
#include "bottombuttongroup.h"
@@ -37,7 +38,6 @@
#include <QProcess>
#include <QDesktopServices>
#include <QMessageBox>
#include <QVBoxLayout>
#ifdef HAVE_QTDBUS
#include <QDBusInterface>
@@ -48,56 +48,49 @@ using namespace Qt::Literals::StringLiterals;
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
, ui(new QT_PREPEND_NAMESPACE(Ui::MainWindow))
, m_am(new ActionManager)
, m_pm(new PlaylistManager(this))
, m_fileSystemWatcher(new QFileSystemWatcher(this))
{
ui->setupUi(this);
this->setWindowFlags(Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint);
m_centralLayout = new QVBoxLayout(this);
// YYC MARK:
// 72 px is the height of Windows Photo Viewer's bottom line.
m_centralLayout->setContentsMargins(QMargins(0, 0, 0, 72)); // TODO: This value may be declared as a constant.
// YYC MARK:
// Blumis set original value is 350 x 330.
// It is too small for modern device,
// so I level it up to standard VGA resolution 640 x 480.
this->setMinimumSize(640, 480);
// so I level it up to SVGA resolution 800 x 600.
this->setMinimumSize(800, 600);
this->setWindowIcon(QIcon(u":/icons/app-icon.svg"_s));
this->setAcceptDrops(true);
// Initialize playlist manager
m_pm->setAllowedSuffixes(supportedImageFormats());
// Initialize graphics scene
GraphicsScene * scene = new GraphicsScene(this);
m_gv = new GraphicsView(this);
m_gv->setScene(scene);
m_centralLayout->addWidget(m_gv);
m_nav = new NavigatorView(this);
// YYC MARK:
// Blumia set original value is 220 x 160.
// So if we set its height to 72 for respecting the height of Windows Photo Viewer,
// the width should be set to 99 for keeping aspect ratio.
m_nav->setFixedSize(99, 72);
m_nav->setScene(scene);
m_nav->setMainView(m_gv);
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
connect(m_gv, &GraphicsView::navigatorViewRequired,
// Setup graphics view (main photo view)
ui->m_gv->setScene(scene);
// Setup navigator view (right-bottom corner thumbnail view)
ui->m_nav->setScene(scene);
ui->m_nav->setMainView(ui->m_gv);
ui->m_nav->fitInView(ui->m_nav->sceneRect(), Qt::KeepAspectRatio);
// Binding graphics and navigator views signals
connect(ui->m_gv, &GraphicsView::navigatorViewRequired,
this, [this](bool required, const QTransform & tf){
m_nav->setTransform(GraphicsView::resetScale(tf));
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
m_nav->setVisible(required);
m_nav->updateMainViewportRegion();
ui->m_nav->setTransform(GraphicsView::resetScale(tf));
ui->m_nav->fitInView(ui->m_nav->sceneRect(), Qt::KeepAspectRatio);
ui->m_nav->setVisible(required);
ui->m_navPH->setVisible(required);
ui->m_nav->updateMainViewportRegion();
});
connect(ui->m_gv, &GraphicsView::viewportRectChanged,
ui->m_nav, &NavigatorView::updateMainViewportRegion);
connect(m_gv, &GraphicsView::viewportRectChanged,
m_nav, &NavigatorView::updateMainViewportRegion);
// Setup action manager
m_am->setupAction(this);
m_bottomButtonGroup = new BottomButtonGroup({
// Setup bottom button group
ui->m_bottomButtonGroup->addActions({
m_am->actionActualSize,
m_am->actionFitToScreen,
m_am->actionZoomIn,
@@ -108,55 +101,60 @@ MainWindow::MainWindow(QWidget *parent)
m_am->actionRotateCounterClockwise,
m_am->actionRotateClockwise,
m_am->actionFlipHorizontal,
m_am->actionToggleCheckerboard,
}, this);
m_menu = new QMenu(this);
m_am->actionFlipVertical,
});
// Setup right click menus
{
m_menu->addAction(m_am->actionCopyPixmap);
m_menu->addAction(m_am->actionCopyFilePath);
ui->m_subMenuCopy->addAction(m_am->actionCopyPixmap);
ui->m_subMenuCopy->addAction(m_am->actionCopyFilePath);
}
{
ui->m_subMenuTransform->addAction(m_am->actionActualSize);
ui->m_subMenuTransform->addAction(m_am->actionFitToScreen);
ui->m_subMenuTransform->addAction(m_am->actionFitByWidth);
ui->m_subMenuTransform->addAction(m_am->actionFitByHeight);
ui->m_subMenuTransform->addAction(m_am->actionZoomIn);
ui->m_subMenuTransform->addAction(m_am->actionZoomOut);
m_menu->addSeparator();
ui->m_subMenuTransform->addSeparator();
m_menu->addAction(m_am->actionPrevPicture);
m_menu->addAction(m_am->actionNextPicture);
ui->m_subMenuTransform->addAction(m_am->actionFlipHorizontal);
ui->m_subMenuTransform->addAction(m_am->actionFlipVertical);
ui->m_subMenuTransform->addAction(m_am->actionRotateClockwise);
ui->m_subMenuTransform->addAction(m_am->actionRotateCounterClockwise);
}
{
ui->m_menu->addMenu(ui->m_subMenuCopy);
m_menu->addSeparator();
ui->m_menu->addSeparator();
m_menu->addAction(m_am->actionActualSize);
m_menu->addAction(m_am->actionFitToScreen);
m_menu->addAction(m_am->actionFitByWidth);
m_menu->addAction(m_am->actionFitByHeight);
m_menu->addAction(m_am->actionZoomIn);
m_menu->addAction(m_am->actionZoomOut);
ui->m_menu->addAction(m_am->actionPrevPicture);
ui->m_menu->addAction(m_am->actionNextPicture);
m_menu->addSeparator();
ui->m_menu->addSeparator();
m_menu->addAction(m_am->actionFlipHorizontal);
m_menu->addAction(m_am->actionFlipVertical);
m_menu->addAction(m_am->actionRotateClockwise);
m_menu->addAction(m_am->actionRotateCounterClockwise);
ui->m_menu->addMenu(ui->m_subMenuTransform);
m_menu->addSeparator();
ui->m_menu->addSeparator();
m_menu->addAction(m_am->actionToggleAvoidResetTransform);
m_menu->addAction(m_am->actionToggleCheckerboard);
m_menu->addAction(m_am->actionTogglePauseAnimation);
m_menu->addAction(m_am->actionAnimationNextFrame);
ui->m_menu->addAction(m_am->actionToggleAvoidResetTransform);
ui->m_menu->addAction(m_am->actionToggleCheckerboard);
ui->m_menu->addAction(m_am->actionTogglePauseAnimation);
ui->m_menu->addAction(m_am->actionAnimationNextFrame);
m_menu->addSeparator();
ui->m_menu->addSeparator();
m_menu->addAction(m_am->actionTrash);
m_menu->addAction(m_am->actionLocateInFileManager);
m_menu->addAction(m_am->actionProperties);
ui->m_menu->addAction(m_am->actionTrash);
ui->m_menu->addAction(m_am->actionLocateInFileManager);
ui->m_menu->addAction(m_am->actionProperties);
m_menu->addSeparator();
ui->m_menu->addSeparator();
m_menu->addAction(m_am->actionSettings);
m_menu->addAction(m_am->actionHelp);
ui->m_menu->addAction(m_am->actionSettings);
ui->m_menu->addAction(m_am->actionHelp);
}
// Connect playlist manager and file watcher signals for photo browsering.
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::updateActionState, this));
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::galleryCurrent, this, false));
@@ -166,10 +164,7 @@ MainWindow::MainWindow(QWidget *parent)
QTimer::singleShot(500, this, std::bind(&MainWindow::galleryCurrent, this, true));
});
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
connect(fullscreenShorucut, &QShortcut::activated,
this, &MainWindow::toggleFullscreen);
// Change window location
centerWindow();
QTimer::singleShot(0, this, [this](){
@@ -180,66 +175,31 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::showFiles(const QStringList &files)
{
m_pm->loadPlaylist(files);
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
ui->m_nav->fitInView(ui->m_nav->sceneRect(), Qt::KeepAspectRatio);
}
void MainWindow::showFiles(const QList<QUrl>& urls)
{
m_pm->loadPlaylist(urls);
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
ui->m_nav->fitInView(ui->m_nav->sceneRect(), Qt::KeepAspectRatio);
}
void MainWindow::initWindowSize()
{
switch (Settings::instance()->initWindowSizeBehavior()) {
case Settings::WindowSizeBehavior::Auto:
adjustWindowSizeBySceneRect();
break;
case Settings::WindowSizeBehavior::Maximized:
showMaximized();
break;
case Settings::WindowSizeBehavior::Windowed:
default:
showNormal();
break;
default:
adjustWindowSizeBySceneRect();
break;
}
}
void MainWindow::adjustWindowSizeBySceneRect()
{
if (m_pm->totalCount() < 1) return;
QSize sceneSize = m_gv->sceneRect().toRect().size();
QSize sceneSizeWithMargins = sceneSize + QSize(130, 125);
if (m_gv->scaleFactor() < 1 || size().expandedTo(sceneSizeWithMargins) != size()) {
// if it scaled down by the resize policy:
QSize screenSize = qApp->screenAt(QCursor::pos())->availableSize();
if (screenSize.expandedTo(sceneSize) == screenSize) {
// we can show the picture by increase the window size.
QSize finalSize = (screenSize.expandedTo(sceneSizeWithMargins) == screenSize) ?
sceneSizeWithMargins : screenSize;
// We have a very reasonable sizeHint() value ;P
this->resize(finalSize.expandedTo(this->sizeHint()));
// We're sure the window can display the whole thing with 1:1 scale.
// The old window size may cause fitInView call from resize() and the
// above resize() call won't reset the scale back to 1:1, so we
// just call resetScale() here to ensure the thing is no longer scaled.
m_gv->resetScale();
centerWindow();
} else {
// toggle maximum
showMaximized();
}
}
}
@@ -275,13 +235,13 @@ void MainWindow::galleryCurrent(bool fromFileWatcher)
auto index = m_pm->currentIndex();
if (index.has_value()) {
QString localFilePath = m_pm->localFileByIndex(*index);
m_gv->showFileFromPath(localFilePath);
ui->m_gv->showFileFromPath(localFilePath);
if (!fromFileWatcher) {
updateFileWatcher(localFilePath);
setWindowTitle(m_pm->urlByIndex(*index).fileName());
}
} else {
m_gv->showText(tr("Drag image here"));
ui->m_gv->showText(tr("Drag image here"));
setWindowTitle(QString());
updateFileWatcher();
}
@@ -302,43 +262,6 @@ QStringList MainWindow::supportedImageFormats()
return formatFilters;
}
void MainWindow::showEvent(QShowEvent *event)
{
updateWidgetsPosition();
return QWidget::showEvent(event);
}
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
// The forward/back mouse button can also used to trigger a mouse double-click event
// Since we use that for gallery navigation so we ignore these two buttons.
if (event->buttons() & Qt::ForwardButton || event->buttons() & Qt::BackButton) {
return;
}
switch (Settings::instance()->doubleClickBehavior()) {
case Settings::DoubleClickBehavior::Close:
closeWindow();
event->accept();
break;
case Settings::DoubleClickBehavior::Maximize:
toggleMaximize();
event->accept();
break;
case Settings::DoubleClickBehavior::FullScreen:
toggleFullscreen();
event->accept();
break;
case Settings::DoubleClickBehavior::Ignore:
break;
}
// blumia: don't call parent constructor here, seems it will cause mouse move
// event get called even if we set event->accept();
// return QMainWindow::mouseDoubleClickEvent(event);
}
void MainWindow::wheelEvent(QWheelEvent *event)
{
QPoint numDegrees = event->angleDelta() / 8;
@@ -375,16 +298,9 @@ void MainWindow::wheelEvent(QWheelEvent *event)
}
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
updateWidgetsPosition();
return QWidget::resizeEvent(event);
}
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
{
m_menu->exec(mapToGlobal(event->pos()));
ui->m_menu->exec(mapToGlobal(event->pos()));
return QWidget::contextMenuEvent(event);
}
@@ -413,7 +329,7 @@ void MainWindow::dropEvent(QDropEvent *event)
if (mimeData->hasUrls()) {
showFiles(mimeData->urls());
} else {
m_gv->showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
ui->m_gv->showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
}
}
@@ -429,20 +345,9 @@ void MainWindow::centerWindow()
);
}
void MainWindow::closeWindow()
{
}
void MainWindow::updateWidgetsPosition()
{
m_bottomButtonGroup->move((width() - m_bottomButtonGroup->width()) / 2,
(72 - m_bottomButtonGroup->height()) / 2 + (height() - 72));
m_nav->move(width() - m_nav->width(), height() - m_nav->height());
}
void MainWindow::toggleAvoidResetTransform()
{
m_gv->setAvoidResetTransform(!m_gv->isAvoidResetTransform());
ui->m_gv->setAvoidResetTransform(!ui->m_gv->isAvoidResetTransform());
}
void MainWindow::toggleFullscreen()
@@ -473,7 +378,7 @@ QSize MainWindow::sizeHint() const
void MainWindow::on_actionCopyPixmap_triggered()
{
QClipboard *cb = QApplication::clipboard();
cb->setPixmap(m_gv->scene()->renderToPixmap());
cb->setPixmap(ui->m_gv->scene()->renderToPixmap());
}
void MainWindow::on_actionCopyFilePath_triggered()
@@ -497,58 +402,58 @@ void MainWindow::on_actionNextPicture_triggered()
void MainWindow::on_actionActualSize_triggered()
{
m_gv->resetScale();
m_gv->setEnableAutoFitToScreen(false);
ui->m_gv->resetScale();
ui->m_gv->setEnableAutoFitToScreen(false);
}
void MainWindow::on_actionFitToScreen_triggered()
{
m_gv->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
m_gv->setEnableAutoFitToScreen(m_gv->scaleFactor() <= 1);
ui->m_gv->fitInView(ui->m_nav->sceneRect(), Qt::KeepAspectRatio);
ui->m_gv->setEnableAutoFitToScreen(ui->m_gv->scaleFactor() <= 1);
}
void MainWindow::on_actionFitByWidth_triggered()
{
m_gv->fitByOrientation(Qt::Orientation::Horizontal);
ui->m_gv->fitByOrientation(Qt::Orientation::Horizontal);
}
void MainWindow::on_actionFitByHeight_triggered()
{
m_gv->fitByOrientation(Qt::Orientation::Vertical);
ui->m_gv->fitByOrientation(Qt::Orientation::Vertical);
}
void MainWindow::on_actionZoomIn_triggered()
{
if (m_gv->scaleFactor() < 1000) {
m_gv->zoomView(1.25);
if (ui->m_gv->scaleFactor() < 1000) {
ui->m_gv->zoomView(1.25);
}
}
void MainWindow::on_actionZoomOut_triggered()
{
m_gv->zoomView(0.8);
ui->m_gv->zoomView(0.8);
}
void MainWindow::on_actionFlipHorizontal_triggered()
{
m_gv->flipView(true);
ui->m_gv->flipView(true);
}
void MainWindow::on_actionFlipVertical_triggered()
{
m_gv->flipView(false);
ui->m_gv->flipView(false);
}
void MainWindow::on_actionRotateClockwise_triggered()
{
m_gv->rotateView(true);
m_gv->displayScene();
ui->m_gv->rotateView(true);
ui->m_gv->displayScene();
}
void MainWindow::on_actionRotateCounterClockwise_triggered()
{
m_gv->rotateView(false);
m_gv->displayScene();
ui->m_gv->rotateView(false);
ui->m_gv->displayScene();
}
void MainWindow::on_actionToggleAvoidResetTransform_triggered()
@@ -558,17 +463,17 @@ void MainWindow::on_actionToggleAvoidResetTransform_triggered()
void MainWindow::on_actionToggleCheckerboard_triggered()
{
m_gv->toggleCheckerboard(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier));
ui->m_gv->toggleCheckerboard(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier));
}
void MainWindow::on_actionTogglePauseAnimation_triggered()
{
m_gv->scene()->togglePauseAnimation();
ui->m_gv->scene()->togglePauseAnimation();
}
void MainWindow::on_actionAnimationNextFrame_triggered()
{
m_gv->scene()->skipAnimationFrame(1);
ui->m_gv->scene()->skipAnimationFrame(1);
}
void MainWindow::on_actionTrash_triggered()
@@ -670,5 +575,5 @@ bool MainWindow::updateFileWatcher(const QString &basePath)
}
void MainWindow::updateActionState() {
m_am->updateActionState(m_pm, m_gv);
m_am->updateActionState(m_pm, ui->m_gv);
}
+7 -21
View File
@@ -5,22 +5,18 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QWidget>
QT_BEGIN_NAMESPACE
class QMenu;
class QVBoxLayout;
class QGraphicsView;
class QFileSystemWatcher;
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class ActionManager;
class PlaylistManager;
class GraphicsView;
class NavigatorView;
class BottomButtonGroup;
class MainWindow : public QWidget
{
Q_OBJECT
@@ -32,7 +28,6 @@ public:
void showFiles(const QStringList& files);
void showFiles(const QList<QUrl>& urls);
void initWindowSize();
void adjustWindowSizeBySceneRect();
void clearGallery();
void galleryPrev();
@@ -42,18 +37,13 @@ public:
static QStringList supportedImageFormats();
protected slots:
void showEvent(QShowEvent *event) override;
void mouseDoubleClickEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void centerWindow();
void closeWindow();
void updateWidgetsPosition();
void toggleAvoidResetTransform();
void toggleFullscreen();
void toggleMaximize();
@@ -97,15 +87,11 @@ private:
void updateActionState();
private:
QT_PREPEND_NAMESPACE(Ui::MainWindow) *ui;
ActionManager *m_am;
PlaylistManager *m_pm;
GraphicsView *m_gv;
NavigatorView *m_nav;
QMenu *m_menu;
QVBoxLayout * m_centralLayout = nullptr;
QFileSystemWatcher *m_fileSystemWatcher;
BottomButtonGroup *m_bottomButtonGroup;
};
#endif // MAINWINDOW_H
+172
View File
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QWidget" name="MainWindow">
<property name="windowModality">
<enum>Qt::NonModal</enum>
</property>
<property name="minimumSize">
<size>
<width>640</width>
<height>480</height>
</size>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="m_centralLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="GraphicsView" name="m_gv">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="bottomBar" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>72</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>72</height>
</size>
</property>
<layout class="QHBoxLayout" name="bottomBarLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="m_navPH" native="true">
<property name="minimumSize">
<size>
<width>99</width>
<height>72</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>99</width>
<height>72</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="leftSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="BottomButtonGroup" name="m_bottomButtonGroup"/>
</item>
<item>
<spacer name="rightSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="NavigatorView" name="m_nav">
<property name="minimumSize">
<size>
<width>99</width>
<height>72</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>99</width>
<height>72</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
<widget class="QMenu" name="m_subMenuCopy">
<property name="title">
<string>&amp;Copy</string>
</property>
<property name="icon">
<iconset theme="edit-copy"/>
</property>
</widget>
<widget class="QMenu" name="m_subMenuTransform">
<property name="title">
<string>&amp;Transform</string>
</property>
</widget>
<widget class="QMenu" name="m_menu"/>
</widget>
<customwidgets>
<customwidget>
<class>GraphicsView</class>
<extends>QGraphicsView</extends>
<header>graphicsview.h</header>
</customwidget>
<customwidget>
<class>NavigatorView</class>
<extends>QGraphicsView</extends>
<header>navigatorview.h</header>
</customwidget>
<customwidget>
<class>BottomButtonGroup</class>
<extends>QGroupBox</extends>
<header>bottombuttongroup.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+2
View File
@@ -18,6 +18,8 @@ public:
public slots:
void updateMainViewportRegion();
private slots:
void updateStyle(Qt::ColorScheme colorScheme);
private:
+7 -26
View File
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT
#include "playlistmanager.h"
#include "playlistscout.h"
#include <QCollator>
#include <QDir>
@@ -71,34 +72,14 @@ bool PlaylistManager::loadSingleFilePlaylist(const QUrl& url) {
if (!checkUrl(url))
return false;
QFileInfo info(url.toLocalFile());
QDir dir(info.path());
QString currentFileName = info.fileName();
QStringList entryList = dir.entryList(
m_allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
QCollator collator;
collator.setNumericMode(true);
std::sort(entryList.begin(), entryList.end(), collator);
QList<QUrl> playlist;
std::optional<qsizetype> idx = std::nullopt;
for (qsizetype i = 0; i < entryList.count(); i++) {
const QString &fileName = entryList.at(i);
const QString &oneEntry = dir.absoluteFilePath(fileName);
const QUrl &url = QUrl::fromLocalFile(oneEntry);
if (!checkUrl(url)) {
return false;
}
playlist.append(url);
if (fileName == currentFileName) {
idx = i;
}
// Scout playlist by given file
auto scoutResult = PlaylistScout::scout(url, m_allowedSuffixes);
if (!scoutResult.has_value()) {
return false;
}
setPlaylist(std::move(playlist), idx);
// Setup playlist with index
setPlaylist(std::move(scoutResult->playlist), scoutResult->indexHint);
return true;
}
+25
View File
@@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#include "playlistscout.h"
#include "playlistscouts/qtscout.h"
#include "playlistscouts/explorerscout.h"
std::optional<PlaylistScout::ScoutResult> PlaylistScout::scout(
const QUrl &url, const QStringList &allowedSuffixes) {
#ifdef Q_OS_WIN
// In Windows, Qt approach can not aware Windows Explorer custom sort order.
// So we need to try using Win32 functions for fetching it at first.
if (auto rv = PlaylistScout::Explorer::scout(url, allowedSuffixes); rv.has_value()) {
return rv;
}
#endif // Q_OS_WIN
// TODO: We may add macOS Finder, KDE Dolphin and etc supports in future.
// Qt way is the last fallback.
return PlaylistScout::Qt::scout(url, allowedSuffixes);
}
+31
View File
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include <QUrl>
#include <QStringList>
#include <QList>
#include <optional>
namespace PlaylistScout {
/// The result of scout
struct ScoutResult {
/// The final fetched playlist (it can be empty).
/// Each item must be a QUrl pointing to a local file.
QList<QUrl> playlist;
/// The index of given file located in playlist.
/// Nullopt if it is not presented in playlist.
std::optional<qsizetype> indexHint;
};
/// @brief Scout available image files in given file's parent dir with given allowed suffixes.
/// @param[in] url The path to file whose parent dir is our target dir for finding image files.
/// @param[in] allowedSuffixes
/// Each items in this list is a wildcard pattern for matching allowed file name, like \c *.png .
/// The fetched item will be preserved if its name matching any of them.
/// If this list is empty, there is no restrictions for finding files.
/// @return Scout result if success, otherwise nullopt.
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
+332
View File
@@ -0,0 +1,332 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#include "explorerscout.h"
#ifdef Q_OS_WIN
#include <QDir>
#include <QDebug>
#include <QFileInfo>
#include <QRegularExpression>
#include <optional>
#include <exdisp.h> // IShellWindows, CLSID_ShellWindows
#include <servprov.h> // IServiceProvider
#include <shlguid.h> // SID_STopLevelBrowser
#include <shlobj.h>
// YYC MARK:
// We use custom RAII wrappers instead of existing COM smart pointers because the
// project may be built with MinGW, which does not have good compatibility with
// CComPtr and Microsoft's WRL ComPtr (<wrl/client.h>).
// Furthermore, PIDLs are not COM objects (they are freed via ILFree, not Release),
// so no standard smart pointer covers them regardless of compiler.
/// RAII wrapper for COM initialization (CoInitialize / CoUninitialize).
/// Must be declared before any COM objects (e.g., ComPtr, UniquePidl) so that
/// CoUninitialize runs after all COM objects are released during scope exit.
class ComGuard {
public:
ComGuard() : m_hr(CoInitialize(nullptr)) {}
~ComGuard() { if (SUCCEEDED(m_hr)) CoUninitialize(); }
ComGuard(const ComGuard&) = delete;
ComGuard& operator=(const ComGuard&) = delete;
bool IsInitialized() const { return SUCCEEDED(m_hr); }
private:
HRESULT m_hr;
};
/// RAII wrapper for COM interface pointers.
template <typename T>
class ComPtr {
public:
ComPtr() = default;
ComPtr(std::nullptr_t) {}
~ComPtr() { Reset(); }
ComPtr(const ComPtr&) = delete;
ComPtr& operator=(const ComPtr&) = delete;
ComPtr(ComPtr&& other) noexcept : m_ptr(other.m_ptr) { other.m_ptr = nullptr; }
ComPtr& operator=(ComPtr&& other) noexcept {
if (this != &other) {
Reset();
m_ptr = other.m_ptr;
other.m_ptr = nullptr;
}
return *this;
}
T* operator->() const { return m_ptr; }
T* Get() const { return m_ptr; }
T** GetAddressOf() { return &m_ptr; }
explicit operator bool() const { return m_ptr != nullptr; }
void Reset() { if (m_ptr) { m_ptr->Release(); m_ptr = nullptr; } }
private:
T* m_ptr = nullptr;
};
/// RAII wrapper for PIDL (Item ID List).
class UniquePidl {
public:
UniquePidl() = default;
explicit UniquePidl(LPITEMIDLIST pidl) : m_pidl(pidl) {}
~UniquePidl() { Reset(); }
UniquePidl(const UniquePidl&) = delete;
UniquePidl& operator=(const UniquePidl&) = delete;
UniquePidl(UniquePidl&& other) noexcept : m_pidl(other.m_pidl) { other.m_pidl = nullptr; }
UniquePidl& operator=(UniquePidl&& other) noexcept {
if (this != &other) {
Reset();
m_pidl = other.m_pidl;
other.m_pidl = nullptr;
}
return *this;
}
LPCITEMIDLIST Get() const { return m_pidl; }
LPITEMIDLIST* GetAddressOf() { return &m_pidl; }
explicit operator bool() const { return m_pidl != nullptr; }
void Reset() { if (m_pidl) { ILFree(m_pidl); m_pidl = nullptr; } }
private:
LPITEMIDLIST m_pidl = nullptr;
};
/// Outputs a diagnostic warning for debugging Windows Explorer integration failures.
static void ReportError(const QString& detail)
{
qWarning("Windows Explorer file list fetch error: %s",
qPrintable(detail));
}
/// Normalizes a Windows directory path for case-insensitive comparison:
/// converts to native separators, lowercases, and ensures a trailing backslash.
static QString NormalizePath(const QString& path)
{
QString result = QDir::toNativeSeparators(path).toLower();
if (!result.endsWith(QChar('\\'))) {
result += QChar('\\');
}
return result;
}
/// Checks whether the Explorer window represented by the given IDispatch is
/// displaying the target folder. Returns its IFolderView2 on match, or null.
static ComPtr<IFolderView2> TryMatchWindow(IDispatch* dispatch, const QString& normalizedTarget)
{
// Navigate: IDispatch -> IServiceProvider -> IShellBrowser.
ComPtr<IServiceProvider> serviceProvider;
if (FAILED(dispatch->QueryInterface(IID_PPV_ARGS(serviceProvider.GetAddressOf())))) {
return nullptr;
}
ComPtr<IShellBrowser> shellBrowser;
if (FAILED(serviceProvider->QueryService(SID_STopLevelBrowser,
IID_PPV_ARGS(shellBrowser.GetAddressOf())))) {
return nullptr;
}
// IShellBrowser -> active IShellView.
ComPtr<IShellView> shellView;
if (FAILED(shellBrowser->QueryActiveShellView(shellView.GetAddressOf()))) {
return nullptr;
}
// Retrieve the folder's path via IPersistIDList.
ComPtr<IPersistIDList> persistIdList;
if (FAILED(shellView->QueryInterface(IID_PPV_ARGS(persistIdList.GetAddressOf())))) {
return nullptr;
}
UniquePidl folderPidl;
if (FAILED(persistIdList->GetIDList(folderPidl.GetAddressOf()))) {
return nullptr;
}
PWSTR pathStr = nullptr;
if (FAILED(SHGetNameFromIDList(folderPidl.Get(), SIGDN_DESKTOPABSOLUTEPARSING, &pathStr))) {
return nullptr;
}
QString windowPath = NormalizePath(QString::fromWCharArray(pathStr));
CoTaskMemFree(pathStr);
if (windowPath != normalizedTarget) {
return nullptr;
}
// Path matches — query IFolderView2 from the ShellView.
ComPtr<IFolderView2> folderView2;
if (FAILED(shellView->QueryInterface(IID_PPV_ARGS(folderView2.GetAddressOf())))) {
return nullptr;
}
return folderView2;
}
/// Tries to find a live Explorer window that is currently displaying the given
/// folder path. Returns the IFolderView2 from that window, or an empty ComPtr.
///
/// Explorer's sort order is stored only in the running Explorer process's memory.
/// Creating a new shell view via IShellFolder::CreateViewObject yields a default
/// sort (always by name), so we must find the actual live IFolderView2 instead.
///
/// Reference:
/// - https://www.stephenli.ca/getting-windows-explorers-current-directory-sort-order
/// - https://github.com/d2phap/ImageGlass/blob/75a296287f338edcf409eee3917531b25ff5d927/v9/Components/ImageGlass.Base/FileSystem/FileFinder.cs
/// - NuGet Package: D2Phap.EggShell
static ComPtr<IFolderView2> FindLiveFolderView(const QString& targetFolderPath)
{
// Create the Shell windows collection (all open Explorer windows).
ComPtr<IShellWindows> shellWindows;
if (FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL,
IID_PPV_ARGS(shellWindows.GetAddressOf())))) {
ReportError("Failed to create IShellWindows instance");
return nullptr;
}
QString normalizedTarget = NormalizePath(targetFolderPath);
// 1. Try the Desktop window first — it may contain the file the user opened.
{
VARIANT varLoc;
VariantInit(&varLoc);
varLoc.vt = VT_I4;
varLoc.lVal = 0;
VARIANT varLocRoot;
VariantInit(&varLocRoot);
long desktopHwnd = 0;
ComPtr<IDispatch> desktopDispatch;
HRESULT findHr = shellWindows->FindWindowSW(
&varLoc, &varLocRoot, SWC_DESKTOP,
&desktopHwnd, SWFO_NEEDDISPATCH,
desktopDispatch.GetAddressOf());
VariantClear(&varLoc);
VariantClear(&varLocRoot);
if (SUCCEEDED(findHr) && desktopDispatch) {
ComPtr<IFolderView2> result = TryMatchWindow(desktopDispatch.Get(), normalizedTarget);
if (result) return result;
}
}
// 2. Iterate regular Explorer windows.
LONG count = 0;
shellWindows->get_Count(&count);
for (LONG i = 0; i < count; i++) {
VARIANT varIndex;
VariantInit(&varIndex);
varIndex.vt = VT_I4;
varIndex.lVal = i;
ComPtr<IDispatch> dispatch;
HRESULT itemHr = shellWindows->Item(varIndex, dispatch.GetAddressOf());
VariantClear(&varIndex);
if (FAILED(itemHr) || !dispatch) {
continue;
}
ComPtr<IFolderView2> result = TryMatchWindow(dispatch.Get(), normalizedTarget);
if (result) return result;
}
return nullptr;
}
std::optional<PlaylistScout::ScoutResult> PlaylistScout::Explorer::scout(
const QUrl &url, const QStringList &allowedSuffixes) {
PlaylistScout::ScoutResult rv;
QFileInfo info(url.toLocalFile());
QString folderPath = info.path();
QString currentFileName = info.fileName();
// Pre-compile wildcard suffix filters into regular expressions for performance.
QList<QRegularExpression> nameRegexps;
for (const QString &pattern : allowedSuffixes) {
nameRegexps.append(QRegularExpression(
QRegularExpression::wildcardToRegularExpression(pattern),
QRegularExpression::CaseInsensitiveOption));
}
// Initialize COM. The ComGuard must be declared before any COM objects
// below so that CoUninitialize runs after all of them are released.
ComGuard comGuard;
if (!comGuard.IsInitialized()) {
ReportError("COM initialization failed");
return std::nullopt;
}
// Find a live Explorer window that is currently displaying the target folder.
ComPtr<IFolderView2> folderView2 = FindLiveFolderView(folderPath);
if (!folderView2) {
ReportError("No open Explorer window matches the folder: " + folderPath);
return std::nullopt;
}
// Get items already sorted in Explorer's view order, as an IShellItemArray.
// SVGIO_ALLVIEW returns all items; SVGIO_FLAG_VIEWORDER respects the view's sort.
ComPtr<IShellItemArray> itemArray;
if (FAILED(folderView2->Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER,
IID_PPV_ARGS(itemArray.GetAddressOf())))) {
ReportError("IFolderView2::Items(SVGIO_ALLVIEW | SVGIO_FLAG_VIEWORDER) failed");
return std::nullopt;
}
DWORD itemCount = 0;
itemArray->GetCount(&itemCount);
// Enumerate sorted items, filter by pattern, and build the playlist.
for (DWORD i = 0; i < itemCount; i++) {
ComPtr<IShellItem> shellItem;
if (FAILED(itemArray->GetItemAt(i, shellItem.GetAddressOf()))) {
continue;
}
// Get the full parsing path of the item.
PWSTR pathStr = nullptr;
if (FAILED(shellItem->GetDisplayName(SIGDN_DESKTOPABSOLUTEPARSING, &pathStr)) || !pathStr) {
continue;
}
QString filePath = QString::fromWCharArray(pathStr);
CoTaskMemFree(pathStr);
// Skip directories — the view may include folders alongside files.
QFileInfo fileInfo(filePath);
if (!fileInfo.isFile()) {
continue;
}
QString fileName = fileInfo.fileName();
// Match against compiled wildcard patterns.
bool matches = nameRegexps.isEmpty();
for (const QRegularExpression &re : std::as_const(nameRegexps)) {
if (re.match(fileName).hasMatch()) {
matches = true;
break;
}
}
if (!matches) {
continue;
}
rv.playlist.append(QUrl::fromLocalFile(filePath));
if (fileName == currentFileName) {
rv.indexHint = rv.playlist.size() - 1;
}
}
return rv;
}
#endif // Q_OS_WIN
+16
View File
@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include "../playlistscout.h"
#include <QtSystemDetection>
#ifdef Q_OS_WIN
namespace PlaylistScout::Explorer {
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
#endif // Q_OS_WIN
+40
View File
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#include "qtscout.h"
#include <QFileInfo>
#include <QDir>
#include <QCollator>
std::optional<PlaylistScout::ScoutResult> PlaylistScout::Qt::scout(
const QUrl &url, const QStringList &allowedSuffixes) {
PlaylistScout::ScoutResult rv;
QFileInfo info(url.toLocalFile());
QDir dir(info.path());
QString currentFileName = info.fileName();
QStringList entryList = dir.entryList(
allowedSuffixes, QDir::Files | QDir::NoSymLinks, QDir::NoSort);
QCollator collator;
collator.setNumericMode(true);
std::sort(entryList.begin(), entryList.end(), collator);
for (qsizetype i = 0; i < entryList.count(); i++) {
const QString &fileName = entryList.at(i);
const QString &oneEntry = dir.absoluteFilePath(fileName);
const QUrl &url = QUrl::fromLocalFile(oneEntry);
rv.playlist.append(url);
if (fileName == currentFileName) {
rv.indexHint = i;
}
}
return rv;
}
+11
View File
@@ -0,0 +1,11 @@
// SPDX-FileCopyrightText: 2026 yyc12345 <yyc12321@outlook.com>
//
// SPDX-License-Identifier: MIT
#pragma once
#include "../playlistscout.h"
namespace PlaylistScout::Qt {
std::optional<ScoutResult> scout(const QUrl &url, const QStringList &allowedSuffixes);
}
-12
View File
@@ -65,12 +65,6 @@ bool Settings::svgTiny12Only() const
#endif // QT_VERSION < QT_VERSION_CHECK(6, 9, 3)
}
Settings::DoubleClickBehavior Settings::doubleClickBehavior() const
{
QString result = m_qsettings->value("double_click_behavior", "").toString();
return QEnumHelper::fromString<DoubleClickBehavior>(result, DoubleClickBehavior::Ignore);
}
Settings::MouseWheelBehavior Settings::mouseWheelBehavior() const
{
QString result = m_qsettings->value("mouse_wheel_behavior", "").toString();
@@ -107,12 +101,6 @@ void Settings::setSvgTiny12Only(bool on)
m_qsettings->sync();
}
void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
{
m_qsettings->setValue("double_click_behavior", QEnumHelper::toString(dcb));
m_qsettings->sync();
}
void Settings::setMouseWheelBehavior(MouseWheelBehavior mwb)
{
m_qsettings->setValue("mouse_wheel_behavior", QEnumHelper::toString(mwb));
-12
View File
@@ -11,23 +11,13 @@ class Settings : public QObject
{
Q_OBJECT
public:
enum DoubleClickBehavior {
Ignore,
Close,
Maximize,
FullScreen,
};
Q_ENUM(DoubleClickBehavior)
enum MouseWheelBehavior {
Zoom,
Switch,
};
Q_ENUM(MouseWheelBehavior)
// TODO: I want to remove Auto behavior in future.
enum WindowSizeBehavior {
Auto,
Maximized,
Windowed,
};
@@ -38,7 +28,6 @@ public:
bool useLightCheckerboard() const;
bool loopGallery() const;
bool svgTiny12Only() const;
DoubleClickBehavior doubleClickBehavior() const;
MouseWheelBehavior mouseWheelBehavior() const;
WindowSizeBehavior initWindowSizeBehavior() const;
Qt::HighDpiScaleFactorRoundingPolicy hiDpiScaleFactorBehavior() const;
@@ -46,7 +35,6 @@ public:
void setUseLightCheckerboard(bool light);
void setLoopGallery(bool on);
void setSvgTiny12Only(bool on);
void setDoubleClickBehavior(DoubleClickBehavior dcb);
void setMouseWheelBehavior(MouseWheelBehavior mwb);
void setInitWindowSizeBehavior(WindowSizeBehavior wsb);
void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi);
-17
View File
@@ -49,15 +49,6 @@ private:
Pairs pairs;
};
static EnumComboBoxTransformer<Settings::DoubleClickBehavior> DC_OPTIONS {
{
{Settings::DoubleClickBehavior::Ignore, QCoreApplication::translate("SettingsDialog", "Do nothing")},
{Settings::DoubleClickBehavior::Close, QCoreApplication::translate("SettingsDialog", "Close the window")},
{Settings::DoubleClickBehavior::Maximize, QCoreApplication::translate("SettingsDialog", "Toggle maximize")},
{Settings::DoubleClickBehavior::FullScreen, QCoreApplication::translate("SettingsDialog", "Toggle fullscreen")}
}
};
static EnumComboBoxTransformer<Settings::MouseWheelBehavior, QString> MW_OPTIONS {
{
{ Settings::MouseWheelBehavior::Zoom, QCoreApplication::translate("SettingsDialog", "Zoom in and out") },
@@ -67,7 +58,6 @@ static EnumComboBoxTransformer<Settings::MouseWheelBehavior, QString> MW_OPTIONS
static EnumComboBoxTransformer<Settings::WindowSizeBehavior, QString> IWS_OPTIONS {
{
{ Settings::WindowSizeBehavior::Auto, QCoreApplication::translate("SettingsDialog", "Auto size") },
{ Settings::WindowSizeBehavior::Maximized, QCoreApplication::translate("SettingsDialog", "Maximized") },
{ Settings::WindowSizeBehavior::Windowed, QCoreApplication::translate("SettingsDialog", "Windowed") }
}
@@ -92,9 +82,6 @@ SettingsDialog::SettingsDialog(QWidget *parent)
ui->m_useLightCheckerboard->setChecked(settings->useLightCheckerboard());
ui->m_loopGallery->setChecked(settings->loopGallery());
ui->m_svgTiny12Only->setChecked(settings->svgTiny12Only());
ui->m_doubleClickBehavior->setModel(DC_OPTIONS.buildComboBoxModel(this));
Settings::DoubleClickBehavior dcb = settings->doubleClickBehavior();
ui->m_doubleClickBehavior->setCurrentIndex(DC_OPTIONS.to_index(dcb));
ui->m_mouseWheelBehavior->setModel(MW_OPTIONS.buildComboBoxModel(this));
Settings::MouseWheelBehavior mwb = settings->mouseWheelBehavior();
ui->m_mouseWheelBehavior->setCurrentIndex(MW_OPTIONS.to_index(mwb));
@@ -125,10 +112,6 @@ SettingsDialog::SettingsDialog(QWidget *parent)
Settings::instance()->setSvgTiny12Only(state == Qt::Checked);
});
connect(ui->m_doubleClickBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
Settings::instance()->setDoubleClickBehavior(DC_OPTIONS.to_value(index));
});
connect(ui->m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
Settings::instance()->setMouseWheelBehavior(MW_OPTIONS.to_value(index));
});
+5 -15
View File
@@ -41,43 +41,33 @@
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="m_doubleClickBehaviorLabel">
<property name="text">
<string>Double-click behavior</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="m_doubleClickBehavior"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="m_mouseWheelBehaviorLabel">
<property name="text">
<string>Mouse wheel behavior</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="m_mouseWheelBehavior"/>
</item>
<item row="5" column="0">
<item row="4" column="0">
<widget class="QLabel" name="m_initWindowSizeBehaviorLabel">
<property name="text">
<string>Default window size</string>
</property>
</widget>
</item>
<item row="5" column="1">
<item row="4" column="1">
<widget class="QComboBox" name="m_initWindowSizeBehavior"/>
</item>
<item row="6" column="0">
<item row="5" column="0">
<widget class="QLabel" name="m_hiDpiRoundingPolicyBehaviorLabel">
<property name="text">
<string>HiDPI scale factor rounding policy</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="5" column="1">
<widget class="QComboBox" name="m_hiDpiRoundingPolicyBehavior"/>
</item>
</layout>