swift
enableforframelesswindow.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QEvent>
7 #include <QFlags>
8 #include <QHBoxLayout>
9 #include <QMainWindow>
10 #include <QMenuBar>
11 #include <QMouseEvent>
12 #include <QObject>
13 #include <QPointer>
14 #include <QPushButton>
15 #include <QRect>
16 #include <QSizeGrip>
17 #include <QStatusBar>
18 #include <QThread>
19 #include <QTimer>
20 #include <QVariant>
21 #include <QWidget>
22 #include <QtGlobal>
23 
24 #include "gui/guiutility.h"
25 #include "misc/icons.h"
26 #include "misc/stringutils.h"
27 #include "misc/worker.h"
28 
29 using namespace swift::misc;
30 
31 namespace swift::gui
32 {
33  CEnableForFramelessWindow::CEnableForFramelessWindow(CEnableForFramelessWindow::WindowMode mode,
34  bool isMainApplicationWindow,
35  const char *framelessPropertyName,
36  QWidget *correspondingWidget)
37  : m_windowMode(mode), m_isMainApplicationWindow(isMainApplicationWindow), m_widget(correspondingWidget),
38  m_framelessPropertyName(framelessPropertyName)
39  {
40  Q_ASSERT(correspondingWidget);
41  Q_ASSERT(!m_framelessPropertyName.isEmpty());
42  m_originalWindowMode = mode;
43  this->setWindowAttributes(mode);
44  this->windowFlagsChanged();
45  }
46 
48  {
49  if (mode == m_windowMode) { return; }
50  m_windowMode = mode;
51 
52  // set the main window or dock widget flags and attributes
53  m_widget->setWindowFlags(modeToWindowFlags(mode));
54  this->windowFlagsChanged();
55  this->setWindowAttributes(mode);
56  m_widget->show();
57  }
58 
60  {
61  const bool isFrameless = this->isFrameless();
62  if (isFrameless == frameless) { return; }
63 
64  QWidget *w = this->getWidget();
65  if (!w) { return; }
66  const QRect oldFrameGeometry = w->frameGeometry();
67  const QRect oldGeometry = w->geometry();
68 
69  WindowMode nonFrameLessMode = m_originalWindowMode; // Tool/Normal Window
70  if (nonFrameLessMode == WindowFrameless) { nonFrameLessMode = WindowNormal; }
71  this->setMode(frameless ? WindowFrameless : nonFrameLessMode);
72 
73  if (frameless)
74  {
75  // from framed to frameless
76  w->setGeometry(oldFrameGeometry);
77  m_windowFrameSizeW = oldFrameGeometry.width() - oldGeometry.width();
78  m_windowFrameSizeH = oldFrameGeometry.height() - oldGeometry.height();
79  }
80  else
81  {
82  if (m_windowFrameSizeW >= 0 && m_windowFrameSizeH >= 0)
83  {
84  QRect newGeometry = oldGeometry;
85  // newGeometry.setWidth(oldGeometry.width() - m_windowFrameSizeW);
86  // newGeometry.setHeight(oldGeometry.height() - m_windowFrameSizeH);
87  newGeometry.setX(oldGeometry.x() + m_windowFrameSizeW);
88  newGeometry.setY(oldGeometry.y() + m_windowFrameSizeH);
89  w->setGeometry(newGeometry);
90  }
91  }
92  }
93 
95  {
96  Qt::WindowFlags flags = m_widget->windowFlags();
97  if (onTop) { flags |= Qt::WindowStaysOnTopHint; }
98  else { flags &= ~Qt::WindowStaysOnTopHint; }
99  m_widget->setWindowFlags(flags);
100  this->windowFlagsChanged();
101  }
102 
104  {
105  if (!m_widget) { return; }
106  m_widget->setWindowState(Qt::WindowActive);
107  }
108 
110  {
111  const QString ws(s.trimmed().toLower());
112  if (ws.isEmpty()) { return WindowNormal; }
113  if (ws.contains("frameless") || ws.startsWith("f")) { return WindowFrameless; }
114  if (ws.contains("tool") || ws.startsWith("t")) { return WindowTool; }
115  return WindowNormal;
116  }
117 
119  {
120  static const QString n("normal");
121  static const QString f("frameless");
122  static const QString t("tool");
123 
124  switch (m)
125  {
126  case WindowFrameless: return f;
127  case WindowNormal: return n;
128  case WindowTool: return t;
129  default: break;
130  }
131  return n;
132  }
133 
135  {
136  // void
137  }
138 
140  {
141  Q_ASSERT_X(m_widget, "CEnableForFramelessWindow::setWindowAttributes", "Missing widget representing window");
142  Q_ASSERT_X(!m_framelessPropertyName.isEmpty(), "CEnableForFramelessWindow::setWindowAttributes",
143  "Missing property name");
144 
145  const bool frameless = (mode == WindowFrameless);
146 
147  // http://stackoverflow.com/questions/18316710/frameless-and-transparent-window-qt5
148  // https://bugreports.qt.io/browse/QTBUG-52206
149  // UpdateLayeredWindowIndirect failed for ptDst
151  {
152  m_widget->setAttribute(Qt::WA_NativeWindow);
153 
154  // causing a BLACK background
155  m_widget->setAttribute(Qt::WA_NoSystemBackground, frameless);
156  m_widget->setAttribute(Qt::WA_TranslucentBackground, frameless); // causing QTBUG-52206
157  }
158 
159  // Qt::WA_PaintOnScreen leads to a warning
160  // setMask(QRegion(10, 10, 10, 10) would work, but requires "complex" calcs for rounded corners
162  this->setDynamicProperties(frameless);
163  }
164 
166  {
167  Q_ASSERT_X(m_widget, "CEnableForFramelessWindow::setDynamicProperties", "Missing widget representing window");
168  Q_ASSERT_X(!m_framelessPropertyName.isEmpty(), "CEnableForFramelessWindow::setDynamicProperties",
169  "Missing property name");
170 
171  // property selector will check on string, so I directly provide a string
172  const QString f(swift::misc::boolToTrueFalse(frameless));
173  m_widget->setProperty(m_framelessPropertyName.constData(), f);
174  for (QObject *w : m_widget->children())
175  {
176  if (w && w->isWidgetType()) { w->setProperty(m_framelessPropertyName.constData(), f); }
177  }
178  }
179 
181  {
182  if (m_windowMode == CEnableForFramelessWindow::WindowTool) { this->toolToNormalWindow(); }
183  m_widget->showMinimized();
184  }
185 
187  {
188  if (m_windowMode == CEnableForFramelessWindow::WindowTool) { this->normalToToolWindow(); }
189  m_widget->showMinimized();
190  }
191 
193  {
194  Q_ASSERT(m_widget);
195  if (m_windowMode == WindowFrameless && event->button() == Qt::LeftButton)
196  {
197  m_framelessDragPosition = event->globalPos() - m_widget->frameGeometry().topLeft();
198  event->accept();
199  return true;
200  }
201  return false;
202  }
203 
205  {
206  Q_ASSERT(m_widget);
207  if (m_windowMode == WindowFrameless && event->buttons() == Qt::LeftButton && !m_framelessDragPosition.isNull())
208  {
209  const QSize s = m_widget->size();
210  const bool changedSize = (m_moveSize != s);
211 
212  // avoid "jumping around" if window was resized
213  // resizing in frameless is subject of QSizeGrip in the status bar
214  if (changedSize)
215  {
216  m_moveSize = s;
217  return false;
218  }
219 
220  m_widget->move(event->globalPos() - m_framelessDragPosition);
221  event->accept();
222  return true;
223  }
224  return false;
225  }
226 
228  {
229  if (event->type() != QEvent::WindowStateChange) { return false; }
230  if (m_windowMode != WindowTool) { return false; }
231  if (!m_widget) { return false; }
232 
233  // make sure a tool window is changed to Normal window so it is show in taskbar
234  // here we are already in transition state, so isMinimized means will be minimize right now
235  // this check here is needed if minimized is called from somewhere else than ps_showMinimized
236 
237  QPointer<QWidget> widgetSelf(m_widget); // almost as good as myself
238  if (m_widget->isMinimized())
239  {
240  // still tool, force normal window
241  // decouple, otherwise we end up in infinite loop as it triggers a new changeEvent
242 
243  QTimer::singleShot(0, m_widget, [=] {
244  if (!widgetSelf) { return; }
245  this->showMinimizedModeChecked();
246  });
247  }
248  else
249  {
250  // not tool, force tool window
251  // decouple, otherwise we end up in infinite loop as it triggers a new changeEvent
252  QTimer::singleShot(0, m_widget, [=] {
253  if (!widgetSelf) { return; }
254  this->showNormalModeChecked();
255  });
256  }
257  event->accept();
258  return true;
259  }
260 
262  {
263  if (!statusBar) { return; }
264  if (!m_framelessSizeGrip)
265  {
266  m_framelessSizeGrip = new QSizeGrip(m_widget);
267  m_framelessSizeGrip->setObjectName("sg_FramelessSizeGrip");
268  statusBar->addPermanentWidget(m_framelessSizeGrip);
269  }
270  else { m_framelessSizeGrip->show(); }
271  statusBar->repaint();
272  }
273 
275  {
276  if (!m_framelessSizeGrip) { return; }
277  m_framelessSizeGrip->hide();
278  }
279 
281  {
282  Q_ASSERT(isFrameless());
283  Q_ASSERT(menuBar);
284  Q_ASSERT(m_widget);
285 
287  {
288  m_framelessCloseButton = new QPushButton(m_widget);
289  m_framelessCloseButton->setObjectName("pb_FramelessCloseButton");
290  m_framelessCloseButton->setIcon(CIcons::close16());
291  QObject::connect(m_framelessCloseButton, &QPushButton::clicked, m_widget, &QWidget::close,
292  Qt::QueuedConnection);
293  }
294 
295  QHBoxLayout *menuBarLayout = new QHBoxLayout;
296  menuBarLayout->setObjectName("hl_MenuBar");
297  menuBarLayout->addWidget(menuBar, 0, Qt::AlignTop | Qt::AlignLeft);
298  menuBarLayout->addWidget(m_framelessCloseButton, 0, Qt::AlignTop | Qt::AlignRight);
299  return menuBarLayout;
300  }
301 
303  {
304  m_widget->setWindowFlags((m_widget->windowFlags() & (~Qt::Tool)) | Qt::Window);
305  this->windowFlagsChanged();
306  m_originalWindowMode = WindowNormal;
307  }
308 
310  {
311  m_widget->setWindowFlags(m_widget->windowFlags() | Qt::Tool);
312  this->windowFlagsChanged();
313  m_originalWindowMode = WindowTool;
314  }
315 
316  bool CEnableForFramelessWindow::isToolWindow() const { return (m_widget->windowFlags() & Qt::Tool) == Qt::Tool; }
317 
319  {
320  switch (mode)
321  {
322  case WindowFrameless: return (Qt::Window | Qt::FramelessWindowHint);
323  case WindowTool:
324  // tool window and minimized not supported on Windows
325  // tool window always with close button on Windows
326  return (Qt::Tool | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint);
327  case WindowNormal:
328  default:
329  return (Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint |
330  Qt::WindowCloseButtonHint);
331  }
332  }
333 } // namespace swift::gui
QWidget * getWidget() const
Corresponding QMainWindow.
bool handleMousePressEvent(QMouseEvent *event)
Mouse press, required for frameless window.
void setMode(WindowMode mode)
Window mode.
void addFramelessSizeGripToStatusBar(QStatusBar *statusBar)
Resize grip handle.
static Qt::WindowFlags modeToWindowFlags(WindowMode mode)
Translate mode.
void showMinimizedModeChecked()
Check mode and then show minimized.
static WindowMode stringToWindowMode(const QString &s)
String to window mode.
virtual void windowFlagsChanged()
Can be used as notification if window mode changes.
void hideFramelessSizeGripInStatusBar()
Resize grip handle.
virtual void setFrameless(bool frameless)
Framless.
static const QString & windowModeToString(WindowMode m)
String to window mode.
QPoint m_framelessDragPosition
position, if moving is handled with frameless window
bool m_isMainApplicationWindow
is this the main application window (only 1)?
void setWindowAttributes(WindowMode mode)
Attributes.
bool handleChangeEvent(QEvent *event)
Mouse window change event.
WindowMode m_originalWindowMode
mode when initialized
QByteArray m_framelessPropertyName
property name for frameless widgets
void toolToNormalWindow()
Remove tool and add desktop window.
QHBoxLayout * addFramelessCloseButton(QMenuBar *menuBar)
Close button for frameless windows.
void showNormalModeChecked()
Check mode and then show normal.
bool handleMouseMoveEvent(QMouseEvent *event)
Mouse moving, required for frameless window.
QPushButton * m_framelessCloseButton
close button
QSizeGrip * m_framelessSizeGrip
size grip object
QSize m_moveSize
size when moved (in frameless window)
void alwaysOnTop(bool onTop)
Always on top?
void normalToToolWindow()
Remove desktop and add tool window.
QWidget * m_widget
corresponding window or dock widget
void setDynamicProperties(bool frameless)
Set dynamic properties such as frameless.
static bool isTopLevelWindow(QWidget *widget)
Is top level window?
Definition: guiutility.cpp:666
GUI related classes.
Free functions in swift::misc.
SWIFT_MISC_EXPORT const QString & boolToTrueFalse(bool v)
Bool to true/false.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30