introduce FramelessMainWindow

This commit is contained in:
Le Tan 2021-10-15 20:55:09 +08:00
parent 0a2bdc7033
commit 5bc48be5d0
14 changed files with 390 additions and 89 deletions

View File

@ -21,6 +21,10 @@ QVector<NotebookTagMgr::TagGraphPair> NotebookTagMgr::stringToTagGraph(const QSt
QVector<TagGraphPair> tagGraph;
auto pairs = p_text.split(QLatin1Char(';'));
for (const auto &pa : pairs) {
if (pa.isEmpty()) {
continue;
}
auto paCh = pa.split(QLatin1Char('>'));
if (paCh.size() != 2 || paCh[0].isEmpty() || paCh[1].isEmpty()) {
qWarning() << "ignore invalid <parent, child> tag pair" << pa;

View File

@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1630643879525" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4410" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M810.666667 512V384h-170.666667a85.333333 85.333333 0 0 1-85.333333-85.333333V128H213.333333v384h597.333334zM128 512V128a85.333333 85.333333 0 0 1 85.333333-85.333333h444.330667L896 281.002667V512h85.333333v85.333333H42.666667v-85.333333h85.333333z m682.666667 170.666667h85.333333v213.333333a85.333333 85.333333 0 0 1-85.333333 85.333333H213.333333a85.333333 85.333333 0 0 1-85.333333-85.333333v-213.333333h85.333333v213.333333h597.333334v-213.333333zM640 145.664V298.666667h153.002667L640 145.664z" p-id="4411" fill="#000000"></path></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1634653362644" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2399" width="512" height="512" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M688 312v-48c0-4.4-3.6-8-8-8H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8zM296 400c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296z" p-id="2400" fill="#000000"></path><path d="M440 852H208V148h560v344c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h272c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z" p-id="2401" fill="#000000"></path><path d="M885.7 903.5l-93.3-93.3C814.7 780.7 828 743.9 828 704c0-97.2-78.8-176-176-176s-176 78.8-176 176 78.8 176 176 176c35.8 0 69-10.7 96.8-29l94.7 94.7c1.6 1.6 3.6 2.3 5.6 2.3s4.1-0.8 5.6-2.3l31-31c3.1-3.1 3.1-8.1 0-11.2zM652 816c-61.9 0-112-50.1-112-112s50.1-112 112-112 112 50.1 112 112-50.1 112-112 112z" p-id="2402" fill="#000000"></path></svg>

Before

Width:  |  Height:  |  Size: 918 B

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,47 @@
#include "framelessmainwindow.h"
#include <QEvent>
using namespace vnotex;
FramelessMainWindow::FramelessMainWindow(bool p_frameless, QWidget *p_parent)
: QMainWindow(p_parent),
m_frameless(p_frameless),
m_defaultFlags(windowFlags())
{
if (m_frameless) {
m_resizeAreaWidth *= devicePixelRatio();
setWindowFlags(m_defaultFlags | Qt::FramelessWindowHint);
}
}
bool FramelessMainWindow::isFrameless() const
{
return m_frameless;
}
void FramelessMainWindow::setTitleBar(QWidget *p_titleBar)
{
Q_ASSERT(!m_titleBar && m_frameless);
m_titleBar = p_titleBar;
m_titleBar->installEventFilter(this);
}
void FramelessMainWindow::changeEvent(QEvent *p_event)
{
QMainWindow::changeEvent(p_event);
if (p_event->type() == QEvent::WindowStateChange) {
m_windowStates = windowState();
m_resizable = m_movable = m_windowStates == Qt::WindowNoState;
emit windowStateChanged(m_windowStates);
}
}
bool FramelessMainWindow::isMaximized() const
{
return (m_windowStates & Qt::WindowMaximized) && !(m_windowStates & Qt::WindowFullScreen);
}

View File

@ -0,0 +1,47 @@
#ifndef FRAMELESSMAINWINDOW_H
#define FRAMELESSMAINWINDOW_H
#include <QMainWindow>
#include <QMargins>
class QTimer;
namespace vnotex
{
// Base class. Use FramelessMainWindowImpl instead.
class FramelessMainWindow : public QMainWindow
{
Q_OBJECT
public:
FramelessMainWindow(bool p_frameless, QWidget *p_parent);
bool isFrameless() const;
void setTitleBar(QWidget *p_titleBar);
signals:
void windowStateChanged(Qt::WindowStates p_state);
protected:
void changeEvent(QEvent *p_event) Q_DECL_OVERRIDE;
protected:
bool isMaximized() const;
const bool m_frameless = true;
int m_resizeAreaWidth = 5;
bool m_movable = true;
bool m_resizable = true;
const Qt::WindowFlags m_defaultFlags;
QWidget *m_titleBar = nullptr;
Qt::WindowStates m_windowStates = Qt::WindowNoState;
};
}
#endif // FRAMELESSMAINWINDOW_H

View File

@ -0,0 +1,15 @@
#ifndef FRAMELESSMAINWINDOWIMPL_H
#define FRAMELESSMAINWINDOWIMPL_H
#include "framelessmainwindowwin.h"
namespace vnotex
{
#ifdef Q_OS_WIN
typedef FramelessMainWindowWin FramelessMainWindowImpl;
#else
typedef FramelessMainWindow FramelessMainWindowImpl;
#endif
}
#endif // FRAMELESSMAINWINDOWIMPL_H

View File

@ -0,0 +1,217 @@
#include "framelessmainwindowwin.h"
#ifdef Q_OS_WIN
#include <QTimer>
#include <QDebug>
#include <QEvent>
#include <windows.h>
#include <windowsx.h>
#include <dwmapi.h>
#pragma comment (lib,"dwmapi.lib")
#pragma comment (lib, "user32.lib")
using namespace vnotex;
FramelessMainWindowWin::FramelessMainWindowWin(bool p_frameless, QWidget *p_parent)
: FramelessMainWindow(p_frameless, p_parent)
{
if (m_frameless) {
m_redrawTimer = new QTimer(this);
m_redrawTimer->setSingleShot(true);
m_redrawTimer->setInterval(500);
connect(m_redrawTimer, &QTimer::timeout,
this, &FramelessMainWindowWin::forceRedraw);
connect(this, &FramelessMainWindow::windowStateChanged,
this, &FramelessMainWindowWin::updateMargins);
// Enable some window effects on Win, such as snap and maximizing.
// It will activate the title bar again. Need to remove it in WM_NCCALCSIZE msg.
HWND hwnd = reinterpret_cast<HWND>(winId());
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
// Leave 1 pixel width of border so OS will draw a window shadow.
const MARGINS shadow = {1, 1, 1, 1};
DwmExtendFrameIntoClientArea(hwnd, &shadow);
}
}
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
bool FramelessMainWindowWin::nativeEvent(const QByteArray &p_eventType, void *p_message, qintptr *p_result)
#else
bool FramelessMainWindowWin::nativeEvent(const QByteArray &p_eventType, void *p_message, long *p_result)
#endif
{
if (!m_frameless) {
return FramelessMainWindow::nativeEvent(p_eventType, p_message, p_result);
}
if (p_eventType == QStringLiteral("windows_generic_MSG")) {
MSG *msg = static_cast<MSG *>(p_message);
switch (msg->message) {
case WM_NCCALCSIZE:
*p_result = 0;
return true;
case WM_NCHITTEST:
{
if (m_windowStates & Qt::WindowFullScreen) {
*p_result = HTCLIENT;
return true;
}
RECT windowRect;
::GetWindowRect(msg->hwnd, &windowRect);
// x and y could not be compared with width() and height() in hidpi case.
int x = static_cast<int>(GET_X_LPARAM(msg->lParam) - windowRect.left);
int y = static_cast<int>(GET_Y_LPARAM(msg->lParam) - windowRect.top);
bool onLeft = x < m_resizeAreaWidth;
bool onRight = x > windowRect.right - windowRect.left - m_resizeAreaWidth;
bool onTop = y < m_resizeAreaWidth;
bool onBottom = y > windowRect.bottom - windowRect.top - m_resizeAreaWidth;
*p_result = 0;
if (m_resizable) {
if (onLeft && onTop) {
*p_result = HTTOPLEFT;
} else if (onLeft && onBottom) {
*p_result = HTBOTTOMLEFT;
} else if (onRight && onTop) {
*p_result = HTTOPRIGHT;
} else if (onRight && onBottom) {
*p_result = HTBOTTOMRIGHT;
} else if (onLeft) {
*p_result = HTLEFT;
} else if (onRight) {
*p_result = HTRIGHT;
} else if (onTop) {
*p_result = HTTOP;
} else if (onBottom) {
*p_result = HTBOTTOM;
}
}
if (0 != *p_result) {
return true;
}
if (m_titleBar) {
if (m_titleBarHeight == 0) {
m_titleBarHeight = m_titleBar->height() * devicePixelRatio();
}
if (y < m_titleBarHeight) {
QWidget *child = m_titleBar->childAt(m_titleBar->mapFromGlobal(QCursor::pos()));
if (!child) {
*p_result = HTCAPTION;
if (::GetAsyncKeyState(VK_LBUTTON) < 0 || ::GetAsyncKeyState(VK_RBUTTON) < 0) {
m_sizeBeforeMove = size();
}
return true;
}
}
}
break;
}
case WM_POWERBROADCAST:
{
if (msg->wParam == PBT_APMSUSPEND) {
// Minimize when system is going to sleep to avoid bugs.
showMinimized();
}
break;
}
case WM_GETMINMAXINFO:
{
// When maximized, OS will expand the content area. To avoid missing the real contents, set extra margins.
if (::IsZoomed(msg->hwnd)) {
RECT frame = {0, 0, 0, 0};
::AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, false, 0);
const int dpiScale = devicePixelRatio();
m_maximizedMargins.setLeft(qAbs(frame.left) / dpiScale);
// Use bottom as top.
m_maximizedMargins.setTop(qAbs(frame.bottom) / dpiScale);
m_maximizedMargins.setRight(frame.right / dpiScale);
m_maximizedMargins.setBottom(frame.bottom / dpiScale);
}
break;
}
default:
if (msg->wParam == PBT_APMRESUMESUSPEND) {
// Show after resuming from sleep.
showNormal();
}
break;
}
}
return FramelessMainWindow::nativeEvent(p_eventType, p_message, p_result);
}
void FramelessMainWindowWin::moveEvent(QMoveEvent *p_event)
{
FramelessMainWindow::moveEvent(p_event);
if (m_frameless) {
if (m_windowStates & Qt::WindowMaximized) {
m_redrawTimer->stop();
} else {
m_redrawTimer->start();
}
}
}
void FramelessMainWindowWin::updateMargins()
{
if (!m_frameless) {
return;
}
int topMargin = 0;
if (isMaximized()) {
setContentsMargins(m_maximizedMargins);
topMargin = m_maximizedMargins.top();
} else {
setContentsMargins(0, 0, 0, 0);
}
if (m_titleBar) {
m_titleBarHeight = (m_titleBar->height() + topMargin) * devicePixelRatio();
}
}
void FramelessMainWindowWin::forceRedraw()
{
Q_ASSERT(m_frameless);
if (m_windowStates & Qt::WindowMaximized) {
return;
}
const QSize sz = size();
RECT frame;
::GetWindowRect((HWND)winId(), &frame);
const int clientWidth = (frame.right - frame.left) / devicePixelRatio();
const int clientHeight = (frame.bottom - frame.top) / devicePixelRatio();
if (clientWidth != sz.width() || clientHeight != sz.height()) {
// resize() may result to "unable to set geometry" warning.
// adjustsize() or resize() to another size before could solve this.
resize(sz.width() + 1, sz.height() + 1);
if (m_sizeBeforeMove.isEmpty()) {
resize(clientWidth, clientHeight);
} else {
resize(m_sizeBeforeMove);
}
}
}
#endif

View File

@ -0,0 +1,41 @@
#ifndef FRAMELESSMAINWINDOWWIN_H
#define FRAMELESSMAINWINDOWWIN_H
#include "framelessmainwindow.h"
namespace vnotex
{
#ifdef Q_OS_WIN
class FramelessMainWindowWin : public FramelessMainWindow
{
Q_OBJECT
public:
FramelessMainWindowWin(bool p_frameless = true, QWidget *p_parent = nullptr);
protected:
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
bool nativeEvent(const QByteArray &p_eventType, void *p_message, qintptr *p_result);
#else
bool nativeEvent(const QByteArray &p_eventType, void *p_message, long *p_result);
#endif
void moveEvent(QMoveEvent *p_event) Q_DECL_OVERRIDE;
private:
// To fix some unkonwn bugs of the interface.
void forceRedraw();
void updateMargins();
QTimer *m_redrawTimer = nullptr;
QSize m_sizeBeforeMove;
QMargins m_maximizedMargins;
int m_titleBarHeight = 0;
};
#endif
}
#endif // FRAMELESSMAINWINDOWWIN_H

View File

@ -58,7 +58,7 @@
using namespace vnotex;
MainWindow::MainWindow(QWidget *p_parent)
: QMainWindow(p_parent),
: FramelessMainWindowImpl(!ConfigMgr::getInst().getSessionConfig().getSystemTitleBarEnabled(), p_parent),
m_toolBarHelper(this),
m_statusBarHelper(this),
m_dockWidgetHelper(this)
@ -393,7 +393,7 @@ void MainWindow::closeEvent(QCloseEvent *p_event)
m_trayIcon->hide();
QMainWindow::closeEvent(p_event);
FramelessMainWindowImpl::closeEvent(p_event);
qApp->exit(exitCode > -1 ? exitCode : 0);
} else {
hide();
@ -521,16 +521,8 @@ void MainWindow::setupToolBar()
{
const int sz = ConfigMgr::getInst().getCoreConfig().getToolBarIconSize();
const QSize iconSize(sz, sz);
if (!ConfigMgr::getInst().getSessionConfig().getSystemTitleBarEnabled()) {
// Use unified tool bar as title bar.
auto framelessFlags = Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowMinMaxButtonsHint
| Qt::WindowCloseButtonHint | Qt::WindowFullscreenButtonHint;
auto winFlags = windowFlags();
winFlags |= Qt::CustomizeWindowHint | Qt::FramelessWindowHint;
winFlags &= ~framelessFlags;
setWindowFlags(winFlags);
if (isFrameless()) {
auto toolBar = new TitleToolBar(tr("Global"), this);
toolBar->setIconSize(iconSize);
m_toolBarHelper.setupToolBars(toolBar);
@ -538,6 +530,9 @@ void MainWindow::setupToolBar()
ToolBarHelper::generateIcon(QStringLiteral("maximize.svg")),
ToolBarHelper::generateIcon(QStringLiteral("maximize_restore.svg")),
ToolBarHelper::generateDangerousIcon(QStringLiteral("close.svg")));
setTitleBar(toolBar);
connect(this, &FramelessMainWindowImpl::windowStateChanged,
toolBar, &TitleToolBar::updateMaximizeAct);
} else {
auto toolBar = new QToolBar(tr("Global"), this);
toolBar->setIconSize(iconSize);
@ -594,7 +589,7 @@ void MainWindow::changeEvent(QEvent *p_event)
m_windowOldState = eve->oldState();
}
QMainWindow::changeEvent(p_event);
FramelessMainWindowImpl::changeEvent(p_event);
}
void MainWindow::showMainWindow()

View File

@ -1,7 +1,8 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "framelessmainwindow/framelessmainwindowimpl.h"
#include <QSharedPointer>
#include <QBitArray>
#include <QSet>
@ -30,7 +31,7 @@ namespace vnotex
enum { RESTART_EXIT_CODE = 1000 };
class MainWindow : public QMainWindow
class MainWindow : public FramelessMainWindowImpl
{
Q_OBJECT
public:

View File

@ -106,9 +106,9 @@ void TagExplorer::setupTagTree(QWidget *p_parent)
TreeWidget::showHorizontalScrollbar(m_tagTree);
m_tagTree->setDragDropMode(QAbstractItemView::InternalMove);
connect(m_tagTree, &QTreeWidget::currentItemChanged,
timer, QOverload<void>::of(&QTimer::start));
timer, QOverload<>::of(&QTimer::start));
connect(m_tagTree, &QTreeWidget::itemClicked,
timer, QOverload<void>::of(&QTimer::start));
timer, QOverload<>::of(&QTimer::start));
connect(m_tagTree, &QTreeWidget::customContextMenuRequested,
this, &TagExplorer::handleTagTreeContextMenuRequested);
connect(m_tagTree, &TreeWidget::itemMoved,

View File

@ -1,8 +1,5 @@
#include "titletoolbar.h"
#include <QDebug>
#include <QMouseEvent>
#include <QCoreApplication>
#include <QToolButton>
#include "propertydefs.h"
@ -14,7 +11,6 @@ TitleToolBar::TitleToolBar(QWidget *p_parent)
m_window(p_parent)
{
setupUI();
m_window->installEventFilter(this);
}
TitleToolBar::TitleToolBar(const QString &p_title, QWidget *p_parent)
@ -22,51 +18,17 @@ TitleToolBar::TitleToolBar(const QString &p_title, QWidget *p_parent)
m_window(p_parent)
{
setupUI();
m_window->installEventFilter(this);
}
void TitleToolBar::setupUI()
{
}
void TitleToolBar::mousePressEvent(QMouseEvent *p_event)
{
QToolBar::mousePressEvent(p_event);
m_lastPos = p_event->pos();
}
void TitleToolBar::mouseDoubleClickEvent(QMouseEvent *p_event)
{
QToolBar::mouseDoubleClickEvent(p_event);
m_ignoreNextMove = true;
maximizeRestoreWindow();
}
void TitleToolBar::maximizeRestoreWindow()
{
m_window->isMaximized() ? m_window->showNormal() : m_window->showMaximized();
}
void TitleToolBar::mouseMoveEvent(QMouseEvent *p_event)
{
auto delta = p_event->pos() - m_lastPos;
if (!m_ignoreNextMove && !m_lastPos.isNull() && (qAbs(delta.x()) > 10 || qAbs(delta.y()) > 10)) {
if (m_window->isMaximized()) {
m_window->showNormal();
} else {
m_window->move(p_event->globalPos() - m_lastPos);
}
}
QToolBar::mouseMoveEvent(p_event);
}
void TitleToolBar::mouseReleaseEvent(QMouseEvent *p_event)
{
QToolBar::mouseReleaseEvent(p_event);
m_ignoreNextMove = false;
m_lastPos = QPoint();
}
void TitleToolBar::addTitleBarIcons(const QIcon &p_minimizeIcon,
const QIcon &p_maximizeIcon,
const QIcon &p_restoreIcon,
@ -98,16 +60,6 @@ void TitleToolBar::addTitleBarIcons(const QIcon &p_minimizeIcon,
updateMaximizeAct();
}
bool TitleToolBar::eventFilter(QObject *p_obj, QEvent *p_event)
{
if (p_obj == m_window) {
if (p_event->type() == QEvent::WindowStateChange) {
updateMaximizeAct();
}
}
return QToolBar::eventFilter(p_obj, p_event);
}
void TitleToolBar::updateMaximizeAct()
{
if (m_window->isMaximized()) {

View File

@ -19,28 +19,13 @@ namespace vnotex
const QIcon &p_restoreIcon,
const QIcon &p_closeIcon);
protected:
void mousePressEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
void mouseDoubleClickEvent(QMouseEvent *p_event) Q_DECL_OVERRIDE;
bool eventFilter(QObject *p_obj, QEvent *p_event) Q_DECL_OVERRIDE;
void updateMaximizeAct();
private:
void setupUI();
void maximizeRestoreWindow();
void updateMaximizeAct();
QPoint m_lastPos;
bool m_ignoreNextMove = false;
QWidget *m_window = nullptr;
QAction *m_maximizeAct = nullptr;

View File

@ -167,8 +167,6 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
auto act = tb->addAction(generateIcon("import_menu.svg"), MainWindow::tr("Import"));
auto btn = dynamic_cast<QToolButton *>(tb->widgetForAction(act));
Q_ASSERT(btn);
btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
btn->setPopupMode(QToolButton::InstantPopup);
btn->setProperty(PropertyDefs::c_toolButtonWithoutMenuIndicator, true);
@ -200,12 +198,6 @@ QToolBar *ToolBarHelper::setupFileToolBar(MainWindow *p_win, QToolBar *p_toolBar
WidgetUtils::addActionShortcut(exportAct,
coreConfig.getShortcut(CoreConfig::Shortcut::Export));
// To hide the shortcut text shown in button.
auto toolBtn = dynamic_cast<QToolButton *>(tb->widgetForAction(exportAct));
Q_ASSERT(toolBtn);
toolBtn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolBtn->setText(MainWindow::tr("Export"));
}
return tb;

View File

@ -58,6 +58,8 @@ SOURCES += \
$$PWD/dialogs/folderfilesfilterwidget.cpp \
$$PWD/findandreplacewidget.cpp \
$$PWD/floatingwidget.cpp \
$$PWD/framelessmainwindow/framelessmainwindow.cpp \
$$PWD/framelessmainwindow/framelessmainwindowwin.cpp \
$$PWD/fullscreentoggleaction.cpp \
$$PWD/historypanel.cpp \
$$PWD/itemproxystyle.cpp \
@ -181,6 +183,9 @@ HEADERS += \
$$PWD/dialogs/folderfilesfilterwidget.h \
$$PWD/findandreplacewidget.h \
$$PWD/floatingwidget.h \
$$PWD/framelessmainwindow/framelessmainwindow.h \
$$PWD/framelessmainwindow/framelessmainwindowimpl.h \
$$PWD/framelessmainwindow/framelessmainwindowwin.h \
$$PWD/fullscreentoggleaction.h \
$$PWD/historypanel.h \
$$PWD/itemproxystyle.h \