From d2c66b9e399b652f24e830432a8e94b58230d8aa Mon Sep 17 00:00:00 2001 From: czyt1988 Date: Mon, 20 Nov 2023 17:23:35 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=8F=A6=E5=A4=96?= =?UTF-8?q?=E4=B8=80=E4=B8=AAframeless=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SARibbonBar.pri | 20 +- src/SARibbonBar/SARibbonBar.pri | 31 +- src/SARibbonBar/SARibbonMainWindow.cpp | 31 +- src/SARibbonBar/SARibbonMainWindow.h | 20 +- src/SARibbonBar/framelesswindow.cpp | 310 +++++++++++++++++++ src/SARibbonBar/framelesswindow.h | 185 +++++++++++ src/SARibbonBar/framelesswindow.mm | 226 ++++++++++++++ src/example/MainWindowExample/mainwindow.cpp | 16 +- 8 files changed, 781 insertions(+), 58 deletions(-) create mode 100644 src/SARibbonBar/framelesswindow.cpp create mode 100644 src/SARibbonBar/framelesswindow.h create mode 100644 src/SARibbonBar/framelesswindow.mm diff --git a/SARibbonBar.pri b/SARibbonBar.pri index f8ca72b..2c0391d 100644 --- a/SARibbonBar.pri +++ b/SARibbonBar.pri @@ -3,16 +3,16 @@ INCLUDEPATH += $$PWD/src/SARibbonBar DEPENDPATH += $$PWD/src/SARibbonBar -equals(QT_MAJOR_VERSION, 5){ - greaterThan(QT_MINOR_VERSION, 13){ - SA_RIBBON_CONFIG += use_frameless - } -}else{ -# qt6.0不支持 - greaterThan(QT_MINOR_VERSION, 0){ - SA_RIBBON_CONFIG += use_frameless - } -} +#equals(QT_MAJOR_VERSION, 5){ +# greaterThan(QT_MINOR_VERSION, 13){ +# SA_RIBBON_CONFIG += use_frameless +# } +#}else{ +## qt6.0不支持 +# greaterThan(QT_MINOR_VERSION, 0){ +# SA_RIBBON_CONFIG += use_frameless +# } +#} contains( SA_RIBBON_CONFIG, use_frameless ) { !contains(CONFIG,C++17){ diff --git a/src/SARibbonBar/SARibbonBar.pri b/src/SARibbonBar/SARibbonBar.pri index 3d7bb6d..463e96f 100644 --- a/src/SARibbonBar/SARibbonBar.pri +++ b/src/SARibbonBar/SARibbonBar.pri @@ -4,16 +4,16 @@ # 2.要预定义FRAMELESSHELPER_CORE_LIBRARY宏,这样才能导出符号 # 3.要预定义FRAMELESSHELPER_WIDGETS_LIBRARY宏,这样才能导出符号 -equals(QT_MAJOR_VERSION, 5){ - greaterThan(QT_MINOR_VERSION, 13){ - SA_RIBBON_CONFIG += use_frameless - } -}else{ -# qt6.0不支持 - greaterThan(QT_MINOR_VERSION, 0){ - SA_RIBBON_CONFIG += use_frameless - } -} +#equals(QT_MAJOR_VERSION, 5){ +# greaterThan(QT_MINOR_VERSION, 13){ +# SA_RIBBON_CONFIG += use_frameless +# } +#}else{ +## qt6.0不支持 +# greaterThan(QT_MINOR_VERSION, 0){ +# SA_RIBBON_CONFIG += use_frameless +# } +#} contains( SA_RIBBON_CONFIG, use_frameless ) { message("use frameless lib,compile with c+=17") @@ -32,6 +32,17 @@ contains( SA_RIBBON_CONFIG, use_frameless ) { message("do not use_frameless lib,compile with c+=11") CONFIG += c++11 DEFINES += SARIBBON_USE_3RDPARTY_FRAMELESSHELPER=0 + HEADERS += \ + $$PWD/framelesswindow.h + win32{ + SOURCES += \ + $$PWD/framelesswindow.cpp + } + macx{ + LIBS += -framework Cocoa + OBJECTIVE_SOURCES += \ + $$PWD/framelesswindow.mm + } } message("SA_RIBBON_CONFIG="$${SA_RIBBON_CONFIG}) #------------------------------------------------- diff --git a/src/SARibbonBar/SARibbonMainWindow.cpp b/src/SARibbonBar/SARibbonMainWindow.cpp index 665f4fe..085538f 100644 --- a/src/SARibbonBar/SARibbonMainWindow.cpp +++ b/src/SARibbonBar/SARibbonMainWindow.cpp @@ -40,7 +40,7 @@ class SARibbonMainWindow::PrivateData #if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER FRAMELESSHELPER_PREPEND_NAMESPACE(StandardTitleBar) * mTitleBar = nullptr; #else - SAFramelessHelper* mFramelessHelper { nullptr }; + #endif }; @@ -59,7 +59,7 @@ SARibbonMainWindow::SARibbonMainWindow(QWidget* parent, bool useRibbon, const Qt #if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER : FramelessMainWindow(parent, flags) #else - : QMainWindow(parent, flags) + : CFramelessWindow(parent) #endif , d_ptr(new SARibbonMainWindow::PrivateData(this)) { @@ -85,15 +85,11 @@ SARibbonBar* SARibbonMainWindow::ribbonBar() const } #if !SARIBBON_USE_3RDPARTY_FRAMELESSHELPER -SAFramelessHelper* SARibbonMainWindow::framelessHelper() -{ - return (d_ptr->mFramelessHelper); -} bool SARibbonMainWindow::eventFilter(QObject* obj, QEvent* e) { - //这个过滤是为了把ribbonBar上的动作传递到mainwindow,再传递到frameless, - //由于ribbonbar会遮挡刁frameless的区域,导致frameless无法捕获这些消息 + // 这个过滤是为了把ribbonBar上的动作传递到mainwindow,再传递到frameless, + // 由于ribbonbar会遮挡刁frameless的区域,导致frameless无法捕获这些消息 if (obj == ribbonBar()) { switch (e->type()) { case QEvent::MouseButtonPress: @@ -140,7 +136,7 @@ void SARibbonMainWindow::setRibbonTheme(SARibbonMainWindow::RibbonTheme theme) sa_set_ribbon_theme(this, theme); d_ptr->mCurrentRibbonTheme = theme; if (SARibbonBar* bar = ribbonBar()) { - //尺寸修正 + // 尺寸修正 switch (ribbonTheme()) { case RibbonThemeWindows7: break; @@ -165,7 +161,7 @@ void SARibbonMainWindow::setRibbonTheme(SARibbonMainWindow::RibbonTheme theme) default: break; } - //上下文标签颜色设置 + // 上下文标签颜色设置 switch (ribbonTheme()) { case RibbonThemeWindows7: case RibbonThemeOffice2013: @@ -238,7 +234,7 @@ void SARibbonMainWindow::installRibbonBar(SARibbonBar* bar) { QWidget* old = QMainWindow::menuWidget(); if (old) { - //如果之前已经设置了menubar,要把之前的删除 + // 如果之前已经设置了menubar,要把之前的删除 old->deleteLater(); } #if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER @@ -246,7 +242,7 @@ void SARibbonMainWindow::installRibbonBar(SARibbonBar* bar) QMainWindow::setMenuWidget(bar); helper->setTitleBarWidget(bar); - //设置window按钮 + // 设置window按钮 if (nullptr == d_ptr->mWindowButtonGroup) { d_ptr->mWindowButtonGroup = new SAWindowButtonGroup(this); } @@ -266,12 +262,9 @@ void SARibbonMainWindow::installRibbonBar(SARibbonBar* bar) QMainWindow::setMenuWidget(bar); bar->installEventFilter(this); - //设置窗体的标题栏高度 - if (nullptr == d_ptr->mFramelessHelper) { - d_ptr->mFramelessHelper = new SAFramelessHelper(this); - } - d_ptr->mFramelessHelper->setTitleHeight(bar->titleBarHeight()); - //设置window按钮 + setTitleBar(bar); + + // 设置window按钮 if (nullptr == d_ptr->mWindowButtonGroup) { d_ptr->mWindowButtonGroup = new SAWindowButtonGroup(this); } @@ -280,7 +273,7 @@ void SARibbonMainWindow::installRibbonBar(SARibbonBar* bar) d_ptr->mWindowButtonGroup->setFixedSize(s); d_ptr->mWindowButtonGroup->setWindowStates(windowState()); d_ptr->mWindowButtonGroup->show(); - + addHitTestVisibleWidgets(d_ptr->mWindowButtonGroup); #endif } diff --git a/src/SARibbonBar/SARibbonMainWindow.h b/src/SARibbonBar/SARibbonMainWindow.h index 48eb568..a5b8491 100644 --- a/src/SARibbonBar/SARibbonMainWindow.h +++ b/src/SARibbonBar/SARibbonMainWindow.h @@ -9,7 +9,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE class StandardTitleBar; FRAMELESSHELPER_END_NAMESPACE #else -class SAFramelessHelper; +#include "framelesswindow.h" #endif class SARibbonBar; @@ -40,7 +40,7 @@ class SARibbonBar; #if SARIBBON_USE_3RDPARTY_FRAMELESSHELPER class SA_RIBBON_EXPORT SARibbonMainWindow : public FRAMELESSHELPER_PREPEND_NAMESPACE(FramelessMainWindow) #else -class SA_RIBBON_EXPORT SARibbonMainWindow : public QMainWindow +class SA_RIBBON_EXPORT SARibbonMainWindow : public CFramelessWindow #endif { Q_OBJECT @@ -66,31 +66,29 @@ class SA_RIBBON_EXPORT SARibbonMainWindow : public QMainWindow public: SARibbonMainWindow(QWidget* parent = nullptr, bool useRibbon = true, const Qt::WindowFlags flags = {}); ~SARibbonMainWindow() Q_DECL_OVERRIDE; - //返回SARibbonBar + // 返回SARibbonBar SARibbonBar* ribbonBar() const; #if !SARIBBON_USE_3RDPARTY_FRAMELESSHELPER - //返回SAFramelessHelper - SAFramelessHelper* framelessHelper(); - //把ribbonbar的事件传递到frameless + // 把ribbonbar的事件传递到frameless virtual bool eventFilter(QObject* obj, QEvent* e) Q_DECL_OVERRIDE; #endif - //此函数仅用于控制最小最大化和关闭按钮的显示 + // 此函数仅用于控制最小最大化和关闭按钮的显示 void updateWindowFlag(Qt::WindowFlags flags); - //获取系统按钮的状态 + // 获取系统按钮的状态 Qt::WindowFlags windowButtonFlags() const; void setRibbonTheme(RibbonTheme theme); RibbonTheme ribbonTheme() const; - //判断当前是否使用ribbon模式 + // 判断当前是否使用ribbon模式 bool isUseRibbon() const; protected: - //创建ribbonbar的工厂函数 + // 创建ribbonbar的工厂函数 SARibbonBar* createRibbonBar(); virtual void resizeEvent(QResizeEvent* event) Q_DECL_OVERRIDE; virtual bool event(QEvent* e) Q_DECL_OVERRIDE; private: - //安装ribbon + // 安装ribbon void installRibbonBar(SARibbonBar* bar); }; diff --git a/src/SARibbonBar/framelesswindow.cpp b/src/SARibbonBar/framelesswindow.cpp new file mode 100644 index 0000000..9c1b5e0 --- /dev/null +++ b/src/SARibbonBar/framelesswindow.cpp @@ -0,0 +1,310 @@ +#include "framelesswindow.h" +#include +#include +#include +#ifdef Q_OS_WIN + +#include +#include +#include +#include +#include // Fixes error C2504: 'IUnknown' : base class undefined +#include +#include +#pragma comment(lib, "Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea +#pragma comment(lib, "user32.lib") + +CFramelessWindow::CFramelessWindow(QWidget* parent) + : QMainWindow(parent), m_titlebar(Q_NULLPTR), m_borderWidth(5), m_bJustMaximized(false), m_bResizeable(true) +{ + // setWindowFlag(Qt::Window,true); + // setWindowFlag(Qt::FramelessWindowHint, true); + // setWindowFlag(Qt::WindowSystemMenuHint, true); + // setWindowFlag() is not avaliable before Qt v5.9, so we should use setWindowFlags instead + + setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); + + setResizeable(m_bResizeable); +} + +void CFramelessWindow::setResizeable(bool resizeable) +{ + bool visible = isVisible(); + m_bResizeable = resizeable; + if (m_bResizeable) { + setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); + // setWindowFlag(Qt::WindowMaximizeButtonHint); + + // 此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏 + // + // this line will get titlebar/thick frame/Aero back, which is exactly what we want + // we will get rid of titlebar and thick frame again in nativeEvent() later + HWND hwnd = (HWND)this->winId(); + DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); + ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); + } else { + setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); + // setWindowFlag(Qt::WindowMaximizeButtonHint,false); + + HWND hwnd = (HWND)this->winId(); + DWORD style = ::GetWindowLong(hwnd, GWL_STYLE); + ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX & ~WS_CAPTION); + } + + // 保留一个像素的边框宽度,否则系统不会绘制边框阴影 + // + // we better left 1 piexl width of border untouch, so OS can draw nice shadow around it + const MARGINS shadow = { 1, 1, 1, 1 }; + DwmExtendFrameIntoClientArea(HWND(winId()), &shadow); + + setVisible(visible); +} + +void CFramelessWindow::setResizeableAreaWidth(int width) +{ + if (1 > width) + width = 1; + m_borderWidth = width; +} + +void CFramelessWindow::setTitleBar(QWidget* titlebar) +{ + m_titlebar = titlebar; + if (!titlebar) + return; + connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed())); +} + +void CFramelessWindow::onTitleBarDestroyed() +{ + if (m_titlebar == QObject::sender()) { + m_titlebar = Q_NULLPTR; + } +} + +void CFramelessWindow::addIgnoreWidget(QWidget* widget) +{ + if (!widget) + return; + if (m_whiteList.contains(widget)) + return; + m_whiteList.append(widget); +} + +void CFramelessWindow::addHitTestVisibleWidgets(QWidget* widget) +{ + if (!widget) + return; + if (m_hitTestVisibleWidgets.contains(widget)) + return; + m_hitTestVisibleWidgets.append(widget); +} + +bool CFramelessWindow::nativeEvent(const QByteArray& eventType, void* message, long* result) +{ +// Workaround for known bug -> check Qt forum : https://forum.qt.io/topic/93141/qtablewidget-itemselectionchanged/13 +#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1)) + MSG* msg = *reinterpret_cast< MSG** >(message); +#else + MSG* msg = reinterpret_cast< MSG* >(message); +#endif + + switch (msg->message) { + case WM_NCCALCSIZE: { + NCCALCSIZE_PARAMS& params = *reinterpret_cast< NCCALCSIZE_PARAMS* >(msg->lParam); + if (params.rgrc[ 0 ].top != 0) + params.rgrc[ 0 ].top -= 1; + + // this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION + *result = WVR_REDRAW; + return true; + } + case WM_NCHITTEST: { + *result = 0; + + const LONG border_width = m_borderWidth; + RECT winrect; + GetWindowRect(HWND(winId()), &winrect); + + long x = GET_X_LPARAM(msg->lParam); + long y = GET_Y_LPARAM(msg->lParam); + + if (m_bResizeable) { + + bool resizeWidth = minimumWidth() != maximumWidth(); + bool resizeHeight = minimumHeight() != maximumHeight(); + + if (resizeWidth) { + // left border + if (x >= winrect.left && x < winrect.left + border_width) { + *result = HTLEFT; + } + // right border + if (x < winrect.right && x >= winrect.right - border_width) { + *result = HTRIGHT; + } + } + if (resizeHeight) { + // bottom border + if (y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOM; + } + // top border + if (y >= winrect.top && y < winrect.top + border_width) { + *result = HTTOP; + } + } + if (resizeWidth && resizeHeight) { + // bottom left corner + if (x >= winrect.left && x < winrect.left + border_width && y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOMLEFT; + } + // bottom right corner + if (x < winrect.right && x >= winrect.right - border_width && y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOMRIGHT; + } + // top left corner + if (x >= winrect.left && x < winrect.left + border_width && y >= winrect.top && y < winrect.top + border_width) { + *result = HTTOPLEFT; + } + // top right corner + if (x < winrect.right && x >= winrect.right - border_width && y >= winrect.top && y < winrect.top + border_width) { + *result = HTTOPRIGHT; + } + } + } + if (0 != *result) + return true; + + //*result still equals 0, that means the cursor locate OUTSIDE the frame area + // but it may locate in titlebar area + if (!m_titlebar) + return false; + + // support highdpi + double dpr = this->devicePixelRatioF(); + // 先判断m_hitTestVisibleWidgets + for (QWidget* w : qAsConst(m_hitTestVisibleWidgets)) { + QPoint pos = w->mapFromGlobal(QPoint(x / dpr, y / dpr)); + if (w->rect().contains(pos)) { + return false; + } + } + + QPoint pos = m_titlebar->mapFromGlobal(QPoint(x / dpr, y / dpr)); + + if (!m_titlebar->rect().contains(pos)) + return false; + QWidget* child = m_titlebar->childAt(pos); + if (!child) { + *result = HTCAPTION; + return true; + } else { + if (m_whiteList.contains(child)) { + *result = HTCAPTION; + return true; + } + } + return false; + } // end case WM_NCHITTEST + case WM_GETMINMAXINFO: { + if (::IsZoomed(msg->hwnd)) { + RECT frame = { 0, 0, 0, 0 }; + AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0); + + // record frame area data + double dpr = this->devicePixelRatioF(); + + m_frames.setLeft(abs(frame.left) / dpr + 0.5); + m_frames.setTop(abs(frame.bottom) / dpr + 0.5); + m_frames.setRight(abs(frame.right) / dpr + 0.5); + m_frames.setBottom(abs(frame.bottom) / dpr + 0.5); + + QMainWindow::setContentsMargins(m_frames.left() + m_margins.left(), + m_frames.top() + m_margins.top(), + m_frames.right() + m_margins.right(), + m_frames.bottom() + m_margins.bottom()); + m_bJustMaximized = true; + } else { + if (m_bJustMaximized) { + QMainWindow::setContentsMargins(m_margins); + m_frames = QMargins(); + m_bJustMaximized = false; + } + } + return false; + } + default: + return QMainWindow::nativeEvent(eventType, message, result); + } +} + +void CFramelessWindow::moveEvent(QMoveEvent* event) +{ + if (!m_currentScreen) { + m_currentScreen = screen(); + } else if (m_currentScreen != screen()) { + m_currentScreen = screen(); + SetWindowPos((HWND)winId(), NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE); + } + QMainWindow::moveEvent(event); +} + +void CFramelessWindow::showEvent(QShowEvent* event) +{ + m_currentScreen = screen(); + QMainWindow::showEvent(event); +} + +void CFramelessWindow::setContentsMargins(const QMargins& margins) +{ + QMainWindow::setContentsMargins(margins + m_frames); + m_margins = margins; +} +void CFramelessWindow::setContentsMargins(int left, int top, int right, int bottom) +{ + QMainWindow::setContentsMargins(left + m_frames.left(), top + m_frames.top(), right + m_frames.right(), bottom + m_frames.bottom()); + m_margins.setLeft(left); + m_margins.setTop(top); + m_margins.setRight(right); + m_margins.setBottom(bottom); +} +QMargins CFramelessWindow::contentsMargins() const +{ + QMargins margins = QMainWindow::contentsMargins(); + margins -= m_frames; + return margins; +} +void CFramelessWindow::getContentsMargins(int* left, int* top, int* right, int* bottom) const +{ + QMainWindow::getContentsMargins(left, top, right, bottom); + if (!(left && top && right && bottom)) + return; + if (isMaximized()) { + *left -= m_frames.left(); + *top -= m_frames.top(); + *right -= m_frames.right(); + *bottom -= m_frames.bottom(); + } +} +QRect CFramelessWindow::contentsRect() const +{ + QRect rect = QMainWindow::contentsRect(); + int width = rect.width(); + int height = rect.height(); + rect.setLeft(rect.left() - m_frames.left()); + rect.setTop(rect.top() - m_frames.top()); + rect.setWidth(width); + rect.setHeight(height); + return rect; +} +void CFramelessWindow::showFullScreen() +{ + if (isMaximized()) { + QMainWindow::setContentsMargins(m_margins); + m_frames = QMargins(); + } + QMainWindow::showFullScreen(); +} + +#endif // Q_OS_WIN diff --git a/src/SARibbonBar/framelesswindow.h b/src/SARibbonBar/framelesswindow.h new file mode 100644 index 0000000..d38e4d0 --- /dev/null +++ b/src/SARibbonBar/framelesswindow.h @@ -0,0 +1,185 @@ +#ifndef CFRAMELESSWINDOW_H +#define CFRAMELESSWINDOW_H +#include "qsystemdetection.h" +#include +#include +#include "SARibbonGlobal.h" +// A nice frameless window for both Windows and OS X +// Author: Bringer-of-Light +// Github: https://github.com/Bringer-of-Light/Qt-Nice-Frameless-Window +// Usage: use "CFramelessWindow" as base class instead of "QMainWindow", and enjoy +#ifdef Q_OS_WIN +#include +#include +#include +#include +class SA_RIBBON_EXPORT CFramelessWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit CFramelessWindow(QWidget* parent = 0); + +public: + // 设置是否可以通过鼠标调整窗口大小 + // if resizeable is set to false, then the window can not be resized by mouse + // but still can be resized programtically + void setResizeable(bool resizeable = true); + bool isResizeable() + { + return m_bResizeable; + } + + // 设置可调整大小区域的宽度,在此区域内,可以使用鼠标调整窗口大小 + // set border width, inside this aera, window can be resized by mouse + void setResizeableAreaWidth(int width = 5); + +protected: + // 设置一个标题栏widget,此widget会被当做标题栏对待 + // set a widget which will be treat as SYSTEM titlebar + void setTitleBar(QWidget* titlebar); + + // 在标题栏控件内,也可以有子控件如标签控件“label1”,此label1遮盖了标题栏,导致不能通过label1拖动窗口 + // 要解决此问题,使用addIgnoreWidget(label1) + // generally, we can add widget say "label1" on titlebar, and it will cover the titlebar under it + // as a result, we can not drag and move the MainWindow with this "label1" again + // we can fix this by add "label1" to a ignorelist, just call addIgnoreWidget(label1) + void addIgnoreWidget(QWidget* widget); + + void addHitTestVisibleWidgets(QWidget* widget); + + bool nativeEvent(const QByteArray& eventType, void* message, long* result); + void moveEvent(QMoveEvent* event); + void showEvent(QShowEvent* event); +private slots: + void onTitleBarDestroyed(); + +public: + void setContentsMargins(const QMargins& margins); + void setContentsMargins(int left, int top, int right, int bottom); + QMargins contentsMargins() const; + QRect contentsRect() const; + void getContentsMargins(int* left, int* top, int* right, int* bottom) const; +public slots: + void showFullScreen(); + +private: + QWidget* m_titlebar; + QList< QWidget* > m_whiteList; + QList< QWidget* > m_hitTestVisibleWidgets; ///< czy:这个列表的窗口,点击区域如果命中了,就不执行拖动,直接命中 + int m_borderWidth; + + QMargins m_margins; + QMargins m_frames; + bool m_bJustMaximized; + + bool m_bResizeable; + QScreen* m_currentScreen { nullptr }; +}; + +#elif defined Q_OS_MAC +#include +#include +#include +class CFramelessWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit CFramelessWindow(QWidget* parent = 0); + +private: + void initUI(); + +public: + // 设置可拖动区域的高度,在此区域内,可以通过鼠标拖动窗口, 0表示整个窗口都可拖动 + // In draggable area, window can be moved by mouse, (height = 0) means that the whole window is draggable + void setDraggableAreaHeight(int height = 0); + + // 只有OS X10.10及以后系统,才支持OS X原生样式包括:三个系统按钮、窗口圆角、窗口阴影 + // 类初始化完成后,可以通过此函数查看是否已经启用了原生样式。如果未启动,需要自定义关闭按钮、最小化按钮、最大化按钮 + // Native style(three system button/ round corner/ drop shadow) works only on OS X 10.10 or later + // after init, we should check whether NativeStyle is OK with this function + // if NOT ok, we should implement close button/ min button/ max button ourself + bool isNativeStyleOK() + { + return m_bNativeSystemBtn; + } + + // 如果设置setCloseBtnQuit(false),那么点击关闭按钮后,程序不会退出,而是会隐藏,只有在OS X 10.10 及以后系统中有效 + // if setCloseBtnQuit(false), then when close button is clicked, the application will hide itself instead of quit + // be carefull, after you set this to false, you can NOT change it to true again + // this function should be called inside of the constructor function of derived classes, and can NOT be called more + // than once only works for OS X 10.10 or later + void setCloseBtnQuit(bool bQuit = true); + + // 启用或禁用关闭按钮,只有在isNativeStyleOK()返回true的情况下才有效 + // enable or disable Close button, only worked if isNativeStyleOK() returns true + void setCloseBtnEnabled(bool bEnable = true); + + // 启用或禁用最小化按钮,只有在isNativeStyleOK()返回true的情况下才有效 + // enable or disable Miniaturize button, only worked if isNativeStyleOK() returns true + void setMinBtnEnabled(bool bEnable = true); + + // 启用或禁用zoom(最大化)按钮,只有在isNativeStyleOK()返回true的情况下才有效 + // enable or disable Zoom button(fullscreen button), only worked if isNativeStyleOK() returns true + void setZoomBtnEnabled(bool bEnable = true); + + bool isCloseBtnEnabled() + { + return m_bIsCloseBtnEnabled; + } + bool isMinBtnEnabled() + { + return m_bIsMinBtnEnabled; + } + bool isZoomBtnEnabled() + { + return m_bIsZoomBtnEnabled; + } + +protected: + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + +private: + int m_draggableHeight; + bool m_bWinMoving; + bool m_bMousePressed; + QPoint m_MousePos; + QPoint m_WindowPos; + bool m_bCloseBtnQuit; + bool m_bNativeSystemBtn; + bool m_bIsCloseBtnEnabled, m_bIsMinBtnEnabled, m_bIsZoomBtnEnabled; + + //=============================================== + // TODO + // 下面的代码是试验性质的 + // tentative code + + // 窗口从全屏状态恢复正常大小时,标题栏又会出现,原因未知。 + // 默认情况下,系统的最大化按钮(zoom button)是进入全屏,为了避免标题栏重新出现的问题, + // 以上代码已经重新定义了系统zoom button的行为,是其功能变为最大化而不是全屏 + // 以下代码尝试,每次窗口从全屏状态恢复正常大小时,都再次进行设置,以消除标题栏 + // after the window restore from fullscreen mode, the titlebar will show again, it looks like a BUG + // on OS X 10.10 and later, click the system green button (zoom button) will make the app become fullscreen + // so we have override it's action to "maximized" in the CFramelessWindow Constructor function + // but we may try something else such as delete the titlebar again and again... +private: + bool m_bTitleBarVisible; + + void setTitlebarVisible(bool bTitlebarVisible = false); + bool isTitlebarVisible() + { + return m_bTitleBarVisible; + } +private slots: + void onRestoreFromFullScreen(); +signals: + void restoreFromFullScreen(); + +protected: + void resizeEvent(QResizeEvent* event); +}; +#endif + +#endif // CFRAMELESSWINDOW_H diff --git a/src/SARibbonBar/framelesswindow.mm b/src/SARibbonBar/framelesswindow.mm new file mode 100644 index 0000000..1f0e81a --- /dev/null +++ b/src/SARibbonBar/framelesswindow.mm @@ -0,0 +1,226 @@ +#include "framelesswindow.h" +#ifdef Q_OS_MAC +#include +#include + +CFramelessWindow::CFramelessWindow(QWidget *parent) + : QMainWindow(parent), + m_draggableHeight(0), + m_bWinMoving(false), + m_bMousePressed(false), + m_bCloseBtnQuit(true), + m_bNativeSystemBtn(false), + m_bIsCloseBtnEnabled(true), + m_bIsMinBtnEnabled(true), + m_bIsZoomBtnEnabled(true), + m_bTitleBarVisible(false) +{ + initUI(); +} + +//此类用于支持重载系统按钮的行为 +//this Objective-c class is used to override the action of sysytem close button and zoom button +//https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results +@interface ButtonPasser : NSObject{ +} +@property(readwrite) CFramelessWindow* window; ++ (void)closeButtonAction:(id)sender; +- (void)zoomButtonAction:(id)sender; +@end + +@implementation ButtonPasser{ +} ++ (void)closeButtonAction:(id)sender +{ + Q_UNUSED(sender); + ProcessSerialNumber pn; + GetFrontProcess (&pn); + ShowHideProcess(&pn,false); +} +- (void)zoomButtonAction:(id)sender +{ + Q_UNUSED(sender); + if (0 == self.window) return; + if (self.window->isMaximized()) self.window->showNormal(); + else self.window->showMaximized(); +} +@end + +void CFramelessWindow::initUI() +{ + m_bNativeSystemBtn = false; + + //如果当前osx版本老于10.9,则后续代码不可用。转为使用定制的系统按钮,不支持自由缩放窗口及窗口阴影 + if (QSysInfo::MV_None == QSysInfo::macVersion()) + { + if (QSysInfo::MV_None == QSysInfo::MacintoshVersion) {setWindowFlags(Qt::FramelessWindowHint); return;} + } + if (QSysInfo::MV_10_9 >= QSysInfo::MacintoshVersion) {setWindowFlags(Qt::FramelessWindowHint); return;} + + NSView* view = (NSView*)winId(); + if (0 == view) {setWindowFlags(Qt::FramelessWindowHint); return;} + NSWindow *window = view.window; + if (0 == window) {setWindowFlags(Qt::FramelessWindowHint); return;} + + //设置标题文字和图标为不可见 + window.titleVisibility = NSWindowTitleHidden; //MAC_10_10及以上版本支持 + //设置标题栏为透明 + window.titlebarAppearsTransparent = YES; //MAC_10_10及以上版本支持 + //设置不可由标题栏拖动,避免与自定义拖动冲突 + [window setMovable:NO]; //MAC_10_6及以上版本支持 + //window.movableByWindowBackground = YES; + //设置view扩展到标题栏 + window.styleMask |= NSWindowStyleMaskFullSizeContentView; //MAC_10_10及以上版本支持 + + m_bNativeSystemBtn = true; + + ButtonPasser * passer = [[ButtonPasser alloc] init]; + passer.window = this; + //重载全屏按钮的行为 + //override the action of fullscreen button + NSButton *zoomButton = [window standardWindowButton:NSWindowZoomButton]; + [zoomButton setTarget:passer]; + [zoomButton setAction:@selector(zoomButtonAction:)]; +} + +void CFramelessWindow::setCloseBtnQuit(bool bQuit) +{ + if (bQuit || !m_bNativeSystemBtn) return; + NSView* view = (NSView*)winId(); + if (0 == view) return; + NSWindow *window = view.window; + if (0 == window) return; + + //重载关闭按钮的行为 + //override the action of close button + //https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results + //https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html + NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; + [closeButton setTarget:[ButtonPasser class]]; + [closeButton setAction:@selector(closeButtonAction:)]; +} + +void CFramelessWindow::setCloseBtnEnabled(bool bEnable) +{ + if (!m_bNativeSystemBtn) return; + NSView* view = (NSView*)winId(); + if (0 == view) return; + NSWindow *window = view.window; + if (0 == window) return; + + m_bIsCloseBtnEnabled = bEnable; + if (bEnable){ + [[window standardWindowButton:NSWindowCloseButton] setEnabled:YES]; + }else{ + [[window standardWindowButton:NSWindowCloseButton] setEnabled:NO]; + } +} + +void CFramelessWindow::setMinBtnEnabled(bool bEnable) +{ + if (!m_bNativeSystemBtn) return; + NSView* view = (NSView*)winId(); + if (0 == view) return; + NSWindow *window = view.window; + if (0 == window) return; + + m_bIsMinBtnEnabled = bEnable; + if (bEnable){ + [[window standardWindowButton:NSWindowMiniaturizeButton] setEnabled:YES]; + }else{ + [[window standardWindowButton:NSWindowMiniaturizeButton] setEnabled:NO]; + } +} + +void CFramelessWindow::setZoomBtnEnabled(bool bEnable) +{ + if (!m_bNativeSystemBtn) return; + NSView* view = (NSView*)winId(); + if (0 == view) return; + NSWindow *window = view.window; + if (0 == window) return; + + m_bIsZoomBtnEnabled = bEnable; + if (bEnable){ + [[window standardWindowButton:NSWindowZoomButton] setEnabled:YES]; + }else{ + [[window standardWindowButton:NSWindowZoomButton] setEnabled:NO]; + } +} + +void CFramelessWindow::setDraggableAreaHeight(int height) +{ + if (height < 0) height = 0; + m_draggableHeight = height; +} + +void CFramelessWindow::mousePressEvent(QMouseEvent *event) +{ + if ((event->button() != Qt::LeftButton) || isMaximized() ) + { + return QMainWindow::mousePressEvent(event); + } + + int height = size().height(); + if (m_draggableHeight > 0) height = m_draggableHeight; + QRect rc; + rc.setRect(0,0,size().width(), height); + if(rc.contains(this->mapFromGlobal(QCursor::pos()))==true)//如果按下的位置 + { + m_WindowPos = this->pos(); + m_MousePos = event->globalPos(); + m_bMousePressed = true; + } + return QMainWindow::mousePressEvent(event); +} + +void CFramelessWindow::mouseReleaseEvent(QMouseEvent *event) +{ + m_bWinMoving = false; + if ((event->button() == Qt::LeftButton)) + { + m_bMousePressed = false; + } + return QMainWindow::mouseReleaseEvent(event); +} + +void CFramelessWindow::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_bMousePressed) return QMainWindow::mouseMoveEvent(event); + m_bWinMoving = true; + this->move(m_WindowPos + (event->globalPos() - m_MousePos)); + return QMainWindow::mouseMoveEvent(event); +} + +void CFramelessWindow::resizeEvent(QResizeEvent *event) +{ + QMainWindow::resizeEvent(event); + //TODO +// if (!isFullScreen()) +// { +// emit restoreFromFullScreen(); +// } +} + +void CFramelessWindow::onRestoreFromFullScreen() +{ + setTitlebarVisible(false); +} + +void CFramelessWindow::setTitlebarVisible(bool bTitlebarVisible) +{ + if (!m_bNativeSystemBtn) return; + NSView* view = (NSView*)winId(); + if (0 == view) return; + NSWindow *window = view.window; + if (0 == window) return; + + m_bTitleBarVisible = bTitlebarVisible; + if (bTitlebarVisible) + { + window.styleMask ^= NSWindowStyleMaskFullSizeContentView; //MAC_10_10及以上版本支持 + }else{ + window.styleMask |= NSWindowStyleMaskFullSizeContentView; //MAC_10_10及以上版本支持 + } +} +#endif //Q_OS_MAC diff --git a/src/example/MainWindowExample/mainwindow.cpp b/src/example/MainWindowExample/mainwindow.cpp index f5d6c4d..0a74518 100644 --- a/src/example/MainWindowExample/mainwindow.cpp +++ b/src/example/MainWindowExample/mainwindow.cpp @@ -59,8 +59,8 @@ MainWindow::MainWindow(QWidget* par) { PRINT_COST_START(); #if !SARIBBON_USE_3RDPARTY_FRAMELESSHELPER - SAFramelessHelper* helper = framelessHelper(); - helper->setRubberBandOnResize(false); +// SAFramelessHelper* helper = framelessHelper(); +// helper->setRubberBandOnResize(false); #endif setWindowTitle(("ribbon mainwindow test")); mTextedit = new QTextEdit(this); @@ -216,14 +216,14 @@ void MainWindow::onStyleClicked(int id) switch (ribbonStyle) { case SARibbonBar::RibbonStyleLooseThreeRow: mTextedit->append( - tr("\nchange ribbon style to office style,The standard office style text display is line wrapped, " - "and you can also control whether it wrap through SARibbonToolButton::setEnableWordWrap")); // cn:标准的office样式的文字显示是换行的,你也可以通过SARibbonToolButton::setEnableWordWrap来控制它是否换行 + tr("\nchange ribbon style to office style,The standard office style text display is line wrapped, " + "and you can also control whether it wrap through SARibbonToolButton::setEnableWordWrap")); // cn:标准的office样式的文字显示是换行的,你也可以通过SARibbonToolButton::setEnableWordWrap来控制它是否换行 mTextedit->append(tr("ribbonBar()->setRibbonStyle(SARibbonBar::OfficeStyle);")); break; case SARibbonBar::RibbonStyleLooseTwoRow: mTextedit->append( - tr("\nchange ribbon style to office style 2 row,All text in 2-line mode does not wrap, and you " - "can also control whether it wraps through SARibbonToolButton: setEnableWordWrap")); // cn:所有2行模式的文字都是不换行的,你也可以通过SARibbonToolButton::setEnableWordWrap来控制它是否换行 + tr("\nchange ribbon style to office style 2 row,All text in 2-line mode does not wrap, and you " + "can also control whether it wraps through SARibbonToolButton: setEnableWordWrap")); // cn:所有2行模式的文字都是不换行的,你也可以通过SARibbonToolButton::setEnableWordWrap来控制它是否换行 mTextedit->append(tr("ribbonBar()->setRibbonStyle(SARibbonBar::OfficeStyleTwoRow);")); break; case SARibbonBar::RibbonStyleCompactThreeRow: @@ -297,7 +297,7 @@ void MainWindow::onActionHelpTriggered() "\n Author:czy" "\n Email:czy.t@163.com" "\n ===============") - .arg(SARibbonBar::versionString())); + .arg(SARibbonBar::versionString())); } void MainWindow::onActionRemoveAppBtnTriggered(bool b) @@ -429,7 +429,7 @@ void MainWindow::onColorButtonColorClicked(const QColor& c, bool on) void MainWindow::onRibbonThemeComboBoxCurrentIndexChanged(int index) { SARibbonMainWindow::RibbonTheme t = static_cast< SARibbonMainWindow::RibbonTheme >( - mComboboxRibbonTheme->itemData(index).toInt()); + mComboboxRibbonTheme->itemData(index).toInt()); setRibbonTheme(t); }