QtCanpool 3.3.0
载入中...
搜索中...
未找到
framelesshelper.cpp
浏览该文件的文档.
1
5#include "framelesshelper.h"
6
7#include <QWidget>
8#include <QApplication>
9#include <QHoverEvent>
10#include <QMouseEvent>
11#include "qtcompat/qtcompat.h"
12#include <QIcon>
13#include <QSizeGrip>
14
15#ifdef FRAMELESS_USE_NATIVE
16#include <QWindow>
17#endif // FRAMELESS_USE_NATIVE
18
19#ifdef Q_OS_WINDOWS
20#include <windows.h>
21#include <windowsx.h>
22#include <winuser.h>
23#include <QScreen>
24#endif
25
26static int s_nBorderWidth = 5;
27static int s_nTitleHeight = 30;
28
29class FramelessWidgetData;
30
31class FramelessHelperPrivate
32{
33public:
34 FramelessHelperPrivate();
35 ~FramelessHelperPrivate();
36
37 bool isCaptionClassName(const char *name);
38public:
39 QHash<QWidget *, FramelessWidgetData *> m_widgetDataHash;
40 QList<QString> m_captionClassNameList;
41 bool m_bWidgetResizable = true;
42 bool m_bWidgetMovable = true;
43};
44
45/* FramelessHelperPrivate */
46FramelessHelperPrivate::FramelessHelperPrivate()
47{
48 m_captionClassNameList << "QWidget";
49}
50
51FramelessHelperPrivate::~FramelessHelperPrivate()
52{
53}
54
55bool FramelessHelperPrivate::isCaptionClassName(const char *name)
56{
57 foreach (const QString &cn, m_captionClassNameList) {
58 if (cn.compare(QLatin1String(name)) == 0) {
59 return true;
60 }
61 }
62 return false;
63}
64
65class FramelessWidgetData
66{
67public:
68 explicit FramelessWidgetData(FramelessHelperPrivate *_d, QWidget *widget);
69 virtual ~FramelessWidgetData();
70
71 QWidget *widget();
72 virtual bool handleWidgetEvent(QEvent *event);
73
74 void handleWindowStateChangeEvent();
75protected:
76 FramelessHelperPrivate *d;
77 QWidget *m_pWidget;
78};
79
80FramelessWidgetData::FramelessWidgetData(FramelessHelperPrivate *_d, QWidget *widget)
81{
82 Q_ASSERT(widget);
83 d = _d;
84 m_pWidget = widget;
85}
86
87FramelessWidgetData::~FramelessWidgetData()
88{
89}
90
91QWidget *FramelessWidgetData::widget()
92{
93 return m_pWidget;
94}
95
96bool FramelessWidgetData::handleWidgetEvent(QEvent *event)
97{
98 Q_UNUSED(event);
99 return false;
100}
101
102void FramelessWidgetData::handleWindowStateChangeEvent()
103{
104 Qt::WindowStates state = m_pWidget->windowState();
105 bool resizeDisable = state.testFlag(Qt::WindowFullScreen) || state.testFlag(Qt::WindowMaximized);
106 foreach (QSizeGrip *sg, m_pWidget->findChildren<QSizeGrip *>()) {
107 sg->setVisible(!resizeDisable);
108 }
109}
110
111#ifdef FRAMELESS_USE_NATIVE
112
113#ifdef Q_OS_WINDOWS
114
115#ifndef WM_DPICHANGED
116#define WM_DPICHANGED 0x02E0
117#endif
118
119/* FramelessWidgetDataNativeWin */
120
121class FramelessWidgetDataNativeWin : public FramelessWidgetData
122{
123public:
124 explicit FramelessWidgetDataNativeWin(FramelessHelperPrivate *_d, QWidget *widget);
125 virtual ~FramelessWidgetDataNativeWin();
126
127 qreal devicePixelRatio();
128
129 bool handleNativeWindowsMessage(MSG *msg, QTRESULT *result);
130
131 bool handleNonClinetCalcSize(MSG *msg, QTRESULT *result);
132 bool handleNonClientHitTest(MSG *msg, QTRESULT *result);
133
134 void setFrameChanged(MSG *msg);
135private:
136 UINT m_dpi[2] = {96, 96}; // 0-old, 1-new
137};
138
139FramelessWidgetDataNativeWin::FramelessWidgetDataNativeWin(FramelessHelperPrivate *_d, QWidget *widget)
140 : FramelessWidgetData(_d, widget)
141{
142 HWND hwnd = (HWND)m_pWidget->winId();
143 DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE);
144 ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW);
145
146 // FIXME: 96 is not real DPI, should get it through windows api
147 // there are some apis as follows: GetDpiForMonitor, GetDpiForSystem, GetDpiForWindow
148 // reference to https://learn.microsoft.com/zh-cn/windows/win32/api/_hidpi/
149}
150
151FramelessWidgetDataNativeWin::~FramelessWidgetDataNativeWin()
152{
153}
154
155qreal FramelessWidgetDataNativeWin::devicePixelRatio()
156{
157#if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
158 return m_pWidget->windowHandle()->screen()->devicePixelRatio();
159#else
160 return m_pWidget->screen()->devicePixelRatio();
161#endif
162}
163
164bool FramelessWidgetDataNativeWin::handleNativeWindowsMessage(MSG *msg, QTRESULT *result)
165{
166 switch (msg->message) {
167 case WM_NCCALCSIZE:
168 return handleNonClinetCalcSize(msg, result);
169 case WM_NCHITTEST:
170 return handleNonClientHitTest(msg, result);
171 case WM_DPICHANGED: {
172 // handleDpiChanged
173 m_dpi[0] = m_dpi[1];
174 m_dpi[1] = HIWORD(msg->wParam);
175 break;
176 }
177 case WM_SIZE:
178 // fallthrough
179 case WM_MOVE: {
180 if (m_dpi[0] != m_dpi[1]) {
181 break;
182 }
183 setFrameChanged(msg);
184 break;
185 }
186 }
187
188 return false;
189}
190
191bool FramelessWidgetDataNativeWin::handleNonClinetCalcSize(MSG *msg, QTRESULT *result)
192{
193 // https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
194 *result = 0;
195 // If wParam is FALSE, no need handle
196 if (!msg->wParam) {
197 return true;
198 }
199 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nccalcsize_params
200 // If wParam is TRUE, lParam points to the NCCALCSIZE_PARAMS structure, which contains information
201 // that the application can use to calculate the new size and position of the client rectangle.
202 NCCALCSIZE_PARAMS *pncsp = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam);
203
204 const int originalTop = pncsp->rgrc[0].top;
205 const RECT originalRect = pncsp->rgrc[0];
206
207 // call default window proc to handle WM_NCCALCSIZE message
208 const auto ret = ::DefWindowProc(msg->hwnd, WM_NCCALCSIZE, msg->wParam, msg->lParam);
209 if (ret != 0) {
210 *result = ret;
211 return true;
212 }
213 // extend frame into client area
214 pncsp->rgrc[0].top = originalTop;
215
216 bool isMaximized = GetWindowStyle(msg->hwnd) & WS_MAXIMIZE;
217 if (isMaximized) {
218#ifdef SM_CXPADDEDBORDER
219 int border = GetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
220#else
221 int border = GetSystemMetrics(SM_CXSIZEFRAME);
222#endif
223 pncsp->rgrc[0].top += border;
224 } else {
225 pncsp->rgrc[0] = originalRect;
226 }
227 return true;
228}
229
230bool FramelessWidgetDataNativeWin::handleNonClientHitTest(MSG *msg, QTRESULT *result)
231{
232 *result = HTNOWHERE;
233 if (m_pWidget->isFullScreen()) {
234 return false;
235 }
236
237 // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#appendix-c-hittestnca-function
238 int borderWidth = s_nBorderWidth;
239
240 long x = GET_X_LPARAM(msg->lParam);
241 long y = GET_Y_LPARAM(msg->lParam);
242 // If EnableHighDpiScaling is enabled for QApplication, then the pixelRatio is not necessarily 1.0,
243 // so pos needs to be scaled (tested on HightScreen-150% and LowScreen-100%)
244 qreal pixelRatio = devicePixelRatio();
245 QPoint pos = m_pWidget->mapFromGlobal(QPoint(x, y) / pixelRatio);
246 int w = m_pWidget->width();
247 int h = m_pWidget->height();
248
249 bool left = pos.x() < borderWidth;
250 bool right = pos.x() > w - borderWidth;
251 bool top = pos.y() < borderWidth;
252 bool bottom = pos.y() > h - borderWidth;
253
254 if (d->m_bWidgetResizable) {
255 if (top && left) {
256 *result = HTTOPLEFT;
257 } else if (top && right) {
258 *result = HTTOPRIGHT;
259 } else if (bottom && left) {
260 *result = HTBOTTOMLEFT;
261 } else if (bottom && right) {
262 *result = HTBOTTOMRIGHT;
263 } else if (left) {
264 *result = HTLEFT;
265 } else if (right) {
266 *result = HTRIGHT;
267 } else if (top) {
268 *result = HTTOP;
269 } else if (bottom) {
270 *result = HTBOTTOM;
271 }
272
273 if (*result != HTNOWHERE) {
274 return true;
275 }
276 }
277
278 if (d->m_bWidgetMovable && pos.y() < s_nTitleHeight) {
279 QWidget *child = m_pWidget->childAt(pos);
280 if (!child || d->isCaptionClassName(child->metaObject()->className())) {
281 *result = HTCAPTION;
282 return true;
283 }
284 }
285 return false;
286}
287
288void FramelessWidgetDataNativeWin::setFrameChanged(MSG *msg)
289{
290 // When the window is moved between two screens (neither scaled, i.e., dpi with default values),
291 // the window becomes smaller (the non-client area is lost and reduced to
292 // 8,31,8,8(left,top,right,bottom) around), so the window SWP_FRAMECHANGED needs to be reconfigured
293 // to expand the client area
294
295 RECT rcClient;
296 GetWindowRect(msg->hwnd, &rcClient);
297
298 // Inform application of the frame change.
299 // Specify the SWP_FRAMECHANGED flag to send a WM_NCCALCSIZE message to a window,
300 // even if the window size has not changed
301 SetWindowPos(msg->hwnd, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left,
302 rcClient.bottom - rcClient.top, SWP_FRAMECHANGED);
303}
304
305#endif // Q_OS_WINDOWS
306
307#else // not FRAMELESS_USE_NATIVE
308
309/* FramelessCursor */
310
311class FramelessCursor
312{
313public:
314 FramelessCursor();
315
316 void reset();
317 void update(const QPoint &gMousePos, const QRect &frameRect);
318public:
319 bool m_bOnEdges;
320 bool m_bOnLeftEdge;
321 bool m_bOnRightEdge;
322 bool m_bOnTopEdge;
323 bool m_bOnBottomEdge;
324 bool m_bOnTopLeftEdge;
325 bool m_bOnBottomLeftEdge;
326 bool m_bOnTopRightEdge;
327 bool m_bOnBottomRightEdge;
328};
329
330FramelessCursor::FramelessCursor()
331{
332 reset();
333}
334
335void FramelessCursor::reset()
336{
337 m_bOnEdges = false;
338 m_bOnLeftEdge = false;
339 m_bOnRightEdge = false;
340 m_bOnTopEdge = false;
341 m_bOnBottomEdge = false;
342 m_bOnTopLeftEdge = false;
343 m_bOnBottomLeftEdge = false;
344 m_bOnTopRightEdge = false;
345 m_bOnBottomRightEdge = false;
346}
347
348void FramelessCursor::update(const QPoint &gMousePos, const QRect &frameRect)
349{
350 int globalMouseX = gMousePos.x();
351 int globalMouseY = gMousePos.y();
352 int frameX = frameRect.x();
353 int frameY = frameRect.y();
354 int frameWidth = frameRect.width();
355 int frameHeight = frameRect.height();
356
357 m_bOnLeftEdge = (globalMouseX >= frameX && globalMouseX <= frameX + s_nBorderWidth);
358 m_bOnRightEdge = (globalMouseX >= frameX + frameWidth - s_nBorderWidth && globalMouseX <= frameX + frameWidth);
359 m_bOnTopEdge = (globalMouseY >= frameY && globalMouseY <= frameY + s_nBorderWidth);
360 m_bOnBottomEdge = (globalMouseY >= frameY + frameHeight - s_nBorderWidth && globalMouseY <= frameY + frameHeight);
361
362 m_bOnTopLeftEdge = m_bOnTopEdge && m_bOnLeftEdge;
363 m_bOnBottomLeftEdge = m_bOnBottomEdge && m_bOnLeftEdge;
364 m_bOnTopRightEdge = m_bOnTopEdge && m_bOnRightEdge;
365 m_bOnBottomRightEdge = m_bOnBottomEdge && m_bOnRightEdge;
366
367 m_bOnEdges = m_bOnLeftEdge || m_bOnRightEdge || m_bOnTopEdge || m_bOnBottomEdge;
368}
369
370/* FramelessWidgetDataQt */
371
372class FramelessWidgetDataQt : public FramelessWidgetData
373{
374public:
375 explicit FramelessWidgetDataQt(FramelessHelperPrivate *d, QWidget *widget);
376 ~FramelessWidgetDataQt();
377
378 bool handleWidgetEvent(QEvent *event) override;
379private:
380 void updateCursorShape(const QPoint &gMousePos);
381 void resizeWidget(const QPoint &gMousePos);
382 void moveWidget(const QPoint &gMousePos);
383
384 bool handleMousePressEvent(QMouseEvent *event);
385 bool handleMouseReleaseEvent(QMouseEvent *event);
386 bool handleMouseMoveEvent(QMouseEvent *event);
387 bool handleLeaveEvent(QEvent *event);
388 bool handleHoverMoveEvent(QHoverEvent *event);
389 bool handleDoubleClickedMouseEvent(QMouseEvent *event);
390private:
391 QPoint m_ptDragPos;
392 FramelessCursor m_pressedMousePos;
393 FramelessCursor m_moveMousePos;
394 bool m_bLeftButtonPressed;
395 bool m_bCursorShapeChanged;
396 bool m_bLeftButtonTitlePressed;
397};
398
399FramelessWidgetDataQt::FramelessWidgetDataQt(FramelessHelperPrivate *_d, QWidget *widget)
400 : FramelessWidgetData(_d, widget)
401{
402 m_bLeftButtonPressed = false;
403 m_bCursorShapeChanged = false;
404 m_bLeftButtonTitlePressed = false;
405
406 m_pWidget->setMouseTracking(true);
407 m_pWidget->setAttribute(Qt::WA_Hover, true);
408}
409
410FramelessWidgetDataQt::~FramelessWidgetDataQt()
411{
412 m_pWidget->setMouseTracking(false);
413 m_pWidget->setAttribute(Qt::WA_Hover, false);
414}
415
416bool FramelessWidgetDataQt::handleWidgetEvent(QEvent *event)
417{
418 switch (event->type()) {
419 case QEvent::MouseButtonPress:
420 return handleMousePressEvent(static_cast<QMouseEvent *>(event));
421 case QEvent::MouseButtonRelease:
422 return handleMouseReleaseEvent(static_cast<QMouseEvent *>(event));
423 case QEvent::MouseMove:
424 return handleMouseMoveEvent(static_cast<QMouseEvent *>(event));
425 case QEvent::Leave:
426 return handleLeaveEvent(event);
427 case QEvent::HoverMove:
428 return handleHoverMoveEvent(static_cast<QHoverEvent *>(event));
429 case QEvent::MouseButtonDblClick:
430 return handleDoubleClickedMouseEvent(static_cast<QMouseEvent *>(event));
431 default:
432 break;
433 }
434 return false;
435}
436
437void FramelessWidgetDataQt::updateCursorShape(const QPoint &gMousePos)
438{
439 if (m_pWidget->isFullScreen() || m_pWidget->isMaximized()) {
440 if (m_bCursorShapeChanged) {
441 m_pWidget->unsetCursor();
442 }
443 return;
444 }
445
446 m_moveMousePos.update(gMousePos, m_pWidget->frameGeometry());
447
448 if (m_moveMousePos.m_bOnTopLeftEdge || m_moveMousePos.m_bOnBottomRightEdge) {
449 m_pWidget->setCursor(Qt::SizeFDiagCursor);
450 m_bCursorShapeChanged = true;
451 } else if (m_moveMousePos.m_bOnTopRightEdge || m_moveMousePos.m_bOnBottomLeftEdge) {
452 m_pWidget->setCursor(Qt::SizeBDiagCursor);
453 m_bCursorShapeChanged = true;
454 } else if (m_moveMousePos.m_bOnLeftEdge || m_moveMousePos.m_bOnRightEdge) {
455 m_pWidget->setCursor(Qt::SizeHorCursor);
456 m_bCursorShapeChanged = true;
457 } else if (m_moveMousePos.m_bOnTopEdge || m_moveMousePos.m_bOnBottomEdge) {
458 m_pWidget->setCursor(Qt::SizeVerCursor);
459 m_bCursorShapeChanged = true;
460 } else {
461 if (m_bCursorShapeChanged) {
462 m_pWidget->unsetCursor();
463 m_bCursorShapeChanged = false;
464 }
465 }
466}
467
468void FramelessWidgetDataQt::resizeWidget(const QPoint &gMousePos)
469{
470 QRect origRect = m_pWidget->frameGeometry();
471
472 int left = origRect.left();
473 int top = origRect.top();
474 int right = origRect.right();
475 int bottom = origRect.bottom();
476
477 origRect.getCoords(&left, &top, &right, &bottom);
478
479 int minWidth = m_pWidget->minimumWidth();
480 int minHeight = m_pWidget->minimumHeight();
481
482 if (m_pressedMousePos.m_bOnTopLeftEdge) {
483 left = gMousePos.x();
484 top = gMousePos.y();
485 } else if (m_pressedMousePos.m_bOnBottomLeftEdge) {
486 left = gMousePos.x();
487 bottom = gMousePos.y();
488 } else if (m_pressedMousePos.m_bOnTopRightEdge) {
489 right = gMousePos.x();
490 top = gMousePos.y();
491 } else if (m_pressedMousePos.m_bOnBottomRightEdge) {
492 right = gMousePos.x();
493 bottom = gMousePos.y();
494 } else if (m_pressedMousePos.m_bOnLeftEdge) {
495 left = gMousePos.x();
496 } else if (m_pressedMousePos.m_bOnRightEdge) {
497 right = gMousePos.x();
498 } else if (m_pressedMousePos.m_bOnTopEdge) {
499 top = gMousePos.y();
500 } else if (m_pressedMousePos.m_bOnBottomEdge) {
501 bottom = gMousePos.y();
502 }
503
504 QRect newRect(QPoint(left, top), QPoint(right, bottom));
505
506 if (newRect.isValid()) {
507 if (minWidth > newRect.width()) {
508 if (left != origRect.left()) {
509 newRect.setLeft(origRect.left());
510 } else {
511 newRect.setRight(origRect.right());
512 }
513 }
514 if (minHeight > newRect.height()) {
515 if (top != origRect.top()) {
516 newRect.setTop(origRect.top());
517 } else {
518 newRect.setBottom(origRect.bottom());
519 }
520 }
521
522 m_pWidget->setGeometry(newRect);
523 }
524}
525
526void FramelessWidgetDataQt::moveWidget(const QPoint &gMousePos)
527{
528 m_pWidget->move(gMousePos - m_ptDragPos);
529}
530
531bool FramelessWidgetDataQt::handleMousePressEvent(QMouseEvent *event)
532{
533 if (event->button() == Qt::LeftButton) {
534 m_bLeftButtonPressed = true;
535 m_bLeftButtonTitlePressed = event->pos().y() < s_nTitleHeight;
536
537 QRect frameRect = m_pWidget->frameGeometry();
538#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
539 m_pressedMousePos.update(QtCanpoolCompat::globalMousePos(event), frameRect);
540 m_ptDragPos = QtCanpoolCompat::globalMousePos(event) - frameRect.topLeft();
541#else
542 m_pressedMousePos.update(event->globalPosition().toPoint(), frameRect);
543 m_ptDragPos = event->globalPosition().toPoint() - frameRect.topLeft();
544#endif
545
546 if (m_pressedMousePos.m_bOnEdges) {
547 if (m_pWidget->isMaximized() || m_pWidget->isFullScreen()) {
548 return false;
549 }
550 }
551 }
552 return false;
553}
554
555bool FramelessWidgetDataQt::handleMouseReleaseEvent(QMouseEvent *event)
556{
557 if (event->button() == Qt::LeftButton) {
558 m_bLeftButtonPressed = false;
559 m_bLeftButtonTitlePressed = false;
560 m_pressedMousePos.reset();
561 }
562 return false;
563}
564
565bool FramelessWidgetDataQt::handleMouseMoveEvent(QMouseEvent *event)
566{
567 if (m_bLeftButtonPressed) {
568 if (d->m_bWidgetResizable && m_pressedMousePos.m_bOnEdges) {
569 // 窗口在最大化状态时,点击边界不做任何处理
570 if (m_pWidget->isMaximized() || m_pWidget->isFullScreen()) {
571 return false;
572 }
573#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
574 resizeWidget(QtCanpoolCompat::globalMousePos(event));
575#else
576 resizeWidget(event->globalPosition().toPoint());
577#endif
578 return true;
579 } else if (d->m_bWidgetMovable && m_bLeftButtonTitlePressed) {
580 if (m_pWidget->isFullScreen()) {
581 return false;
582 }
583 if (m_pWidget->isMaximized()) {
584 // FIXME:1)Ubuntu 16.04 LTS/Unity 7.4.0环境中,最大化后鼠标拖拽标题栏获得的normalGeometry不是原始的,
585 // 而是无边框最大化后的尺寸,鼠标拖拽窗口最上面的Unity为窗口设计的标题栏才能获得真实的normalGeometry
586 // 2)GNOME环境中,最大化后鼠标拖拽标题栏获得的normalGeometry是原始的,但是会一闪而过,然后尺寸变成最大化的尺寸,
587 // 此时,由于尺寸已是最大化,又因为无边框后,鼠标移动窗口无法将窗口移到非显示区,导致根本无法移动
588 // 建议:Linux环境下不使用无边框方案
589
590 // 先求出窗口到鼠标的相对位置
591 QRect normalGeometry = m_pWidget->normalGeometry();
592 m_pWidget->showNormal();
593#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
594 QPoint p = QtCanpoolCompat::globalMousePos(event);
595#else
596 QPoint p = event->globalPosition().toPoint();
597#endif
598 p.ry() -= 10;
599 p.rx() -= (normalGeometry.width() / 2);
600 m_pWidget->move(p);
601 // 这时要重置m_ptDragPo
602 m_ptDragPos = QPoint(normalGeometry.width() / 2, 10);
603 return true;
604 }
605#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
606 moveWidget(QtCanpoolCompat::globalMousePos(event));
607#else
608 moveWidget(event->globalPosition().toPoint());
609#endif
610 return true;
611 }
612 return false;
613 } else if (d->m_bWidgetResizable) {
614#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
615 updateCursorShape(QtCanpoolCompat::globalMousePos(event));
616#else
617 updateCursorShape(event->globalPosition().toPoint());
618#endif
619 }
620 return false;
621}
622
623bool FramelessWidgetDataQt::handleLeaveEvent(QEvent *event)
624{
625 Q_UNUSED(event)
626 if (!m_bLeftButtonPressed) {
627 m_pWidget->unsetCursor();
628 return true;
629 }
630 return false;
631}
632
633bool FramelessWidgetDataQt::handleHoverMoveEvent(QHoverEvent *event)
634{
635 if (d->m_bWidgetResizable) {
636#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
637 updateCursorShape(m_pWidget->mapToGlobal(event->pos()));
638#else
639 updateCursorShape(m_pWidget->mapToGlobal(event->position().toPoint()));
640#endif
641 }
642 return false;
643}
644
645bool FramelessWidgetDataQt::handleDoubleClickedMouseEvent(QMouseEvent *event)
646{
647 if (event->button() == Qt::LeftButton) {
648 if (m_pWidget->windowFlags() & Qt::WindowMaximizeButtonHint) {
649 bool titlePressed = event->pos().y() < s_nTitleHeight;
650 if (titlePressed && !m_pWidget->isFullScreen()) {
651 if (m_pWidget->isMaximized()) {
652 m_pWidget->showNormal();
653 } else {
654 m_pWidget->showMaximized();
655 }
656 return true;
657 }
658 }
659 }
660 return false;
661}
662
663#endif // FRAMELESS_USE_NATIVE
664
665/* FramelessHelper */
667 : QObject(parent)
668 , d(new FramelessHelperPrivate)
669{
670 if (parent) {
671 if (QWidget *w = qobject_cast<QWidget *>(parent)) {
672 w->setWindowFlags(w->windowFlags() | Qt::FramelessWindowHint);
673 addWidget(w);
674 }
675 }
676 qApp->installNativeEventFilter(this);
677}
678
680{
681 qApp->removeNativeEventFilter(this);
682 QList<QWidget *> keys = d->m_widgetDataHash.keys();
683 int size = keys.size();
684
685 for (int i = 0; i < size; ++i) {
686 keys[i]->removeEventFilter(this);
687 delete d->m_widgetDataHash.take(keys[i]);
688 }
689 delete d;
690}
691
693{
694 if (!d->m_widgetDataHash.contains(w)) {
695#ifdef FRAMELESS_USE_NATIVE
696#ifdef Q_OS_WINDOWS
697 FramelessWidgetDataNativeWin *data = new FramelessWidgetDataNativeWin(d, w);
698#else
699 FramelessWidgetData *data = new FramelessWidgetData(d, w);
700#endif
701#else // FRAMELESS_USE_NATIVE
702 FramelessWidgetDataQt *data = new FramelessWidgetDataQt(d, w);
703#endif
704 d->m_widgetDataHash.insert(w, data);
705 w->installEventFilter(this);
706
707 connect(w, &QWidget::destroyed, this, [this, w]() {
708 this->removeWidget(w);
709 });
710 }
711}
712
714{
715 w->disconnect(this);
716 FramelessWidgetData *data = d->m_widgetDataHash.take(w);
717 if (data) {
718 w->removeEventFilter(this);
719 delete data;
720 }
721}
722
724{
725 return d->m_bWidgetResizable;
726}
727
729{
730 d->m_bWidgetResizable = resizable;
731}
732
734{
735 return d->m_bWidgetMovable;
736}
737
739{
740 d->m_bWidgetMovable = movable;
741}
742
744{
745 return s_nTitleHeight;
746}
747
749{
750 if (height > 0) {
751 s_nTitleHeight = height;
752 }
753}
754
756{
757 return s_nBorderWidth;
758}
759
761{
762 if (width > 0) {
763 s_nBorderWidth = width;
764 }
765}
766
768{
769 if (!d->m_captionClassNameList.contains(name)) {
770 d->m_captionClassNameList.append(name);
771 }
772}
773
774bool FramelessHelper::eventFilter(QObject *object, QEvent *event)
775{
776 QWidget *widget = static_cast<QWidget *>(object);
777 FramelessWidgetData *data = d->m_widgetDataHash.value(widget);
778 if (!data) {
779 return QObject::eventFilter(object, event);
780 }
781 switch (event->type()) {
782 case QEvent::WindowTitleChange:
783 Q_EMIT windowTitleChanged(widget->windowTitle());
784 break;
785 case QEvent::WindowIconChange:
786 Q_EMIT windowIconChanged(widget->windowIcon());
787 break;
788 case QEvent::WindowStateChange:
789 data->handleWindowStateChangeEvent();
790 Q_EMIT windowStateChanged(widget->windowState());
791 break;
792 default:
793 return data->handleWidgetEvent(event);
794 }
795 return QObject::eventFilter(object, event);
796}
797
798bool FramelessHelper::nativeEventFilter(const QByteArray &eventType, void *message, QTRESULT *result)
799{
800#ifdef Q_OS_WINDOWS
801 if (eventType == "windows_generic_MSG") {
802 MSG *msg = reinterpret_cast<MSG *>(message);
803 QWidget *widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
804 FramelessWidgetData *data = d->m_widgetDataHash.value(widget);
805 if (data == nullptr) {
806 return false;
807 }
808#ifndef FRAMELESS_USE_NATIVE
809 switch (msg->message) {
810 case WM_GETMINMAXINFO: {
811 // prevent taskbar is covered when maximized
812 if (widget->isMaximized()) {
813 QScreen *screen = widget->screen();
814 qreal ratio = screen->devicePixelRatio();
815 const QRect rc = screen->availableGeometry();
816 MINMAXINFO *p = (MINMAXINFO *)(msg->lParam);
817 p->ptMaxPosition.x = 0;
818 p->ptMaxPosition.y = 0;
819 p->ptMaxSize.x = rc.width() * ratio;
820 p->ptMaxSize.y = rc.height() * ratio;
821 *result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
822 return true;
823 }
824 }
825 }
826#else
827 FramelessWidgetDataNativeWin *winData = reinterpret_cast<FramelessWidgetDataNativeWin *>(data);
828 if (winData == nullptr) {
829 return false;
830 }
831 return winData->handleNativeWindowsMessage(msg, result);
832#endif // FRAMELESS_USE_NATIVE
833 }
834#else
835 Q_UNUSED(eventType)
836 Q_UNUSED(message)
837 Q_UNUSED(result)
838#endif // Q_OS_WINDOWS
839
840 return false;
841}
void removeWidget(QWidget *w)
int titleHeight() const
void windowTitleChanged(const QString &title)
FramelessHelper(QObject *parent)
void setWidgetResizable(bool resizable)
void windowStateChanged(Qt::WindowStates state)
bool widgetMovable() const
void addCaptionClassName(const QString &name)
bool widgetResizable() const
void setWidgetMovable(bool movable)
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
void addWidget(QWidget *w)
virtual bool eventFilter(QObject *object, QEvent *event)
int borderWidth() const
void setTitleHeight(int height)
void windowIconChanged(const QIcon &icon)
void setBorderWidth(int width)
#define QTRESULT
Copyleft (C) 2023-2024 maminjie canpool@163.com SPDX-License-Identifier: MIT
QPoint globalMousePos(const QMouseEvent *event)
Definition qtcompat.h:26
const char * name
Definition qxtheme.cpp:25
#define GET_Y_LPARAM(lp)
#define SM_CXPADDEDBORDER
#define WM_DPICHANGED
#define GET_X_LPARAM(lp)
Copyright (C) 2023-2024 maminjie canpool@163.com SPDX-License-Identifier: Apache-2....