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