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
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  {
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; }
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  {
153 
154  // causing a BLACK background
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));
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(); }
184  }
185 
187  {
188  if (m_windowMode == CEnableForFramelessWindow::WindowTool) { this->normalToToolWindow(); }
190  }
191 
193  {
194  Q_ASSERT(m_widget);
195  if (m_windowMode == WindowFrameless && event->button() == Qt::LeftButton)
196  {
197  m_framelessDragPosition = event->globalPosition().toPoint() - 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 
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  {
267  m_framelessSizeGrip->setObjectName("sg_FramelessSizeGrip");
269  }
270  else { m_framelessSizeGrip->show(); }
271  statusBar->repaint();
272  }
273 
275  {
276  if (!m_framelessSizeGrip) { return; }
278  }
279 
281  {
282  Q_ASSERT(isFrameless());
283  Q_ASSERT(menuBar);
284  Q_ASSERT(m_widget);
285 
287  {
289  m_framelessCloseButton->setObjectName("pb_FramelessCloseButton");
290  m_framelessCloseButton->setIcon(CIcons::close16());
293  }
294 
295  QHBoxLayout *menuBarLayout = new QHBoxLayout;
296  menuBarLayout->setObjectName("hl_MenuBar");
297  menuBarLayout->addWidget(menuBar, 0, Qt::AlignTop | Qt::AlignLeft);
299  return menuBarLayout;
300  }
301 
303  {
305  this->windowFlagsChanged();
306  m_originalWindowMode = WindowNormal;
307  }
308 
310  {
312  this->windowFlagsChanged();
313  m_originalWindowMode = WindowTool;
314  }
315 
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
327  case WindowNormal:
328  default:
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:664
GUI related classes.
Free functions in swift::misc.
SWIFT_MISC_EXPORT const QString & boolToTrueFalse(bool v)
Bool to true/false.
void clicked(bool checked)
void setIcon(const QIcon &icon)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
const char * constData() const const
bool isEmpty() const const
WindowStateChange
QEvent::Type type() const const
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void setObjectName(QAnyStringView name)
bool setProperty(const char *name, QVariant &&value)
bool isNull() const const
QPoint toPoint() const const
int height() const const
void setX(int x)
void setY(int y)
int width() const const
int x() const const
int y() const const
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF globalPosition() const const
void addPermanentWidget(QWidget *widget, int stretch)
QString toLower() const const
QString trimmed() const const
AlignTop
QueuedConnection
LeftButton
WA_NativeWindow
WindowActive
typedef WindowFlags
bool close()
void hide()
bool isMinimized() const const
void move(const QPoint &)
void repaint()
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setWindowState(Qt::WindowStates windowState)
void show()
void showMinimized()
void setWindowFlags(Qt::WindowFlags type)