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