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