fix: avoid create menu actions everytime when trigger context menu.

fix the minor memory leak issue, also bring some possibility to
implement custom keybinding.

And oops I also did another feature in this commit..
Now we are able to set mouse wheel behavior in config dialog.
This commit is contained in:
Gary Wang
2021-01-24 00:07:58 +08:00
parent b3011f47e4
commit 1449844fdd
16 changed files with 624 additions and 249 deletions

62
app/actionmanager.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "actionmanager.h"
#include "mainwindow.h"
#include <QCoreApplication>
#define CREATE_NEW_ACTION(window, action)\
action = new QAction(window);\
action->setObjectName(QString::fromUtf8( #action ));\
window->addAction(action);
ActionManager::ActionManager()
{
}
ActionManager::~ActionManager()
{
}
void ActionManager::setupAction(MainWindow *mainWindow)
{
CREATE_NEW_ACTION(mainWindow, actionCopyPixmap);
CREATE_NEW_ACTION(mainWindow, actionCopyFilePath);
CREATE_NEW_ACTION(mainWindow, actionPaste);
CREATE_NEW_ACTION(mainWindow, actionToggleStayOnTop);
CREATE_NEW_ACTION(mainWindow, actionToggleProtectMode);
CREATE_NEW_ACTION(mainWindow, actionSettings);
CREATE_NEW_ACTION(mainWindow, actionHelp);
CREATE_NEW_ACTION(mainWindow, actionProperties);
CREATE_NEW_ACTION(mainWindow, actionQuitApp);
retranslateUi(mainWindow);
QMetaObject::connectSlotsByName(mainWindow);
}
void ActionManager::retranslateUi(MainWindow *mainWindow)
{
Q_UNUSED(mainWindow);
actionCopyPixmap->setText(QCoreApplication::translate("MainWindow", "Copy P&ixmap", nullptr));
actionCopyFilePath->setText(QCoreApplication::translate("MainWindow", "Copy &File Path", nullptr));
actionPaste->setText(QCoreApplication::translate("MainWindow", "&Paste", nullptr));
actionToggleStayOnTop->setText(QCoreApplication::translate("MainWindow", "Stay on top", nullptr));
actionToggleProtectMode->setText(QCoreApplication::translate("MainWindow", "Protected mode", nullptr));
actionSettings->setText(QCoreApplication::translate("MainWindow", "Configure...", nullptr));
actionHelp->setText(QCoreApplication::translate("MainWindow", "Help", nullptr));
actionProperties->setText(QCoreApplication::translate("MainWindow", "Properties", nullptr));
actionQuitApp->setText(QCoreApplication::translate("MainWindow", "Quit", nullptr));
}
void ActionManager::setupShortcuts()
{
actionQuitApp->setShortcuts({
QKeySequence(Qt::Key_Space),
QKeySequence(Qt::Key_Escape)
});
actionQuitApp->setShortcutContext(Qt::ApplicationShortcut);
}

30
app/actionmanager.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef ACTIONMANAGER_H
#define ACTIONMANAGER_H
#include <QAction>
class MainWindow;
class ActionManager
{
public:
ActionManager();
~ActionManager();
void setupAction(MainWindow * mainWindow);
void retranslateUi(MainWindow *MainWindow);
void setupShortcuts();
public:
QAction *actionCopyPixmap;
QAction *actionCopyFilePath;
QAction *actionPaste;
QAction *actionToggleStayOnTop;
QAction *actionToggleProtectMode;
QAction *actionSettings;
QAction *actionHelp;
QAction *actionProperties;
QAction *actionQuitApp;
};
#endif // ACTIONMANAGER_H

View File

@ -10,6 +10,7 @@
#include "aboutdialog.h"
#include "metadatamodel.h"
#include "metadatadialog.h"
#include "actionmanager.h"
#include <QMouseEvent>
#include <QMovie>
@ -25,9 +26,11 @@
#include <QClipboard>
#include <QMimeData>
#include <QWindow>
#include <QTimer>
MainWindow::MainWindow(QWidget *parent) :
FramelessWindow(parent)
MainWindow::MainWindow(QWidget *parent)
: FramelessWindow(parent)
, m_am(new ActionManager)
{
if (Settings::instance()->stayOnTop()) {
this->setWindowFlag(Qt::WindowStaysOnTopHint);
@ -110,7 +113,7 @@ MainWindow::MainWindow(QWidget *parent) :
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomInBtnClicked,
this, [ = ](){ m_graphicsView->zoomView(1.25); });
connect(m_bottomButtonGroup, &BottomButtonGroup::zoomOutBtnClicked,
this, [ = ](){ m_graphicsView->zoomView(0.75); });
this, [ = ](){ m_graphicsView->zoomView(0.8); });
connect(m_bottomButtonGroup, &BottomButtonGroup::toggleCheckerboardBtnClicked,
this, [ = ](){ m_graphicsView->toggleCheckerboard(); });
connect(m_bottomButtonGroup, &BottomButtonGroup::rotateRightBtnClicked,
@ -130,14 +133,6 @@ MainWindow::MainWindow(QWidget *parent) :
m_nextButton->setVisible(isGalleryAvailable());
});
QShortcut * quitAppShorucut = new QShortcut(QKeySequence(Qt::Key_Space), this);
connect(quitAppShorucut, &QShortcut::activated,
std::bind(&MainWindow::quitAppAction, this, false));
QShortcut * quitAppShorucut2 = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(quitAppShorucut2, &QShortcut::activated,
std::bind(&MainWindow::quitAppAction, this, false));
QShortcut * prevPictureShorucut = new QShortcut(QKeySequence(Qt::Key_PageUp), this);
connect(prevPictureShorucut, &QShortcut::activated,
this, &MainWindow::galleryPrev);
@ -150,7 +145,13 @@ MainWindow::MainWindow(QWidget *parent) :
connect(fullscreenShorucut, &QShortcut::activated,
this, &MainWindow::toggleFullscreen);
m_am->setupAction(this);
centerWindow();
QTimer::singleShot(0, this, [this](){
m_am->setupShortcuts();
});
}
MainWindow::~MainWindow()
@ -370,19 +371,29 @@ void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
void MainWindow::wheelEvent(QWheelEvent *event)
{
QPoint numDegrees = event->angleDelta() / 8;
bool needZoom = false, zoomIn = false;
bool needWeelEvent = false, wheelUp = false;
bool actionIsZoom = event->modifiers().testFlag(Qt::ControlModifier)
|| Settings::instance()->mouseWheelBehavior() == ActionZoomImage;
// 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) {
needZoom = true;
zoomIn = numDegrees.y() > 0;
needWeelEvent = true;
wheelUp = numDegrees.y() > 0;
}
if (needZoom) {
m_graphicsView->zoomView(zoomIn ? 1.25 : 0.8);
if (needWeelEvent) {
if (actionIsZoom) {
m_graphicsView->zoomView(wheelUp ? 1.25 : 0.8);
} else {
if (wheelUp) {
galleryPrev();
} else {
galleryNext();
}
}
event->accept();
} else {
FramelessWindow::wheelEvent(event);
@ -404,97 +415,38 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event)
QImage clipboardImage;
QUrl clipboardFileUrl;
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
if (clipboardData->hasImage()) {
QVariant imageVariant(clipboardData->imageData());
if (imageVariant.isValid()) {
clipboardImage = qvariant_cast<QImage>(imageVariant);
}
} else if (clipboardData->hasText()) {
QString clipboardText(clipboardData->text());
if (clipboardText.startsWith("PICTURE:")) {
QString maybeFilename(clipboardText.mid(8));
if (QFile::exists(maybeFilename)) {
clipboardFileUrl = QUrl::fromLocalFile(maybeFilename);
}
}
}
QAction * copyPixmap = m_am->actionCopyPixmap;
QAction * copyFilePath = m_am->actionCopyFilePath;
QAction * copyPixmap = new QAction(tr("Copy P&ixmap"));
connect(copyPixmap, &QAction::triggered, this, [ = ](){
QClipboard *cb = QApplication::clipboard();
cb->setPixmap(m_graphicsView->scene()->renderToPixmap());
});
QAction * copyFilePath = new QAction(tr("Copy &File Path"));
connect(copyFilePath, &QAction::triggered, this, [ = ](){
QClipboard *cb = QApplication::clipboard();
cb->setText(currentFileUrl.toLocalFile());
});
copyMenu->addAction(copyPixmap);
if (currentFileUrl.isValid()) {
copyMenu->addAction(copyFilePath);
}
QAction * pasteImage = new QAction(tr("&Paste Image"));
connect(pasteImage, &QAction::triggered, this, [ = ](){
clearGallery();
m_graphicsView->showImage(clipboardImage);
});
QAction * paste = m_am->actionPaste;
QAction * pasteImageFile = new QAction(tr("&Paste Image File"));
connect(pasteImageFile, &QAction::triggered, this, [ = ](){
m_graphicsView->showFileFromUrl(clipboardFileUrl, true);
});
QAction * stayOnTopMode = new QAction(tr("Stay on top"));
connect(stayOnTopMode, &QAction::triggered, this, [ = ](){
toggleStayOnTop();
});
QAction * stayOnTopMode = m_am->actionToggleStayOnTop;
stayOnTopMode->setCheckable(true);
stayOnTopMode->setChecked(stayOnTop());
QAction * protectedMode = new QAction(tr("Protected mode"));
connect(protectedMode, &QAction::triggered, this, [ = ](){
toggleProtectedMode();
});
QAction * protectedMode = m_am->actionToggleProtectMode;
protectedMode->setCheckable(true);
protectedMode->setChecked(m_protectedMode);
QAction * toggleSettings = new QAction(tr("Configure..."));
connect(toggleSettings, &QAction::triggered, this, [ = ](){
SettingsDialog * sd = new SettingsDialog(this);
sd->exec();
sd->deleteLater();
});
QAction * helpAction = new QAction(tr("Help"));
connect(helpAction, &QAction::triggered, this, [ = ](){
AboutDialog * ad = new AboutDialog(this);
ad->exec();
ad->deleteLater();
});
QAction * propertiesAction = new QAction(tr("Properties"));
connect(propertiesAction, &QAction::triggered, this, [ = ](){
MetadataModel * md = new MetadataModel();
md->setFile(currentFileUrl.toLocalFile());
MetadataDialog * ad = new MetadataDialog(this);
ad->setMetadataModel(md);
ad->exec();
ad->deleteLater();
});
QAction * toggleSettings = m_am->actionSettings;
QAction * helpAction = m_am->actionHelp;
QAction * propertiesAction = m_am->actionProperties;
if (copyMenu->actions().count() == 1) {
menu->addActions(copyMenu->actions());
} else {
menu->addMenu(copyMenu);
}
if (!clipboardImage.isNull()) {
menu->addAction(pasteImage);
} else if (clipboardFileUrl.isValid()) {
menu->addAction(pasteImageFile);
if (canPaste()) {
menu->addAction(paste);
}
menu->addSeparator();
menu->addAction(stayOnTopMode);
menu->addAction(protectedMode);
@ -507,6 +459,7 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event)
}
menu->exec(mapToGlobal(event->pos()));
menu->deleteLater();
copyMenu->deleteLater();
return FramelessWindow::contextMenuEvent(event);
}
@ -563,6 +516,22 @@ bool MainWindow::stayOnTop()
return windowFlags().testFlag(Qt::WindowStaysOnTopHint);
}
bool MainWindow::canPaste()
{
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
if (clipboardData->hasImage()) {
return true;
} else if (clipboardData->hasText()) {
QString clipboardText(clipboardData->text());
if (clipboardText.startsWith("PICTURE:")) {
QString maybeFilename(clipboardText.mid(8));
if (QFile::exists(maybeFilename)) {
return true;
}
}
}
}
void MainWindow::quitAppAction(bool force)
{
if (!m_protectedMode || force) {
@ -592,3 +561,90 @@ QSize MainWindow::sizeHint() const
{
return QSize(710, 530);
}
void MainWindow::on_actionCopyPixmap_triggered()
{
QClipboard *cb = QApplication::clipboard();
cb->setPixmap(m_graphicsView->scene()->renderToPixmap());
}
void MainWindow::on_actionCopyFilePath_triggered()
{
QUrl currentFileUrl(currentImageFileUrl());
if (currentFileUrl.isValid()) {
QClipboard *cb = QApplication::clipboard();
cb->setText(currentFileUrl.toLocalFile());
}
}
void MainWindow::on_actionPaste_triggered()
{
QImage clipboardImage;
QUrl clipboardFileUrl;
const QMimeData * clipboardData = QApplication::clipboard()->mimeData();
if (clipboardData->hasImage()) {
QVariant imageVariant(clipboardData->imageData());
if (imageVariant.isValid()) {
clipboardImage = qvariant_cast<QImage>(imageVariant);
}
} else if (clipboardData->hasText()) {
QString clipboardText(clipboardData->text());
if (clipboardText.startsWith("PICTURE:")) {
QString maybeFilename(clipboardText.mid(8));
if (QFile::exists(maybeFilename)) {
clipboardFileUrl = QUrl::fromLocalFile(maybeFilename);
}
}
}
if (!clipboardImage.isNull()) {
clearGallery();
m_graphicsView->showImage(clipboardImage);
} else if (clipboardFileUrl.isValid()) {
m_graphicsView->showFileFromUrl(clipboardFileUrl, true);
}
}
void MainWindow::on_actionToggleStayOnTop_triggered()
{
toggleStayOnTop();
}
void MainWindow::on_actionToggleProtectMode_triggered()
{
toggleProtectedMode();
}
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();
}
void MainWindow::on_actionProperties_triggered()
{
QUrl currentFileUrl = currentImageFileUrl();
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_actionQuitApp_triggered()
{
quitAppAction(false);
}

View File

@ -12,6 +12,7 @@ class QGraphicsOpacityEffect;
class QGraphicsView;
QT_END_NAMESPACE
class ActionManager;
class ToolButton;
class GraphicsView;
class NavigatorView;
@ -55,6 +56,7 @@ protected slots:
void toggleProtectedMode();
void toggleStayOnTop();
bool stayOnTop();
bool canPaste();
void quitAppAction(bool force = false);
void toggleFullscreen();
void toggleMaximize();
@ -62,7 +64,20 @@ protected slots:
protected:
QSize sizeHint() const override;
private slots:
void on_actionCopyPixmap_triggered();
void on_actionCopyFilePath_triggered();
void on_actionPaste_triggered();
void on_actionToggleStayOnTop_triggered();
void on_actionToggleProtectMode_triggered();
void on_actionSettings_triggered();
void on_actionHelp_triggered();
void on_actionProperties_triggered();
void on_actionQuitApp_triggered();
private:
ActionManager *m_am;
QPoint m_oldMousePos;
QPropertyAnimation *m_fadeOutAnimation;
QPropertyAnimation *m_floatUpAnimation;

View File

@ -28,6 +28,13 @@ DoubleClickBehavior Settings::doubleClickBehavior()
return stringToDoubleClickBehavior(result);
}
MouseWheelBehavior Settings::mouseWheelBehavior()
{
QString result = m_qsettings->value("mouse_wheel_behavior", "close").toString().toLower();
return stringToMouseWheelBehavior(result);
}
void Settings::setStayOnTop(bool on)
{
m_qsettings->setValue("stay_on_top", on);
@ -40,6 +47,12 @@ void Settings::setDoubleClickBehavior(DoubleClickBehavior dcb)
m_qsettings->sync();
}
void Settings::setMouseWheelBehavior(MouseWheelBehavior mwb)
{
m_qsettings->setValue("mouse_wheel_behavior", mouseWheelBehaviorToString(mwb));
m_qsettings->sync();
}
QString Settings::doubleClickBehaviorToString(DoubleClickBehavior dcb)
{
static QMap<DoubleClickBehavior, QString> _map {
@ -51,6 +64,16 @@ QString Settings::doubleClickBehaviorToString(DoubleClickBehavior dcb)
return _map.value(dcb, "close");
}
QString Settings::mouseWheelBehaviorToString(MouseWheelBehavior mwb)
{
static QMap<MouseWheelBehavior, QString> _map {
{ActionZoomImage, "zoom"},
{ActionPrevNextImage, "switch"}
};
return _map.value(mwb, "zoom");
}
DoubleClickBehavior Settings::stringToDoubleClickBehavior(QString str)
{
static QMap<QString, DoubleClickBehavior> _map {
@ -62,6 +85,16 @@ DoubleClickBehavior Settings::stringToDoubleClickBehavior(QString str)
return _map.value(str, ActionCloseWindow);
}
MouseWheelBehavior Settings::stringToMouseWheelBehavior(QString str)
{
static QMap<QString, MouseWheelBehavior> _map {
{"zoom", ActionZoomImage},
{"switch", ActionPrevNextImage}
};
return _map.value(str, ActionZoomImage);
}
Settings::Settings()
: QObject(qApp)
{

View File

@ -8,8 +8,16 @@ enum DoubleClickBehavior {
ActionCloseWindow,
ActionMaximizeWindow,
ActionStart = ActionDoNothing,
ActionEnd = ActionMaximizeWindow
DCActionStart = ActionDoNothing,
DCActionEnd = ActionMaximizeWindow
};
enum MouseWheelBehavior {
ActionZoomImage,
ActionPrevNextImage,
MWActionStart = ActionZoomImage,
MWActionEnd = ActionPrevNextImage
};
class Settings : public QObject
@ -20,12 +28,16 @@ public:
bool stayOnTop();
DoubleClickBehavior doubleClickBehavior();
MouseWheelBehavior mouseWheelBehavior();
void setStayOnTop(bool on);
void setDoubleClickBehavior(DoubleClickBehavior dcb);
void setMouseWheelBehavior(MouseWheelBehavior mwb);
static QString doubleClickBehaviorToString(DoubleClickBehavior dcb);
static QString mouseWheelBehaviorToString(MouseWheelBehavior mwb);
static DoubleClickBehavior stringToDoubleClickBehavior(QString str);
static MouseWheelBehavior stringToMouseWheelBehavior(QString str);
private:
Settings();

View File

@ -11,29 +11,44 @@ SettingsDialog::SettingsDialog(QWidget *parent)
: QDialog(parent)
, m_stayOnTop(new QCheckBox)
, m_doubleClickBehavior(new QComboBox)
, m_mouseWheelBehavior(new QComboBox)
{
this->setWindowTitle(tr("Settings"));
QFormLayout * settingsForm = new QFormLayout(this);
static QMap<DoubleClickBehavior, QString> _map {
static QMap<DoubleClickBehavior, QString> _dc_map {
{ ActionDoNothing, tr("Do nothing") },
{ ActionCloseWindow, tr("Close the window") },
{ ActionMaximizeWindow, tr("Toggle maximize") }
};
QStringList dropDown;
for (int dcb = ActionStart; dcb <= ActionEnd; dcb++) {
dropDown.append(_map.value(static_cast<DoubleClickBehavior>(dcb)));
static QMap<MouseWheelBehavior, QString> _mw_map {
{ ActionZoomImage, tr("Zoom in and out") },
{ ActionPrevNextImage, tr("View next or previous item") }
};
QStringList dcbDropDown;
for (int dcb = DCActionStart; dcb <= DCActionEnd; dcb++) {
dcbDropDown.append(_dc_map.value(static_cast<DoubleClickBehavior>(dcb)));
}
QStringList mwbDropDown;
for (int mwb = MWActionStart; mwb <= MWActionEnd; mwb++) {
mwbDropDown.append(_mw_map.value(static_cast<MouseWheelBehavior>(mwb)));
}
settingsForm->addRow(tr("Stay on top when start-up"), m_stayOnTop);
settingsForm->addRow(tr("Double-click behavior"), m_doubleClickBehavior);
settingsForm->addRow(tr("Mouse wheel behavior"), m_mouseWheelBehavior);
m_stayOnTop->setChecked(Settings::instance()->stayOnTop());
m_doubleClickBehavior->setModel(new QStringListModel(dropDown));
m_doubleClickBehavior->setModel(new QStringListModel(dcbDropDown));
DoubleClickBehavior dcb = Settings::instance()->doubleClickBehavior();
m_doubleClickBehavior->setCurrentIndex(static_cast<int>(dcb));
m_mouseWheelBehavior->setModel(new QStringListModel(mwbDropDown));
MouseWheelBehavior mwb = Settings::instance()->mouseWheelBehavior();
m_mouseWheelBehavior->setCurrentIndex(static_cast<int>(mwb));
connect(m_stayOnTop, &QCheckBox::stateChanged, this, [ = ](int state){
Settings::instance()->setStayOnTop(state == Qt::Checked);
@ -43,6 +58,10 @@ SettingsDialog::SettingsDialog(QWidget *parent)
Settings::instance()->setDoubleClickBehavior(static_cast<DoubleClickBehavior>(index));
});
connect(m_mouseWheelBehavior, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [ = ](int index){
Settings::instance()->setMouseWheelBehavior(static_cast<MouseWheelBehavior>(index));
});
this->setMinimumSize(300, 61); // not sure why it complain "Unable to set geometry"
setWindowFlag(Qt::WindowContextHelpButtonHint, false);
}

View File

@ -20,6 +20,7 @@ public slots:
private:
QCheckBox * m_stayOnTop = nullptr;
QComboBox * m_doubleClickBehavior = nullptr;
QComboBox * m_mouseWheelBehavior = nullptr;
};
#endif // SETTINGSDIALOG_H