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