2025-03-29 13:41:55 +08:00
|
|
|
// SPDX-FileCopyrightText: 2025 Gary Wang <git@blumia.net>
|
2022-10-29 23:35:30 +08:00
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
|
|
#include "mainwindow.h"
|
|
|
|
|
|
|
|
|
|
#include "settings.h"
|
|
|
|
|
#include "bottombuttongroup.h"
|
|
|
|
|
#include "graphicsview.h"
|
|
|
|
|
#include "navigatorview.h"
|
|
|
|
|
#include "graphicsscene.h"
|
|
|
|
|
#include "settingsdialog.h"
|
|
|
|
|
#include "aboutdialog.h"
|
|
|
|
|
#include "metadatamodel.h"
|
|
|
|
|
#include "metadatadialog.h"
|
|
|
|
|
#include "actionmanager.h"
|
|
|
|
|
#include "playlistmanager.h"
|
|
|
|
|
|
|
|
|
|
#include <QMouseEvent>
|
|
|
|
|
#include <QMovie>
|
|
|
|
|
#include <QDebug>
|
|
|
|
|
#include <QGraphicsTextItem>
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
#include <QStyle>
|
|
|
|
|
#include <QScreen>
|
|
|
|
|
#include <QMenu>
|
|
|
|
|
#include <QShortcut>
|
|
|
|
|
#include <QClipboard>
|
|
|
|
|
#include <QMimeData>
|
|
|
|
|
#include <QWindow>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QTimer>
|
|
|
|
|
#include <QFileDialog>
|
2025-03-29 13:41:55 +08:00
|
|
|
#include <QFileSystemWatcher>
|
2022-10-29 23:35:30 +08:00
|
|
|
#include <QStandardPaths>
|
2024-07-18 16:01:07 +00:00
|
|
|
#include <QStringBuilder>
|
2022-10-29 23:35:30 +08:00
|
|
|
#include <QProcess>
|
|
|
|
|
#include <QDesktopServices>
|
2024-04-19 00:48:28 +08:00
|
|
|
#include <QMessageBox>
|
2026-07-13 15:59:39 +08:00
|
|
|
#include <QVBoxLayout>
|
2022-10-29 23:35:30 +08:00
|
|
|
|
|
|
|
|
#ifdef HAVE_QTDBUS
|
|
|
|
|
#include <QDBusInterface>
|
2022-11-01 20:58:48 +08:00
|
|
|
#include <QDBusConnectionInterface>
|
2022-10-29 23:35:30 +08:00
|
|
|
#endif // HAVE_QTDBUS
|
|
|
|
|
|
2025-05-31 13:10:21 +08:00
|
|
|
using namespace Qt::Literals::StringLiterals;
|
|
|
|
|
|
2022-10-29 23:35:30 +08:00
|
|
|
MainWindow::MainWindow(QWidget *parent)
|
2026-07-13 15:59:39 +08:00
|
|
|
: QWidget(parent)
|
2022-10-29 23:35:30 +08:00
|
|
|
, m_am(new ActionManager)
|
2024-07-20 23:18:42 +08:00
|
|
|
, m_pm(new PlaylistManager(this))
|
2025-03-29 13:41:55 +08:00
|
|
|
, m_fileSystemWatcher(new QFileSystemWatcher(this))
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-13 15:59:39 +08:00
|
|
|
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.
|
|
|
|
|
|
2026-07-04 16:54:48 +08:00
|
|
|
// 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);
|
2025-05-31 13:10:21 +08:00
|
|
|
this->setWindowIcon(QIcon(u":/icons/app-icon.svg"_s));
|
2024-07-30 22:54:43 +08:00
|
|
|
this->setAcceptDrops(true);
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
m_pm->setAllowedSuffixes(supportedImageFormats());
|
2022-10-29 23:35:30 +08:00
|
|
|
|
|
|
|
|
GraphicsScene * scene = new GraphicsScene(this);
|
|
|
|
|
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv = new GraphicsView(this);
|
2022-10-29 23:35:30 +08:00
|
|
|
m_gv->setScene(scene);
|
2026-07-13 15:59:39 +08:00
|
|
|
m_centralLayout->addWidget(m_gv);
|
2026-07-04 14:35:28 +08:00
|
|
|
|
|
|
|
|
m_nav = new NavigatorView(this);
|
2026-07-04 15:20:14 +08:00
|
|
|
// 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);
|
2026-07-04 14:35:28 +08:00
|
|
|
m_nav->setScene(scene);
|
|
|
|
|
m_nav->setMainView(m_gv);
|
|
|
|
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-04 14:35:28 +08:00
|
|
|
connect(m_gv, &GraphicsView::navigatorViewRequired,
|
2025-05-31 13:10:21 +08:00
|
|
|
this, [this](bool required, const QTransform & tf){
|
2026-07-04 14:35:28 +08:00
|
|
|
m_nav->setTransform(GraphicsView::resetScale(tf));
|
|
|
|
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
|
|
|
|
m_nav->setVisible(required);
|
|
|
|
|
m_nav->updateMainViewportRegion();
|
2022-10-29 23:35:30 +08:00
|
|
|
});
|
|
|
|
|
|
2026-07-04 14:35:28 +08:00
|
|
|
connect(m_gv, &GraphicsView::viewportRectChanged,
|
|
|
|
|
m_nav, &NavigatorView::updateMainViewportRegion);
|
2022-10-29 23:35:30 +08:00
|
|
|
|
|
|
|
|
m_am->setupAction(this);
|
|
|
|
|
|
|
|
|
|
m_bottomButtonGroup = new BottomButtonGroup({
|
|
|
|
|
m_am->actionActualSize,
|
2026-07-12 21:00:11 +08:00
|
|
|
m_am->actionFitToScreen,
|
2022-10-29 23:35:30 +08:00
|
|
|
m_am->actionZoomIn,
|
|
|
|
|
m_am->actionZoomOut,
|
2026-07-03 14:56:54 +08:00
|
|
|
m_am->actionPrevPicture,
|
2026-07-12 21:00:11 +08:00
|
|
|
m_am->actionProperties,
|
2026-07-03 14:56:54 +08:00
|
|
|
m_am->actionNextPicture,
|
2026-07-12 21:00:11 +08:00
|
|
|
m_am->actionRotateCounterClockwise,
|
|
|
|
|
m_am->actionRotateClockwise,
|
|
|
|
|
m_am->actionFlipHorizontal,
|
2022-10-29 23:35:30 +08:00
|
|
|
m_am->actionToggleCheckerboard,
|
|
|
|
|
}, this);
|
|
|
|
|
|
2026-07-13 15:47:17 +08:00
|
|
|
m_menu = new QMenu(this);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
m_menu->addAction(m_am->actionCopyPixmap);
|
|
|
|
|
m_menu->addAction(m_am->actionCopyFilePath);
|
|
|
|
|
|
|
|
|
|
m_menu->addSeparator();
|
|
|
|
|
|
|
|
|
|
m_menu->addAction(m_am->actionPrevPicture);
|
|
|
|
|
m_menu->addAction(m_am->actionNextPicture);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
m_menu->addSeparator();
|
|
|
|
|
|
|
|
|
|
m_menu->addAction(m_am->actionTrash);
|
|
|
|
|
m_menu->addAction(m_am->actionLocateInFileManager);
|
|
|
|
|
m_menu->addAction(m_am->actionProperties);
|
|
|
|
|
|
|
|
|
|
m_menu->addSeparator();
|
|
|
|
|
|
|
|
|
|
m_menu->addAction(m_am->actionSettings);
|
|
|
|
|
m_menu->addAction(m_am->actionHelp);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::updateActionState, this));
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
connect(m_pm, &PlaylistManager::playlistChanged, this, std::bind(&MainWindow::galleryCurrent, this, false));
|
|
|
|
|
connect(m_pm, &PlaylistManager::currentIndexChanged, this, std::bind(&MainWindow::galleryCurrent, this, false));
|
2023-05-29 23:59:45 +08:00
|
|
|
|
2025-03-31 01:18:50 +08:00
|
|
|
connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, [this](){
|
2026-07-13 15:40:59 +08:00
|
|
|
QTimer::singleShot(500, this, std::bind(&MainWindow::galleryCurrent, this, true));
|
2025-03-31 01:18:50 +08:00
|
|
|
});
|
2025-03-29 13:41:55 +08:00
|
|
|
|
2022-10-29 23:35:30 +08:00
|
|
|
QShortcut * fullscreenShorucut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this);
|
|
|
|
|
connect(fullscreenShorucut, &QShortcut::activated,
|
|
|
|
|
this, &MainWindow::toggleFullscreen);
|
|
|
|
|
|
|
|
|
|
centerWindow();
|
|
|
|
|
|
|
|
|
|
QTimer::singleShot(0, this, [this](){
|
|
|
|
|
m_am->setupShortcuts();
|
|
|
|
|
});
|
2023-06-24 14:37:54 +08:00
|
|
|
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MainWindow::~MainWindow()
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
void MainWindow::showFiles(const QStringList &files)
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
m_pm->loadPlaylist(files);
|
|
|
|
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
|
|
|
|
}
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
void MainWindow::showFiles(const QList<QUrl>& urls)
|
|
|
|
|
{
|
|
|
|
|
m_pm->loadPlaylist(urls);
|
2026-07-04 14:35:28 +08:00
|
|
|
m_nav->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::initWindowSize()
|
|
|
|
|
{
|
|
|
|
|
switch (Settings::instance()->initWindowSizeBehavior()) {
|
|
|
|
|
case Settings::WindowSizeBehavior::Auto:
|
|
|
|
|
adjustWindowSizeBySceneRect();
|
|
|
|
|
break;
|
|
|
|
|
case Settings::WindowSizeBehavior::Maximized:
|
|
|
|
|
showMaximized();
|
|
|
|
|
break;
|
2025-03-26 22:45:12 +08:00
|
|
|
case Settings::WindowSizeBehavior::Windowed:
|
|
|
|
|
showNormal();
|
|
|
|
|
break;
|
2022-10-29 23:35:30 +08:00
|
|
|
default:
|
|
|
|
|
adjustWindowSizeBySceneRect();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::adjustWindowSizeBySceneRect()
|
|
|
|
|
{
|
2024-07-20 23:18:42 +08:00
|
|
|
if (m_pm->totalCount() < 1) return;
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-04 14:35:28 +08:00
|
|
|
QSize sceneSize = m_gv->sceneRect().toRect().size();
|
2022-10-29 23:35:30 +08:00
|
|
|
QSize sceneSizeWithMargins = sceneSize + QSize(130, 125);
|
|
|
|
|
|
2026-07-04 14:35:28 +08:00
|
|
|
if (m_gv->scaleFactor() < 1 || size().expandedTo(sceneSizeWithMargins) != size()) {
|
2022-10-29 23:35:30 +08:00
|
|
|
// if it scaled down by the resize policy:
|
2025-12-14 11:26:21 +08:00
|
|
|
QSize screenSize = qApp->screenAt(QCursor::pos())->availableSize();
|
2022-10-29 23:35:30 +08:00
|
|
|
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.
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->resetScale();
|
2022-10-29 23:35:30 +08:00
|
|
|
centerWindow();
|
|
|
|
|
} else {
|
|
|
|
|
// toggle maximum
|
|
|
|
|
showMaximized();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::clearGallery()
|
|
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
m_pm->clearPlaylist();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::galleryPrev()
|
|
|
|
|
{
|
2025-06-29 16:24:06 +08:00
|
|
|
const bool loopGallery = Settings::instance()->loopGallery();
|
|
|
|
|
if (!loopGallery && m_pm->isFirstIndex()) return;
|
|
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->previousIndex();
|
|
|
|
|
if (index.has_value()) {
|
|
|
|
|
m_pm->setCurrentIndex(*index);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::galleryNext()
|
|
|
|
|
{
|
2025-06-29 16:24:06 +08:00
|
|
|
const bool loopGallery = Settings::instance()->loopGallery();
|
|
|
|
|
if (!loopGallery && m_pm->isLastIndex()) return;
|
|
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->nextIndex();
|
|
|
|
|
if (index.has_value()) {
|
|
|
|
|
m_pm->setCurrentIndex(*index);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
void MainWindow::galleryCurrent(bool fromFileWatcher)
|
2024-04-15 01:16:00 +08:00
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->currentIndex();
|
|
|
|
|
if (index.has_value()) {
|
|
|
|
|
QString localFilePath = m_pm->localFileByIndex(*index);
|
|
|
|
|
m_gv->showFileFromPath(localFilePath);
|
|
|
|
|
if (!fromFileWatcher) {
|
|
|
|
|
updateFileWatcher(localFilePath);
|
|
|
|
|
setWindowTitle(m_pm->urlByIndex(*index).fileName());
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
m_gv->showText(tr("Drag image here"));
|
2026-06-30 21:08:35 +08:00
|
|
|
setWindowTitle(QString());
|
2026-07-13 15:40:59 +08:00
|
|
|
updateFileWatcher();
|
2024-04-15 01:16:00 +08:00
|
|
|
}
|
2025-03-29 13:41:55 +08:00
|
|
|
|
2026-07-13 15:40:59 +08:00
|
|
|
updateActionState();
|
2024-04-15 01:16:00 +08:00
|
|
|
}
|
|
|
|
|
|
2024-07-28 16:16:20 +08:00
|
|
|
QStringList MainWindow::supportedImageFormats()
|
|
|
|
|
{
|
|
|
|
|
QStringList formatFilters {
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
|
|
|
|
|
QStringLiteral("*.jfif")
|
|
|
|
|
#endif // QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
|
|
|
|
|
};
|
|
|
|
|
for (const QByteArray &item : QImageReader::supportedImageFormats()) {
|
|
|
|
|
formatFilters.append(QStringLiteral("*.") % QString::fromLocal8Bit(item));
|
|
|
|
|
}
|
|
|
|
|
return formatFilters;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-29 23:35:30 +08:00
|
|
|
void MainWindow::showEvent(QShowEvent *event)
|
|
|
|
|
{
|
|
|
|
|
updateWidgetsPosition();
|
2025-07-23 21:20:34 +08:00
|
|
|
|
2026-07-13 15:59:39 +08:00
|
|
|
return QWidget::showEvent(event);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:
|
2026-07-13 15:50:30 +08:00
|
|
|
closeWindow();
|
2022-10-29 23:35:30 +08:00
|
|
|
event->accept();
|
|
|
|
|
break;
|
|
|
|
|
case Settings::DoubleClickBehavior::Maximize:
|
|
|
|
|
toggleMaximize();
|
|
|
|
|
event->accept();
|
|
|
|
|
break;
|
2024-12-15 19:12:38 +08:00
|
|
|
case Settings::DoubleClickBehavior::FullScreen:
|
|
|
|
|
toggleFullscreen();
|
|
|
|
|
event->accept();
|
|
|
|
|
break;
|
2022-10-29 23:35:30 +08:00
|
|
|
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;
|
|
|
|
|
bool needWeelEvent = false, wheelUp = false;
|
|
|
|
|
bool actionIsZoom = event->modifiers().testFlag(Qt::ControlModifier)
|
|
|
|
|
|| Settings::instance()->mouseWheelBehavior() == Settings::MouseWheelBehavior::Zoom;
|
|
|
|
|
|
|
|
|
|
// NOTE: Only checking angleDelta since the QWheelEvent::pixelDelta() doc says
|
|
|
|
|
// pixelDelta() value is driver specific and unreliable on X11...
|
|
|
|
|
// We are not scrolling the canvas, just zoom in or out, so it probably
|
|
|
|
|
// doesn't matter here.
|
|
|
|
|
if (!numDegrees.isNull() && numDegrees.y() != 0) {
|
|
|
|
|
needWeelEvent = true;
|
|
|
|
|
wheelUp = numDegrees.y() > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (needWeelEvent) {
|
|
|
|
|
if (actionIsZoom) {
|
|
|
|
|
if (wheelUp) {
|
|
|
|
|
on_actionZoomIn_triggered();
|
|
|
|
|
} else {
|
|
|
|
|
on_actionZoomOut_triggered();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (wheelUp) {
|
|
|
|
|
galleryPrev();
|
|
|
|
|
} else {
|
|
|
|
|
galleryNext();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
event->accept();
|
|
|
|
|
} else {
|
2026-07-13 15:59:39 +08:00
|
|
|
QWidget::wheelEvent(event);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::resizeEvent(QResizeEvent *event)
|
|
|
|
|
{
|
|
|
|
|
updateWidgetsPosition();
|
|
|
|
|
|
2026-07-13 15:59:39 +08:00
|
|
|
return QWidget::resizeEvent(event);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::contextMenuEvent(QContextMenuEvent *event)
|
|
|
|
|
{
|
2026-07-13 15:47:17 +08:00
|
|
|
m_menu->exec(mapToGlobal(event->pos()));
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-13 15:59:39 +08:00
|
|
|
return QWidget::contextMenuEvent(event);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2024-07-30 22:54:43 +08:00
|
|
|
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
if (event->mimeData()->hasUrls()) {
|
2024-07-30 22:54:43 +08:00
|
|
|
event->acceptProposedAction();
|
|
|
|
|
} else {
|
|
|
|
|
event->ignore();
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-13 15:59:39 +08:00
|
|
|
return QWidget::dragEnterEvent(event);
|
2024-07-30 22:54:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::dragMoveEvent(QDragMoveEvent *event)
|
|
|
|
|
{
|
|
|
|
|
Q_UNUSED(event)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::dropEvent(QDropEvent *event)
|
|
|
|
|
{
|
|
|
|
|
event->acceptProposedAction();
|
|
|
|
|
|
|
|
|
|
const QMimeData * mimeData = event->mimeData();
|
|
|
|
|
if (mimeData->hasUrls()) {
|
2026-07-13 15:40:59 +08:00
|
|
|
showFiles(mimeData->urls());
|
2024-07-30 22:54:43 +08:00
|
|
|
} else {
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->showText(tr("Not supported mimedata: %1").arg(mimeData->formats().first()));
|
2024-07-30 22:54:43 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-29 23:35:30 +08:00
|
|
|
void MainWindow::centerWindow()
|
|
|
|
|
{
|
|
|
|
|
this->setGeometry(
|
|
|
|
|
QStyle::alignedRect(
|
|
|
|
|
Qt::LeftToRight,
|
|
|
|
|
Qt::AlignCenter,
|
|
|
|
|
this->size(),
|
2025-12-14 11:26:21 +08:00
|
|
|
qApp->screenAt(QCursor::pos())->availableGeometry()
|
2022-10-29 23:35:30 +08:00
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::closeWindow()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::updateWidgetsPosition()
|
|
|
|
|
{
|
|
|
|
|
m_bottomButtonGroup->move((width() - m_bottomButtonGroup->width()) / 2,
|
2026-07-04 15:20:14 +08:00
|
|
|
(72 - m_bottomButtonGroup->height()) / 2 + (height() - 72));
|
2026-07-04 14:35:28 +08:00
|
|
|
m_nav->move(width() - m_nav->width(), height() - m_nav->height());
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2023-07-09 15:39:59 +08:00
|
|
|
void MainWindow::toggleAvoidResetTransform()
|
|
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
m_gv->setAvoidResetTransform(!m_gv->isAvoidResetTransform());
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleFullscreen()
|
|
|
|
|
{
|
|
|
|
|
if (isFullScreen()) {
|
|
|
|
|
showNormal();
|
|
|
|
|
} else {
|
|
|
|
|
showFullScreen();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::toggleMaximize()
|
|
|
|
|
{
|
|
|
|
|
if (isMaximized()) {
|
|
|
|
|
showNormal();
|
|
|
|
|
} else {
|
|
|
|
|
showMaximized();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QSize MainWindow::sizeHint() const
|
|
|
|
|
{
|
|
|
|
|
return QSize(710, 530);
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
// region: Action Trigger
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionCopyPixmap_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
QClipboard *cb = QApplication::clipboard();
|
|
|
|
|
cb->setPixmap(m_gv->scene()->renderToPixmap());
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionCopyFilePath_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->currentIndex();
|
|
|
|
|
if (!index.has_value()) return;
|
|
|
|
|
|
|
|
|
|
QClipboard *cb = QApplication::clipboard();
|
|
|
|
|
cb->setText(m_pm->localFileByIndex(*index));
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionPrevPicture_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
galleryPrev();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionNextPicture_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
galleryNext();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionActualSize_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->resetScale();
|
|
|
|
|
m_gv->setEnableAutoFitToScreen(false);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionFitToScreen_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->fitInView(m_nav->sceneRect(), Qt::KeepAspectRatio);
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->setEnableAutoFitToScreen(m_gv->scaleFactor() <= 1);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionFitByWidth_triggered()
|
|
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->fitByOrientation(Qt::Orientation::Horizontal);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionFitByHeight_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->fitByOrientation(Qt::Orientation::Vertical);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionZoomIn_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
if (m_gv->scaleFactor() < 1000) {
|
|
|
|
|
m_gv->zoomView(1.25);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionZoomOut_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->zoomView(0.8);
|
2024-04-15 01:16:00 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionFlipHorizontal_triggered()
|
2024-04-15 01:16:00 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->flipView(true);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionFlipVertical_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->flipView(false);
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionRotateClockwise_triggered()
|
|
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->rotateView(true);
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->displayScene();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2024-07-31 19:41:51 +08:00
|
|
|
void MainWindow::on_actionRotateCounterClockwise_triggered()
|
|
|
|
|
{
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->rotateView(false);
|
|
|
|
|
m_gv->displayScene();
|
2024-07-31 19:41:51 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionToggleAvoidResetTransform_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
toggleAvoidResetTransform();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionToggleCheckerboard_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
m_gv->toggleCheckerboard(QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier));
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
2024-11-06 20:28:39 +08:00
|
|
|
void MainWindow::on_actionTogglePauseAnimation_triggered()
|
|
|
|
|
{
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->scene()->togglePauseAnimation();
|
2024-11-06 20:28:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionAnimationNextFrame_triggered()
|
|
|
|
|
{
|
2026-07-04 14:35:28 +08:00
|
|
|
m_gv->scene()->skipAnimationFrame(1);
|
2024-11-06 20:28:39 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionTrash_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-12 21:00:11 +08:00
|
|
|
auto index = m_pm->currentIndex();
|
|
|
|
|
if (!index.has_value()) return;
|
|
|
|
|
if (!m_pm->urlByIndex(*index).isLocalFile()) return;
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
QFile file(m_pm->localFileByIndex(*index));
|
|
|
|
|
QFileInfo fileInfo(file.fileName());
|
2022-10-29 23:35:30 +08:00
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
QMessageBox::StandardButton result = QMessageBox::question(this, tr("Move to Trash"),
|
|
|
|
|
tr("Are you sure you want to move \"%1\" to recycle bin?").arg(fileInfo.fileName()));
|
|
|
|
|
if (result == QMessageBox::Yes) {
|
|
|
|
|
bool succ = file.moveToTrash();
|
|
|
|
|
if (!succ) {
|
|
|
|
|
QMessageBox::warning(this, tr("Failed to move file to trash"),
|
|
|
|
|
tr("Move to trash failed, it might caused by file permission issue, file system limitation, or platform limitation."));
|
|
|
|
|
} else {
|
2026-07-13 15:40:59 +08:00
|
|
|
m_pm->removeFromPlaylist(*index);
|
2026-07-12 21:00:11 +08:00
|
|
|
}
|
|
|
|
|
}
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionLocateInFileManager_triggered()
|
|
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->currentIndex();
|
|
|
|
|
if (!index.has_value()) return;
|
|
|
|
|
QUrl currentFileUrl = m_pm->urlByIndex(*index);
|
2022-10-29 23:35:30 +08:00
|
|
|
if (!currentFileUrl.isValid()) return;
|
|
|
|
|
|
|
|
|
|
QFileInfo fileInfo(currentFileUrl.toLocalFile());
|
|
|
|
|
if (!fileInfo.exists()) return;
|
|
|
|
|
|
|
|
|
|
QUrl && folderUrl = QUrl::fromLocalFile(fileInfo.absolutePath());
|
|
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
|
QProcess::startDetached("explorer", QStringList() << "/select," << QDir::toNativeSeparators(fileInfo.absoluteFilePath()));
|
|
|
|
|
#elif defined(Q_OS_LINUX) and defined(HAVE_QTDBUS)
|
|
|
|
|
// Use https://www.freedesktop.org/wiki/Specifications/file-manager-interface/ if possible
|
2022-11-01 20:58:48 +08:00
|
|
|
const QDBusConnectionInterface * dbusIface = QDBusConnection::sessionBus().interface();
|
|
|
|
|
if (!dbusIface || !dbusIface->isServiceRegistered(QLatin1String("org.freedesktop.FileManager1"))) {
|
|
|
|
|
QDesktopServices::openUrl(folderUrl);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-05-31 13:10:21 +08:00
|
|
|
QDBusInterface fm1Iface(u"org.freedesktop.FileManager1"_s,
|
|
|
|
|
u"/org/freedesktop/FileManager1"_s,
|
|
|
|
|
u"org.freedesktop.FileManager1"_s);
|
2022-11-01 20:58:48 +08:00
|
|
|
fm1Iface.setTimeout(1000);
|
2022-10-29 23:35:30 +08:00
|
|
|
fm1Iface.callWithArgumentList(QDBus::Block, "ShowItems", {
|
|
|
|
|
QStringList{currentFileUrl.toString()},
|
2022-11-01 20:58:48 +08:00
|
|
|
QString()
|
2022-10-29 23:35:30 +08:00
|
|
|
});
|
2022-10-30 12:01:58 +08:00
|
|
|
if (fm1Iface.lastError().isValid()) {
|
|
|
|
|
QDesktopServices::openUrl(folderUrl);
|
|
|
|
|
}
|
2022-10-29 23:35:30 +08:00
|
|
|
#else
|
|
|
|
|
QDesktopServices::openUrl(folderUrl);
|
|
|
|
|
#endif // Q_OS_WIN
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
void MainWindow::on_actionProperties_triggered()
|
2022-10-29 23:35:30 +08:00
|
|
|
{
|
2026-07-13 15:40:59 +08:00
|
|
|
auto index = m_pm->currentIndex();
|
|
|
|
|
if (!index.has_value()) return;
|
|
|
|
|
QUrl currentFileUrl = m_pm->urlByIndex(*index);
|
2026-07-12 21:00:11 +08:00
|
|
|
if (!currentFileUrl.isValid()) return;
|
|
|
|
|
|
|
|
|
|
MetadataModel * md = new MetadataModel();
|
|
|
|
|
md->setFile(currentFileUrl.toLocalFile());
|
|
|
|
|
|
|
|
|
|
MetadataDialog * ad = new MetadataDialog(this);
|
|
|
|
|
ad->setMetadataModel(md);
|
|
|
|
|
ad->exec();
|
|
|
|
|
ad->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionSettings_triggered()
|
|
|
|
|
{
|
|
|
|
|
SettingsDialog * sd = new SettingsDialog(this);
|
|
|
|
|
sd->exec();
|
|
|
|
|
sd->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MainWindow::on_actionHelp_triggered()
|
|
|
|
|
{
|
|
|
|
|
AboutDialog * ad = new AboutDialog(this);
|
|
|
|
|
ad->exec();
|
|
|
|
|
ad->deleteLater();
|
2022-10-29 23:35:30 +08:00
|
|
|
}
|
2025-03-29 13:41:55 +08:00
|
|
|
|
2026-07-12 21:00:11 +08:00
|
|
|
// endregion
|
|
|
|
|
|
2025-03-29 13:41:55 +08:00
|
|
|
bool MainWindow::updateFileWatcher(const QString &basePath)
|
|
|
|
|
{
|
|
|
|
|
m_fileSystemWatcher->removePaths(m_fileSystemWatcher->files());
|
|
|
|
|
if (!basePath.isEmpty()) return m_fileSystemWatcher->addPath(basePath);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-07-13 15:40:59 +08:00
|
|
|
|
|
|
|
|
void MainWindow::updateActionState() {
|
|
|
|
|
m_am->updateActionState(m_pm, m_gv);
|
|
|
|
|
}
|