swift
navigatordialog.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2016 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
5 
6 #include <QAction>
7 #include <QEvent>
8 #include <QFrame>
9 #include <QGridLayout>
10 #include <QGuiApplication>
11 #include <QIcon>
12 #include <QList>
13 #include <QMainWindow>
14 #include <QMenu>
15 #include <QMouseEvent>
16 #include <QPoint>
17 #include <QSize>
18 #include <QString>
19 #include <QStringBuilder>
20 #include <QStyle>
21 #include <QToolButton>
22 #include <QVariant>
23 #include <Qt>
24 #include <QtGlobal>
25 
26 #include "ui_navigatordialog.h"
27 
29 #include "gui/guiapplication.h"
30 #include "gui/guiutility.h"
31 #include "gui/stylesheetutility.h"
32 #include "misc/icons.h"
33 #include "misc/logmessage.h"
34 
35 using namespace swift::gui;
36 using namespace swift::gui::settings;
37 using namespace swift::misc;
38 
39 namespace swift::gui::components
40 {
41  // If the dialog is a normal window, it stays open when the parent is minimized
42  // (and the parent is null for the dialog). If the dialog is a tool winow it is always
43  // minimized, regardless of dialog`s parent
44  CNavigatorDialog::CNavigatorDialog(QWidget *parent)
45  : QDialog(parent, modeToWindowFlags(CEnableForFramelessWindow::WindowTool)),
46  CEnableForFramelessWindow(CEnableForFramelessWindow::WindowTool, false, "navigatorFrameless", this),
47  ui(new Ui::CNavigatorDialog)
48  {
49  ui->setupUi(this);
50 
51  // context menu
53  m_input = new CMarginsInput(this);
54  m_input->setMaximumWidth(150);
55  m_marginMenuAction = new QWidgetAction(this);
56  m_marginMenuAction->setDefaultWidget(m_input);
57 
58  // Quit on window hack
59  m_originalQuitOnLastWindow = QGuiApplication::quitOnLastWindowClosed();
60 
61  // timer
62  m_watchdog.setObjectName(this->objectName() + ":m_timer");
63  connect(&m_watchdog, &QTimer::timeout, this, &CNavigatorDialog::onWatchdog);
64 
66  connect(m_input, &CMarginsInput::changedMargins, this, &CNavigatorDialog::menuChangeMargins);
67  connect(this, &CNavigatorDialog::customContextMenuRequested, this, &CNavigatorDialog::showContextMenu);
68  if (sGui)
69  {
70  connect(sGui, &CGuiApplication::styleSheetsChanged, this, &CNavigatorDialog::onStyleSheetsChanged,
72  }
73  this->onStyleSheetsChanged();
74  }
75 
77 
79  {
80  if (m_firstBuild)
81  {
82  m_firstBuild = false;
83  this->insertOwnActions();
84  }
85 
86  this->onStyleSheetsChanged();
87 
88  // remove old layout
89  CGuiUtility::deleteLayout(ui->fr_NavigatorDialogInner->layout(), false);
90 
91  // new layout
92  auto *gridLayout = new QGridLayout(ui->fr_NavigatorDialogInner);
93  gridLayout->setObjectName("gl_CNavigatorDialog");
94  gridLayout->setSpacing(0);
95  gridLayout->setContentsMargins(0, 0, 0, 0);
96  ui->fr_NavigatorDialogInner->setLayout(gridLayout);
97  int r = 0;
98  int c = 0;
99 
100  // remark: the actions will be set from the main UI
101  for (QAction *action : this->actions())
102  {
103  if (!action) { continue; }
104  auto *tb = new QToolButton(ui->fr_NavigatorDialogInner);
105  tb->setDefaultAction(action);
106  tb->setObjectName(this->objectName() % u':' % action->objectName());
107  if (!action->text().isEmpty()) { tb->setToolTip(action->text()); }
108  gridLayout->addWidget(tb, r, c++);
109  tb->show();
110  if (c < columns) { continue; }
111  c = 0;
112  r++;
113  }
114  m_currentColumns = gridLayout->columnCount();
115  this->adjustNavigatorSize(gridLayout);
116  this->focusWidget();
117  }
118 
120  {
121  // workaround to avoid "closing issue with navigator",
122  // https://discordapp.com/channels/539048679160676382/567139633964646411/620776182027386880
123  if (m_mainWindow)
124  {
125  QGuiApplication::setQuitOnLastWindowClosed(m_originalQuitOnLastWindow);
126  m_mainWindow->show();
127  }
128 
129  this->hide();
130  m_watchdog.stop();
131  emit this->navigatorClosed();
132  }
133 
135 
137  {
138  this->setVisible(visible);
140  this->show();
142  visible ? false : m_originalQuitOnLastWindow); // avoid issues with a dialog closing everything
143 
144  if (visible) { m_watchdog.start(4000); }
145  else { m_watchdog.stop(); }
146  }
147 
149  {
150  const bool visible = !this->isVisible();
151  this->showNavigator(visible);
152  }
153 
155  {
156  const CNavigatorSettings s = m_settings.get();
157  this->setContentsMargins(s.getMargins());
158  if (this->isFrameless() != s.isFramless()) { this->toggleFrameless(); }
159  this->buildNavigator(s.getColumns());
160  const QByteArray geo(s.getGeometry());
161  this->restoreGeometry(geo);
162  }
163 
165  {
166  CNavigatorSettings s = m_settings.get();
167  s.setFrameless(this->isFrameless());
168  s.setMargins(this->contentsMargins());
169  s.setGeometry(this->saveGeometry());
170  s.setColumns(m_currentColumns);
171  const CStatusMessage m = m_settings.setAndSave(s);
172  if (!m.isSuccess()) { CLogMessage::preformatted(m); }
173  }
174 
175  void CNavigatorDialog::onStyleSheetsChanged()
176  {
177  if (!sGui || sGui->isShuttingDown()) { return; }
179  const QString qss(sGui->getStyleSheetUtility().style(fn));
180  this->setStyleSheet("");
181  this->setStyleSheet(qss);
182  this->adjustNavigatorSize();
183  this->repaint();
184  }
185 
187  {
188  if (handleMouseMoveEvent(event)) { return; }
189 
190  // frameless has moving already, but here we also do it dor dialog
191  if (!this->isFrameless())
192  {
193  if (event->buttons() & Qt::LeftButton)
194  {
195  const QPoint pos = this->mapToParent(event->pos() - m_framelessDragPosition);
196  this->move(pos);
197  event->accept();
198  return;
199  }
200  }
202  }
203 
205  {
206  if (handleMousePressEvent(event)) { return; }
207 
208  // frameless has moving already, but here we also do it dor dialog
209  if (!this->isFrameless())
210  {
211  if (event->buttons() & Qt::LeftButton)
212  {
213  m_framelessDragPosition = event->pos();
214  event->accept();
215  return;
216  }
217  }
219  }
220 
222  {
225  }
226 
228  {
229  const QEvent::Type t = evt->type();
230  if (t == QEvent::WindowStateChange)
231  {
232  evt->ignore();
233  hide();
234  }
235  else { QDialog::changeEvent(evt); }
236  }
237 
239  {
240  if (m_firstBuild) { return; }
241  this->buildNavigator(m_currentColumns);
242  }
243 
245  {
247  if (s) { return; }
249  }
250 
252  {
253  // event called when mouse is over, acts as auto-focus
254  activateWindow();
256  }
257 
258  void CNavigatorDialog::showContextMenu(const QPoint &pos)
259  {
260  const QPoint globalPos = this->mapToGlobal(pos);
261  QScopedPointer<QMenu> contextMenu(new QMenu(this));
262  this->addToContextMenu(contextMenu.data());
263  QAction *selectedItem = contextMenu.data()->exec(globalPos);
264  Q_UNUSED(selectedItem);
265  }
266 
267  void CNavigatorDialog::changeLayout()
268  {
269  auto *a = qobject_cast<QAction *>(QObject::sender());
270  if (!a) { return; }
271  QString v(a->data().toString());
272  if (v == "1c") { buildNavigator(1); }
273  else if (v == "2c") { buildNavigator(2); }
274  else if (v == "1r") { buildNavigator(columnsForRows(1)); }
275  else if (v == "2r") { buildNavigator(columnsForRows(2)); }
276  }
277 
278  void CNavigatorDialog::menuChangeMargins(const QMargins &margins)
279  {
280  this->setContentsMargins(margins);
281  this->adjustNavigatorSize();
282  }
283 
284  void CNavigatorDialog::dummyFunction()
285  {
286  // void
287  }
288 
289  void CNavigatorDialog::onSettingsChanged()
290  {
291  // void
292  }
293 
294  void CNavigatorDialog::insertOwnActions()
295  {
296  // add some space for frameless navigators where I can move the navigator
297  QAction *a = nullptr; // new QAction(CIcons::empty16(), "move navigator here", this);
298  bool c = false;
299 
300  // save
301  a = new QAction(CIcons::save16(), "Save state", this);
303  Q_ASSERT(c);
304  this->addAction(a);
305 
306  // close
307  const QIcon i(CIcons::changeIconBackgroundColor(this->style()->standardIcon(QStyle::SP_TitleBarCloseButton),
308  Qt::white, QSize(16, 16)));
309  a = new QAction(i, "Close", this);
311  Q_ASSERT(c);
312  this->addAction(a);
313  }
314 
315  int CNavigatorDialog::columnsForRows(int rows)
316  {
317  Q_ASSERT_X(rows >= 0, Q_FUNC_INFO, "no rows");
318  int items = this->actions().size();
319  int c = items / rows;
320  return (c * rows) < items ? c + 1 : c;
321  }
322 
323  QGridLayout *CNavigatorDialog::myGridLayout() const { return qobject_cast<QGridLayout *>(this->layout()); }
324 
325  void CNavigatorDialog::adjustNavigatorSize(QGridLayout *layout)
326  {
327  QGridLayout *gridLayout = layout ? layout : this->myGridLayout();
328  Q_ASSERT_X(gridLayout, Q_FUNC_INFO, "Missing layout");
329 
330  int w = 16 * gridLayout->columnCount();
331  int h = 16 * gridLayout->rowCount();
332 
333  // margins
334  const QMargins margins = gridLayout->contentsMargins() + this->contentsMargins();
335  h = h + margins.top() + margins.bottom();
336  w = w + margins.left() + margins.right();
337 
338  // adjust
339  const QSize min(w + 2, h + 2);
340  ui->fr_NavigatorDialogInner->setMinimumSize(min);
341  this->setMinimumSize(min);
342  this->adjustSize();
343  }
344 
345  void CNavigatorDialog::onWatchdog()
346  {
347  // if (!this->isVisible()) { return; }
348  CGuiUtility::stayOnTop(true, this);
349  this->show();
350  }
351 
352  void CNavigatorDialog::addToContextMenu(QMenu *contextMenu) const
353  {
354  QAction *a = contextMenu->addAction(CIcons::resize16(), "1 row", this, &CNavigatorDialog::changeLayout);
355  a->setData("1r");
356  a = contextMenu->addAction(CIcons::resize16(), "2 rows", this, &CNavigatorDialog::changeLayout);
357  a->setData("2r");
358  a = contextMenu->addAction(CIcons::resize16(), "1 column", this, &CNavigatorDialog::changeLayout);
359  a->setData("1c");
360  a = contextMenu->addAction(CIcons::resize16(), "2 columns", this, &CNavigatorDialog::changeLayout);
361  a->setData("2c");
362  const QString frameLessActionText = this->isFrameless() ? "Normal window" : "Frameless";
363  contextMenu->addAction(CIcons::tableSheet16(), frameLessActionText, this, &CNavigatorDialog::toggleFrameless);
364  contextMenu->addAction("Adjust margins", this, &CNavigatorDialog::dummyFunction);
365  contextMenu->addAction(m_marginMenuAction);
366  contextMenu->addSeparator();
367  contextMenu->addAction(CIcons::load16(), "Restore from settings", this, &CNavigatorDialog::restoreFromSettings);
368  contextMenu->addAction(CIcons::save16(), "Save state", this, &CNavigatorDialog::saveToSettings);
369  }
370 } // namespace swift::gui::components
bool isShuttingDown() const
Is application shutting down?
Main window which can be frameless.
bool handleMousePressEvent(QMouseEvent *event)
Mouse press, required for frameless window.
virtual void setFrameless(bool frameless)
Framless.
QPoint m_framelessDragPosition
position, if moving is handled with frameless window
bool handleMouseMoveEvent(QMouseEvent *event)
Mouse moving, required for frameless window.
const CStyleSheetUtility & getStyleSheetUtility() const
Style sheet handling.
void styleSheetsChanged()
Style sheet changed.
static bool stayOnTop(bool onTop, QWidget *widget)
Window flags / stay on top.
Definition: guiutility.cpp:592
static void deleteLayout(QLayout *layout, bool deleteWidgets)
Delete hierarchy of layouts.
Definition: guiutility.cpp:532
static bool useStyleSheetInDerivedWidget(QWidget *derivedWidget, QStyle::PrimitiveElement element=QStyle::PE_Widget)
Use style sheets in derived widgets.
QString style(const QString &fileName) const
Style for given file name.
static const QString & fileNameNavigator()
File name navigator.qss.
Widget alows to enter margins.
Definition: marginsinput.h:24
void changedMargins(const QMargins &margins)
Margins changed.
void navigatorClosed()
Navigator closed.
void showNavigator(bool visible)
Visibility visibility.
void buildNavigator(int columns)
Navigator.
void restoreFromSettings()
Restore from settings.
void windowFlagsChanged()
Can be used as notification if window mode changes.
void toggleNavigatorVisibility()
Toggle visibility.
void toggleFrameless()
Toggle frameless mode.
void reject()
Called when dialog is closed.
void setColumns(int columns)
Set columns.
void setGeometry(const QByteArray &ba)
Set geometry.
void setFrameless(bool frameless)
Frameless.
int getColumns() const
Number pf columns.
void setMargins(const QMargins &margins)
Set margins.
CStatusMessage setAndSave(const T &value, qint64 timestamp=0)
Write and save in the same step. Must be called from the thread in which the owner lives.
Definition: valuecache.h:417
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
Streamable status message, e.g.
bool isSuccess() const
Operation considered successful.
SWIFT_GUI_EXPORT swift::gui::CGuiApplication * sGui
Single instance of GUI application object.
High level reusable GUI components.
Definition: aboutdialog.cpp:14
GUI related classes.
Free functions in swift::misc.
QVariant data() const const
void setData(const QVariant &data)
void triggered(bool checked)
virtual void setVisible(bool visible) override
void ignore()
QEvent::Type type() const const
int columnCount() const const
int rowCount() const const
QMargins contentsMargins() const const
qsizetype size() const const
int bottom() const const
int left() const const
int right() const const
int top() const const
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSeparator()
QPoint pos() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * sender() const const
void setObjectName(QAnyStringView name)
Qt::MouseButtons buttons() const const
SP_TitleBarCloseButton
QueuedConnection
CustomContextMenu
LeftButton
void start()
void stop()
void timeout()
QList< QAction * > actions() const const
void activateWindow()
QAction * addAction(const QIcon &icon, const QString &text)
void adjustSize()
virtual void changeEvent(QEvent *event)
bool close()
QMargins contentsMargins() const const
void setContextMenuPolicy(Qt::ContextMenuPolicy policy)
void customContextMenuRequested(const QPoint &pos)
virtual void enterEvent(QEnterEvent *event)
virtual bool event(QEvent *event) override
QWidget * focusWidget() const const
void hide()
QLayout * layout() const const
QPoint mapToGlobal(const QPoint &pos) const const
QPoint mapToParent(const QPoint &pos) const const
void setMaximumWidth(int maxw)
void setMinimumSize(const QSize &)
virtual void mouseMoveEvent(QMouseEvent *event)
virtual void mousePressEvent(QMouseEvent *event)
virtual void mouseReleaseEvent(QMouseEvent *event)
virtual void paintEvent(QPaintEvent *event)
void repaint()
bool restoreGeometry(const QByteArray &geometry)
QByteArray saveGeometry() const const
void setContentsMargins(const QMargins &margins)
void show()
QStyle * style() const const
void setStyleSheet(const QString &styleSheet)
void setDefaultWidget(QWidget *widget)