QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsdockablewidgethelper.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsdockablewidgethelper.cpp
3 --------------------------------------
4 Date : January 2022
5 Copyright : (C) 2022 by Belgacem Nedjima
6 Email : belgacem dot nedjima at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
18#include "qgsdockwidget.h"
19#include "qgsapplication.h"
20#include "qgssettings.h"
21
22#include <QLayout>
23#include <QAction>
24#include <QUuid>
25
27
28
29std::function< void( Qt::DockWidgetArea, QDockWidget *, const QStringList &, bool ) > QgsDockableWidgetHelper::sAddTabifiedDockWidgetFunction = []( Qt::DockWidgetArea, QDockWidget *, const QStringList &, bool ) {};
30std::function< QString( ) > QgsDockableWidgetHelper::sAppStylesheetFunction = [] { return QString(); };
31QMainWindow *QgsDockableWidgetHelper::sOwnerWindow = nullptr;
32
33QgsDockableWidgetHelper::QgsDockableWidgetHelper( bool isDocked, const QString &windowTitle, QWidget *widget, QMainWindow *ownerWindow,
34 Qt::DockWidgetArea defaultDockArea,
35 const QStringList &tabifyWith,
36 bool raiseTab, const QString &windowGeometrySettingsKey, bool usePersistentWidget )
37 : QObject( nullptr )
38 , mWidget( widget )
39 , mDialogGeometry( 0, 0, 0, 0 )
40 , mIsDockFloating( defaultDockArea == Qt::DockWidgetArea::NoDockWidgetArea )
41 , mDockArea( defaultDockArea == Qt::DockWidgetArea::NoDockWidgetArea ? Qt::DockWidgetArea::RightDockWidgetArea : defaultDockArea )
42 , mWindowTitle( windowTitle )
43 , mOwnerWindow( ownerWindow )
44 , mTabifyWith( tabifyWith )
45 , mRaiseTab( raiseTab )
46 , mWindowGeometrySettingsKey( windowGeometrySettingsKey )
47 , mUuid( QUuid::createUuid().toString() )
48 , mUsePersistentWidget( usePersistentWidget )
49{
50 toggleDockMode( isDocked );
51}
52
53QgsDockableWidgetHelper::~QgsDockableWidgetHelper()
54{
55 if ( mDock )
56 {
57 mDockGeometry = mDock->geometry();
58 mIsDockFloating = mDock->isFloating();
59 if ( mOwnerWindow )
60 mDockArea = mOwnerWindow->dockWidgetArea( mDock );
61
62 mDock->setWidget( nullptr );
63
64 if ( mOwnerWindow )
65 mOwnerWindow->removeDockWidget( mDock );
66 mDock->deleteLater();
67 mDock = nullptr;
68 }
69
70 if ( mDialog )
71 {
72 mDialogGeometry = mDialog->geometry();
73
74 if ( !mWindowGeometrySettingsKey.isEmpty() )
75 {
76 QgsSettings().setValue( mWindowGeometrySettingsKey, mDialog->saveGeometry() );
77 }
78
79 mDialog->layout()->removeWidget( mWidget );
80 mDialog->deleteLater();
81 mDialog = nullptr;
82 }
83}
84
85void QgsDockableWidgetHelper::writeXml( QDomElement &viewDom )
86{
87 viewDom.setAttribute( QStringLiteral( "isDocked" ), mIsDocked );
88
89 if ( mDock )
90 {
91 mDockGeometry = mDock->geometry();
92 mIsDockFloating = mDock->isFloating();
93 if ( mOwnerWindow )
94 mDockArea = mOwnerWindow->dockWidgetArea( mDock );
95 }
96
97 viewDom.setAttribute( QStringLiteral( "x" ), mDockGeometry.x() );
98 viewDom.setAttribute( QStringLiteral( "y" ), mDockGeometry.y() );
99 viewDom.setAttribute( QStringLiteral( "width" ), mDockGeometry.width() );
100 viewDom.setAttribute( QStringLiteral( "height" ), mDockGeometry.height() );
101 viewDom.setAttribute( QStringLiteral( "floating" ), mIsDockFloating );
102 viewDom.setAttribute( QStringLiteral( "area" ), mDockArea );
103 viewDom.setAttribute( QStringLiteral( "uuid" ), mUuid );
104
105 if ( mDock )
106 {
107 const QList<QDockWidget * > tabSiblings = mOwnerWindow ? mOwnerWindow->tabifiedDockWidgets( mDock ) : QList<QDockWidget * >();
108 QDomElement tabSiblingsElement = viewDom.ownerDocument().createElement( QStringLiteral( "tab_siblings" ) );
109 for ( QDockWidget *dock : tabSiblings )
110 {
111 QDomElement siblingElement = viewDom.ownerDocument().createElement( QStringLiteral( "sibling" ) );
112 siblingElement.setAttribute( QStringLiteral( "uuid" ), dock->property( "dock_uuid" ).toString() );
113 siblingElement.setAttribute( QStringLiteral( "object_name" ), dock->objectName() );
114 tabSiblingsElement.appendChild( siblingElement );
115 }
116 viewDom.appendChild( tabSiblingsElement );
117 }
118
119 if ( mDialog )
120 mDialogGeometry = mDialog->geometry();
121
122 viewDom.setAttribute( QStringLiteral( "d_x" ), mDialogGeometry.x() );
123 viewDom.setAttribute( QStringLiteral( "d_y" ), mDialogGeometry.y() );
124 viewDom.setAttribute( QStringLiteral( "d_width" ), mDialogGeometry.width() );
125 viewDom.setAttribute( QStringLiteral( "d_height" ), mDialogGeometry.height() );
126}
127
128void QgsDockableWidgetHelper::readXml( const QDomElement &viewDom )
129{
130 mUuid = viewDom.attribute( QStringLiteral( "uuid" ), mUuid );
131
132 {
133 int x = viewDom.attribute( QStringLiteral( "d_x" ), QStringLiteral( "0" ) ).toInt();
134 int y = viewDom.attribute( QStringLiteral( "d_x" ), QStringLiteral( "0" ) ).toInt();
135 int w = viewDom.attribute( QStringLiteral( "d_width" ), QStringLiteral( "200" ) ).toInt();
136 int h = viewDom.attribute( QStringLiteral( "d_height" ), QStringLiteral( "200" ) ).toInt();
137 mDialogGeometry = QRect( x, y, w, h );
138 if ( mDialog )
139 mDialog->setGeometry( mDialogGeometry );
140 }
141
142 {
143 int x = viewDom.attribute( QStringLiteral( "x" ), QStringLiteral( "0" ) ).toInt();
144 int y = viewDom.attribute( QStringLiteral( "y" ), QStringLiteral( "0" ) ).toInt();
145 int w = viewDom.attribute( QStringLiteral( "width" ), QStringLiteral( "200" ) ).toInt();
146 int h = viewDom.attribute( QStringLiteral( "height" ), QStringLiteral( "200" ) ).toInt();
147 mDockGeometry = QRect( x, y, w, h );
148 mIsDockFloating = viewDom.attribute( QStringLiteral( "floating" ), QStringLiteral( "0" ) ).toInt();
149 mDockArea = static_cast< Qt::DockWidgetArea >( viewDom.attribute( QStringLiteral( "area" ), QString::number( Qt::RightDockWidgetArea ) ).toInt() );
150
151 if ( mDockArea == Qt::DockWidgetArea::NoDockWidgetArea && !mIsDockFloating )
152 {
153 mDockArea = Qt::RightDockWidgetArea;
154 }
155
156 QStringList tabSiblings;
157 const QDomElement tabSiblingsElement = viewDom.firstChildElement( QStringLiteral( "tab_siblings" ) );
158 const QDomNodeList tabSiblingNodes = tabSiblingsElement.childNodes();
159 for ( int i = 0; i < tabSiblingNodes.size(); ++i )
160 {
161 const QDomElement tabSiblingElement = tabSiblingNodes.at( i ).toElement();
162 // prefer uuid if set, as it's always unique
163 QString tabId = tabSiblingElement.attribute( QStringLiteral( "uuid" ) );
164 if ( tabId.isEmpty() )
165 tabId = tabSiblingElement.attribute( QStringLiteral( "object_name" ) );
166 if ( !tabId.isEmpty() )
167 tabSiblings.append( tabId );
168 }
169
170 setupDockWidget( tabSiblings );
171 }
172
173 if ( mDock )
174 {
175 mDock->setProperty( "dock_uuid", mUuid );
176 }
177}
178
179void QgsDockableWidgetHelper::setWidget( QWidget *widget )
180{
181 // Make sure the old mWidget is not stuck as a child of mDialog or mDock
182 if ( mWidget && mOwnerWindow )
183 {
184 mWidget->setParent( mOwnerWindow );
185 }
186 if ( mDialog )
187 {
188 mDialog->layout()->removeWidget( mWidget );
189 }
190 if ( mDock )
191 {
192 mDock->setWidget( nullptr );
193 }
194
195 mWidget = widget;
196 toggleDockMode( mIsDocked );
197}
198
199QgsDockWidget *QgsDockableWidgetHelper::dockWidget()
200{
201 return mDock.data();
202}
203
204QDialog *QgsDockableWidgetHelper::dialog()
205{
206 return mDialog.data();
207}
208
209void QgsDockableWidgetHelper::toggleDockMode( bool docked )
210{
211 // Make sure the old mWidget is not stuck as a child of mDialog or mDock
212 if ( mWidget && mOwnerWindow )
213 {
214 mWidget->setParent( mOwnerWindow );
215 }
216
217 // Remove both the dialog and the dock widget first
218 if ( mDock )
219 {
220 mDockGeometry = mDock->geometry();
221 mIsDockFloating = mDock->isFloating();
222 if ( mOwnerWindow )
223 mDockArea = mOwnerWindow->dockWidgetArea( mDock );
224
225 mDock->setWidget( nullptr );
226 if ( mOwnerWindow )
227 mOwnerWindow->removeDockWidget( mDock );
228 delete mDock;
229 mDock = nullptr;
230 }
231
232 if ( mDialog )
233 {
234 // going from window -> dock, so save current window geometry
235 if ( !mWindowGeometrySettingsKey.isEmpty() )
236 QgsSettings().setValue( mWindowGeometrySettingsKey, mDialog->saveGeometry() );
237
238 mDialogGeometry = mDialog->geometry();
239
240 if ( mWidget )
241 mDialog->layout()->removeWidget( mWidget );
242
243 delete mDialog;
244 mDialog = nullptr;
245 }
246
247 mIsDocked = docked;
248
249 // If there is no widget set, do not create a dock or a dialog
250 if ( !mWidget )
251 return;
252
253 if ( docked )
254 {
255 // going from window -> dock
256 mDock = new QgsDockWidget( mOwnerWindow );
257 mDock->setWindowTitle( mWindowTitle );
258 mDock->setWidget( mWidget );
259 mDock->setObjectName( mObjectName );
260 mDock->setProperty( "dock_uuid", mUuid );
261 setupDockWidget();
262
263 connect( mDock, &QgsDockWidget::closed, this, [ = ]()
264 {
265 mDockGeometry = mDock->geometry();
266 mIsDockFloating = mDock->isFloating();
267 if ( mOwnerWindow )
268 mDockArea = mOwnerWindow->dockWidgetArea( mDock );
269 emit closed();
270 } );
271
272 if ( mUsePersistentWidget )
273 mDock->installEventFilter( this );
274
275 connect( mDock, &QgsDockWidget::visibilityChanged, this, &QgsDockableWidgetHelper::visibilityChanged );
276 mDock->setUserVisible( true );
277 emit visibilityChanged( true );
278 }
279 else
280 {
281 // going from dock -> window
282 // note -- we explicitly DO NOT set the parent for the dialog, as we want these treated as
283 // proper top level windows and have their own taskbar entries. See https://github.com/qgis/QGIS/issues/49286
284 if ( mUsePersistentWidget )
285 mDialog = new QgsNonRejectableDialog( nullptr, Qt::Window );
286 else
287 mDialog = new QDialog( nullptr, Qt::Window );
288 mDialog->setStyleSheet( sAppStylesheetFunction() );
289
290 mDialog->setWindowTitle( mWindowTitle );
291 mDialog->setObjectName( mObjectName );
292
293 if ( mUsePersistentWidget )
294 mDialog->installEventFilter( this );
295
296 QVBoxLayout *vl = new QVBoxLayout();
297 vl->setContentsMargins( 0, 0, 0, 0 );
298 vl->addWidget( mWidget );
299
300 if ( !mWindowGeometrySettingsKey.isEmpty() )
301 {
302 QgsSettings settings;
303 mDialog->restoreGeometry( settings.value( mWindowGeometrySettingsKey ).toByteArray() );
304 }
305 else
306 {
307 if ( !mDockGeometry.isEmpty() )
308 mDialog->setGeometry( mDockGeometry );
309 else if ( !mDialogGeometry.isEmpty() )
310 mDialog->setGeometry( mDialogGeometry );
311 }
312 mDialog->setLayout( vl );
313 mDialog->raise();
314 mDialog->show();
315
316 connect( mDialog, &QDialog::finished, this, [ = ]()
317 {
318 mDialogGeometry = mDialog->geometry();
319 emit closed();
320 emit visibilityChanged( false );
321 } );
322
323 emit visibilityChanged( true );
324 }
325 emit dockModeToggled( docked );
326}
327
328void QgsDockableWidgetHelper::setUserVisible( bool visible )
329{
330 if ( mDialog )
331 {
332 if ( visible )
333 {
334 mDialog->show();
335 mDialog->raise();
336 mDialog->setWindowState( mDialog->windowState() & ~Qt::WindowMinimized );
337 mDialog->activateWindow();
338 }
339 else
340 {
341 mDialog->hide();
342 }
343 }
344 if ( mDock )
345 {
346 mDock->setUserVisible( visible );
347 }
348}
349
350void QgsDockableWidgetHelper::setWindowTitle( const QString &title )
351{
352 mWindowTitle = title;
353 if ( mDialog )
354 {
355 mDialog->setWindowTitle( title );
356 }
357 if ( mDock )
358 {
359 mDock->setWindowTitle( title );
360 }
361}
362
363void QgsDockableWidgetHelper::setDockObjectName( const QString &name )
364{
365 mObjectName = name;
366 if ( mDialog )
367 {
368 mDialog->setObjectName( name );
369 }
370 if ( mDock )
371 {
372 mDock->setObjectName( name );
373 }
374}
375
376QString QgsDockableWidgetHelper::dockObjectName() const { return mObjectName; }
377
378bool QgsDockableWidgetHelper::isUserVisible() const
379{
380 if ( mDialog )
381 {
382 return mDialog->isVisible();
383 }
384 if ( mDock )
385 {
386 return mDock->isUserVisible();
387 }
388 return false;
389}
390
391void QgsDockableWidgetHelper::setupDockWidget( const QStringList &tabSiblings )
392{
393 if ( !mDock )
394 return;
395
396 mDock->setFloating( mIsDockFloating );
397 if ( mDockGeometry.isEmpty() && mOwnerWindow )
398 {
399 const QFontMetrics fm( mOwnerWindow->font() );
400 const int initialDockSize = fm.horizontalAdvance( '0' ) * 75;
401 mDockGeometry = QRect( static_cast< int >( mOwnerWindow->rect().width() * 0.75 ),
402 static_cast< int >( mOwnerWindow->rect().height() * 0.5 ),
403 initialDockSize, initialDockSize );
404 }
405 if ( !tabSiblings.isEmpty() )
406 {
407 sAddTabifiedDockWidgetFunction( mDockArea, mDock, tabSiblings, false );
408 }
409 else if ( mRaiseTab )
410 {
411 sAddTabifiedDockWidgetFunction( mDockArea, mDock, mTabifyWith, mRaiseTab );
412 }
413 else if ( mOwnerWindow )
414 {
415 mOwnerWindow->addDockWidget( mDockArea, mDock );
416 }
417
418 // can only resize properly and set the dock geometry after pending events have been processed,
419 // so queue the geometry setting on the end of the event loop
420 QMetaObject::invokeMethod( mDock, [this] { mDock->setGeometry( mDockGeometry ); }, Qt::QueuedConnection );
421}
422
423QToolButton *QgsDockableWidgetHelper::createDockUndockToolButton()
424{
425 QToolButton *toggleButton = new QToolButton;
426 toggleButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mDockify.svg" ) ) );
427 toggleButton->setCheckable( true );
428 toggleButton->setChecked( mIsDocked );
429 toggleButton->setEnabled( true );
430
431 connect( toggleButton, &QToolButton::toggled, this, &QgsDockableWidgetHelper::toggleDockMode );
432 return toggleButton;
433}
434
435QAction *QgsDockableWidgetHelper::createDockUndockAction( const QString &title, QWidget *parent )
436{
437 QAction *toggleAction = new QAction( title, parent );
438 toggleAction->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "mDockify.svg" ) ) );
439 toggleAction->setCheckable( true );
440 toggleAction->setChecked( mIsDocked );
441 toggleAction->setEnabled( true );
442
443 connect( toggleAction, &QAction::toggled, this, &QgsDockableWidgetHelper::toggleDockMode );
444 return toggleAction;
445}
446
447bool QgsDockableWidgetHelper::eventFilter( QObject *watched, QEvent *event )
448{
449 if ( watched == mDialog )
450 {
451 if ( event->type() == QEvent::Close )
452 {
453 event->ignore();
454 mDialog->hide();
455 emit visibilityChanged( false );
456 return true;
457 }
458 }
459 else if ( watched == mDock )
460 {
461 if ( event->type() == QEvent::Close )
462 {
463 event->ignore();
464 mDock->hide();
465 emit visibilityChanged( false );
466 return true;
467 }
468 }
469 return QObject::eventFilter( watched, event );
470}
471
472//
473// QgsNonRejectableDialog
474//
475
476QgsNonRejectableDialog::QgsNonRejectableDialog( QWidget *parent, Qt::WindowFlags f )
477 : QDialog( parent, f )
478{
479
480}
481
482void QgsNonRejectableDialog::reject()
483{
484 // swallow rejection -- we don't want this dialog to be closable via escape key
485}
486
487
489
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
QgsDockWidget subclass with more fine-grained control over how the widget is closed or opened.
Definition: qgsdockwidget.h:31
void closed()
Emitted when dock widget is closed.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.