1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "fancymainwindow.h"
27
28 #include "algorithm.h"
29 #include "porting.h"
30 #include "qtcassert.h"
31 #include "stringutils.h"
32
33 #include <QAbstractButton>
34 #include <QApplication>
35 #include <QContextMenuEvent>
36 #include <QDockWidget>
37 #include <QHBoxLayout>
38 #include <QLabel>
39 #include <QMenu>
40 #include <QPainter>
41 #include <QSettings>
42 #include <QStyle>
43 #include <QStyleOption>
44 #include <QTimer>
45 #include <QToolButton>
46
47 static const char AutoHideTitleBarsKey[] = "AutoHideTitleBars";
48 static const char ShowCentralWidgetKey[] = "ShowCentralWidget";
49 static const char StateKey[] = "State";
50
51 static const int settingsVersion = 2;
52 static const char dockWidgetActiveState[] = "DockWidgetActiveState";
53
54 namespace Utils {
55
56 class TitleBarWidget;
57
58 struct FancyMainWindowPrivate
59 {
60 FancyMainWindowPrivate(FancyMainWindow *parent);
61
62 FancyMainWindow *q;
63
64 bool m_handleDockVisibilityChanges;
65 QAction m_showCentralWidget;
66 QAction m_menuSeparator1;
67 QAction m_menuSeparator2;
68 QAction m_resetLayoutAction;
69 QAction m_autoHideTitleBars;
70 };
71
72 class DockWidget : public QDockWidget
73 {
74 public:
75 DockWidget(QWidget *inner, FancyMainWindow *parent, bool immutable = false);
76
77 bool eventFilter(QObject *, QEvent *event) override;
78 void enterEvent(EnterEvent *event) override;
79 void leaveEvent(QEvent *event) override;
80 void handleMouseTimeout();
81 void handleToplevelChanged(bool floating);
82
83 FancyMainWindow *q;
84
85 private:
86 QPoint m_startPos;
87 TitleBarWidget *m_titleBar;
88 QTimer m_timer;
89 bool m_immutable = false;
90 };
91
92 // Stolen from QDockWidgetTitleButton
93 class DockWidgetTitleButton : public QAbstractButton
94 {
95 public:
DockWidgetTitleButton(QWidget * parent)96 DockWidgetTitleButton(QWidget *parent)
97 : QAbstractButton(parent)
98 {
99 setFocusPolicy(Qt::NoFocus);
100 }
101
sizeHint() const102 QSize sizeHint() const override
103 {
104 ensurePolished();
105
106 int size = 2*style()->pixelMetric(QStyle::PM_DockWidgetTitleBarButtonMargin, nullptr, this);
107 if (!icon().isNull()) {
108 int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
109 QSize sz = icon().actualSize(QSize(iconSize, iconSize));
110 size += qMax(sz.width(), sz.height());
111 }
112
113 return QSize(size, size);
114 }
115
minimumSizeHint() const116 QSize minimumSizeHint() const override { return sizeHint(); }
117
enterEvent(EnterEvent * event)118 void enterEvent(EnterEvent *event) override
119 {
120 if (isEnabled())
121 update();
122 QAbstractButton::enterEvent(event);
123 }
124
leaveEvent(QEvent * event)125 void leaveEvent(QEvent *event) override
126 {
127 if (isEnabled())
128 update();
129 QAbstractButton::leaveEvent(event);
130 }
131
132 void paintEvent(QPaintEvent *event) override;
133 };
134
paintEvent(QPaintEvent *)135 void DockWidgetTitleButton::paintEvent(QPaintEvent *)
136 {
137 QPainter p(this);
138
139 QStyleOptionToolButton opt;
140 opt.initFrom(this);
141 opt.state |= QStyle::State_AutoRaise;
142 opt.icon = icon();
143 opt.subControls = {};
144 opt.activeSubControls = {};
145 opt.features = QStyleOptionToolButton::None;
146 opt.arrowType = Qt::NoArrow;
147 int size = style()->pixelMetric(QStyle::PM_SmallIconSize, nullptr, this);
148 opt.iconSize = QSize(size, size);
149 style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &p, this);
150 }
151
152 class TitleBarWidget : public QWidget
153 {
154 public:
TitleBarWidget(DockWidget * parent,const QStyleOptionDockWidget & opt)155 TitleBarWidget(DockWidget *parent, const QStyleOptionDockWidget &opt)
156 : QWidget(parent), q(parent), m_active(true)
157 {
158 m_titleLabel = new QLabel(this);
159
160 m_floatButton = new DockWidgetTitleButton(this);
161 m_floatButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarNormalButton, &opt, q));
162
163 m_closeButton = new DockWidgetTitleButton(this);
164 m_closeButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton, &opt, q));
165
166 #ifndef QT_NO_ACCESSIBILITY
167 m_floatButton->setAccessibleName(QDockWidget::tr("Float"));
168 m_floatButton->setAccessibleDescription(QDockWidget::tr("Undocks and re-attaches the dock widget"));
169 m_closeButton->setAccessibleName(QDockWidget::tr("Close"));
170 m_closeButton->setAccessibleDescription(QDockWidget::tr("Closes the dock widget"));
171 #endif
172
173 setActive(false);
174
175 const int minWidth = 10;
176 const int maxWidth = 10000;
177 const int inactiveHeight = 0;
178 const int activeHeight = m_closeButton->sizeHint().height() + 2;
179
180 m_minimumInactiveSize = QSize(minWidth, inactiveHeight);
181 m_maximumInactiveSize = QSize(maxWidth, inactiveHeight);
182 m_minimumActiveSize = QSize(minWidth, activeHeight);
183 m_maximumActiveSize = QSize(maxWidth, activeHeight);
184
185 auto layout = new QHBoxLayout(this);
186 layout->setSpacing(0);
187 layout->setContentsMargins(4, 0, 0, 0);
188 layout->addWidget(m_titleLabel);
189 layout->addStretch();
190 layout->addWidget(m_floatButton);
191 layout->addWidget(m_closeButton);
192 setLayout(layout);
193
194 setProperty("managed_titlebar", 1);
195 }
196
enterEvent(EnterEvent * event)197 void enterEvent(EnterEvent *event) override
198 {
199 setActive(true);
200 QWidget::enterEvent(event);
201 }
202
setActive(bool on)203 void setActive(bool on)
204 {
205 m_active = on;
206 updateChildren();
207 }
208
updateChildren()209 void updateChildren()
210 {
211 bool clickable = isClickable();
212 m_titleLabel->setVisible(clickable);
213 m_floatButton->setVisible(clickable);
214 m_closeButton->setVisible(clickable);
215 }
216
isClickable() const217 bool isClickable() const
218 {
219 return m_active || !q->q->autoHideTitleBars();
220 }
221
sizeHint() const222 QSize sizeHint() const override
223 {
224 ensurePolished();
225 return isClickable() ? m_maximumActiveSize : m_maximumInactiveSize;
226 }
227
minimumSizeHint() const228 QSize minimumSizeHint() const override
229 {
230 ensurePolished();
231 return isClickable() ? m_minimumActiveSize : m_minimumInactiveSize;
232 }
233
234 private:
235 DockWidget *q;
236 bool m_active;
237 QSize m_minimumActiveSize;
238 QSize m_maximumActiveSize;
239 QSize m_minimumInactiveSize;
240 QSize m_maximumInactiveSize;
241
242 public:
243 QLabel *m_titleLabel;
244 DockWidgetTitleButton *m_floatButton;
245 DockWidgetTitleButton *m_closeButton;
246 };
247
DockWidget(QWidget * inner,FancyMainWindow * parent,bool immutable)248 DockWidget::DockWidget(QWidget *inner, FancyMainWindow *parent, bool immutable)
249 : QDockWidget(parent), q(parent), m_immutable(immutable)
250 {
251 setWidget(inner);
252 setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable);
253 setObjectName(inner->objectName() + QLatin1String("DockWidget"));
254 setMouseTracking(true);
255
256 QString title = inner->windowTitle();
257 toggleViewAction()->setProperty("original_title", title);
258 title = Utils::stripAccelerator(title);
259 setWindowTitle(title);
260
261 QStyleOptionDockWidget opt;
262 initStyleOption(&opt);
263 m_titleBar = new TitleBarWidget(this, opt);
264 m_titleBar->m_titleLabel->setText(title);
265 setTitleBarWidget(m_titleBar);
266
267 if (immutable)
268 return;
269
270 m_timer.setSingleShot(true);
271 m_timer.setInterval(500);
272
273 connect(&m_timer, &QTimer::timeout, this, &DockWidget::handleMouseTimeout);
274
275 connect(this, &QDockWidget::topLevelChanged, this, &DockWidget::handleToplevelChanged);
276
277 connect(toggleViewAction(), &QAction::triggered,
278 [this]() {
279 if (isVisible())
280 raise();
281 });
282
283 auto origFloatButton = findChild<QAbstractButton *>(QLatin1String("qt_dockwidget_floatbutton"));
284 connect(m_titleBar->m_floatButton, &QAbstractButton::clicked,
285 origFloatButton, &QAbstractButton::clicked);
286
287 auto origCloseButton = findChild<QAbstractButton *>(QLatin1String("qt_dockwidget_closebutton"));
288 connect(m_titleBar->m_closeButton, &QAbstractButton::clicked,
289 origCloseButton, &QAbstractButton::clicked);
290 }
291
eventFilter(QObject *,QEvent * event)292 bool DockWidget::eventFilter(QObject *, QEvent *event)
293 {
294 if (!m_immutable && event->type() == QEvent::MouseMove && q->autoHideTitleBars()) {
295 auto me = static_cast<QMouseEvent *>(event);
296 int y = me->pos().y();
297 int x = me->pos().x();
298 int h = qMin(8, m_titleBar->m_floatButton->height());
299 if (!isFloating() && widget() && 0 <= x && x < widget()->width() && 0 <= y && y <= h) {
300 m_timer.start();
301 m_startPos = mapToGlobal(me->pos());
302 }
303 }
304 return false;
305 }
306
enterEvent(EnterEvent * event)307 void DockWidget::enterEvent(EnterEvent *event)
308 {
309 if (!m_immutable)
310 QApplication::instance()->installEventFilter(this);
311 QDockWidget::enterEvent(event);
312 }
313
leaveEvent(QEvent * event)314 void DockWidget::leaveEvent(QEvent *event)
315 {
316 if (!m_immutable) {
317 if (!isFloating()) {
318 m_timer.stop();
319 m_titleBar->setActive(false);
320 }
321 QApplication::instance()->removeEventFilter(this);
322 }
323 QDockWidget::leaveEvent(event);
324 }
325
handleMouseTimeout()326 void DockWidget::handleMouseTimeout()
327 {
328 QPoint dist = m_startPos - QCursor::pos();
329 if (!isFloating() && dist.manhattanLength() < 4)
330 m_titleBar->setActive(true);
331 }
332
handleToplevelChanged(bool floating)333 void DockWidget::handleToplevelChanged(bool floating)
334 {
335 m_titleBar->setActive(floating);
336 }
337
338
339
340 /*! \class Utils::FancyMainWindow
341
342 \brief The FancyMainWindow class is a MainWindow with dock widgets and
343 additional "lock" functionality
344 (locking the dock widgets in place) and "reset layout" functionality.
345
346 The dock actions and the additional actions should be accessible
347 in a Window-menu.
348 */
349
FancyMainWindowPrivate(FancyMainWindow * parent)350 FancyMainWindowPrivate::FancyMainWindowPrivate(FancyMainWindow *parent) :
351 q(parent),
352 m_handleDockVisibilityChanges(true),
353 m_showCentralWidget(FancyMainWindow::tr("Central Widget"), nullptr),
354 m_menuSeparator1(nullptr),
355 m_menuSeparator2(nullptr),
356 m_resetLayoutAction(FancyMainWindow::tr("Reset to Default Layout"), nullptr),
357 m_autoHideTitleBars(FancyMainWindow::tr("Automatically Hide View Title Bars"), nullptr)
358 {
359 m_showCentralWidget.setCheckable(true);
360 m_showCentralWidget.setChecked(true);
361
362 m_menuSeparator1.setSeparator(true);
363 m_menuSeparator2.setSeparator(true);
364
365 m_autoHideTitleBars.setCheckable(true);
366 m_autoHideTitleBars.setChecked(true);
367
368 QObject::connect(&m_autoHideTitleBars, &QAction::toggled, q, [this](bool) {
369 for (QDockWidget *dock : q->dockWidgets()) {
370 if (auto titleBar = dynamic_cast<TitleBarWidget *>(dock->titleBarWidget()))
371 titleBar->updateChildren();
372 }
373 });
374
375 QObject::connect(&m_showCentralWidget, &QAction::toggled, q, [this](bool visible) {
376 q->centralWidget()->setVisible(visible);
377 });
378 }
379
FancyMainWindow(QWidget * parent)380 FancyMainWindow::FancyMainWindow(QWidget *parent) :
381 QMainWindow(parent), d(new FancyMainWindowPrivate(this))
382 {
383 connect(&d->m_resetLayoutAction, &QAction::triggered,
384 this, &FancyMainWindow::resetLayout);
385 }
386
~FancyMainWindow()387 FancyMainWindow::~FancyMainWindow()
388 {
389 delete d;
390 }
391
addDockForWidget(QWidget * widget,bool immutable)392 QDockWidget *FancyMainWindow::addDockForWidget(QWidget *widget, bool immutable)
393 {
394 QTC_ASSERT(widget, return nullptr);
395 QTC_CHECK(widget->objectName().size());
396 QTC_CHECK(widget->windowTitle().size());
397
398 auto dockWidget = new DockWidget(widget, this, immutable);
399
400 if (!immutable) {
401 connect(dockWidget, &QDockWidget::visibilityChanged,
402 [this, dockWidget](bool visible) {
403 if (d->m_handleDockVisibilityChanges)
404 dockWidget->setProperty(dockWidgetActiveState, visible);
405 });
406
407 connect(dockWidget->toggleViewAction(), &QAction::triggered,
408 this, &FancyMainWindow::onDockActionTriggered,
409 Qt::QueuedConnection);
410
411 dockWidget->setProperty(dockWidgetActiveState, true);
412 }
413
414 return dockWidget;
415 }
416
onDockActionTriggered()417 void FancyMainWindow::onDockActionTriggered()
418 {
419 auto dw = qobject_cast<QDockWidget *>(sender()->parent());
420 if (dw) {
421 if (dw->isVisible())
422 dw->raise();
423 }
424 }
425
setTrackingEnabled(bool enabled)426 void FancyMainWindow::setTrackingEnabled(bool enabled)
427 {
428 if (enabled) {
429 d->m_handleDockVisibilityChanges = true;
430 for (QDockWidget *dockWidget : dockWidgets())
431 dockWidget->setProperty(dockWidgetActiveState, dockWidget->isVisible());
432 } else {
433 d->m_handleDockVisibilityChanges = false;
434 }
435 }
436
hideEvent(QHideEvent * event)437 void FancyMainWindow::hideEvent(QHideEvent *event)
438 {
439 Q_UNUSED(event)
440 handleVisibilityChanged(false);
441 }
442
showEvent(QShowEvent * event)443 void FancyMainWindow::showEvent(QShowEvent *event)
444 {
445 Q_UNUSED(event)
446 handleVisibilityChanged(true);
447 }
448
contextMenuEvent(QContextMenuEvent * event)449 void FancyMainWindow::contextMenuEvent(QContextMenuEvent *event)
450 {
451 QMenu menu;
452 addDockActionsToMenu(&menu);
453 menu.exec(event->globalPos());
454 }
455
handleVisibilityChanged(bool visible)456 void FancyMainWindow::handleVisibilityChanged(bool visible)
457 {
458 d->m_handleDockVisibilityChanges = false;
459 for (QDockWidget *dockWidget : dockWidgets()) {
460 if (dockWidget->isFloating()) {
461 dockWidget->setVisible(visible
462 && dockWidget->property(dockWidgetActiveState).toBool());
463 }
464 }
465 if (visible)
466 d->m_handleDockVisibilityChanges = true;
467 }
468
saveSettings(QSettings * settings) const469 void FancyMainWindow::saveSettings(QSettings *settings) const
470 {
471 const QHash<QString, QVariant> hash = saveSettings();
472 for (auto it = hash.cbegin(), end = hash.cend(); it != end; ++it)
473 settings->setValue(it.key(), it.value());
474 }
475
restoreSettings(const QSettings * settings)476 void FancyMainWindow::restoreSettings(const QSettings *settings)
477 {
478 QHash<QString, QVariant> hash;
479 const QStringList childKeys = settings->childKeys();
480 for (const QString &key : childKeys)
481 hash.insert(key, settings->value(key));
482 restoreSettings(hash);
483 }
484
saveSettings() const485 QHash<QString, QVariant> FancyMainWindow::saveSettings() const
486 {
487 QHash<QString, QVariant> settings;
488 settings.insert(QLatin1String(StateKey), saveState(settingsVersion));
489 settings.insert(QLatin1String(AutoHideTitleBarsKey),
490 d->m_autoHideTitleBars.isChecked());
491 settings.insert(ShowCentralWidgetKey, d->m_showCentralWidget.isChecked());
492 for (QDockWidget *dockWidget : dockWidgets()) {
493 settings.insert(dockWidget->objectName(),
494 dockWidget->property(dockWidgetActiveState));
495 }
496 return settings;
497 }
498
restoreSettings(const QHash<QString,QVariant> & settings)499 void FancyMainWindow::restoreSettings(const QHash<QString, QVariant> &settings)
500 {
501 QByteArray ba = settings.value(QLatin1String(StateKey), QByteArray()).toByteArray();
502 if (!ba.isEmpty())
503 restoreState(ba, settingsVersion);
504 bool on = settings.value(QLatin1String(AutoHideTitleBarsKey), true).toBool();
505 d->m_autoHideTitleBars.setChecked(on);
506 d->m_showCentralWidget.setChecked(settings.value(ShowCentralWidgetKey, true).toBool());
507 for (QDockWidget *widget : dockWidgets()) {
508 widget->setProperty(dockWidgetActiveState,
509 settings.value(widget->objectName(), false));
510 }
511 }
512
dockWidgets() const513 const QList<QDockWidget *> FancyMainWindow::dockWidgets() const
514 {
515 return findChildren<QDockWidget *>();
516 }
517
autoHideTitleBars() const518 bool FancyMainWindow::autoHideTitleBars() const
519 {
520 return d->m_autoHideTitleBars.isChecked();
521 }
522
setAutoHideTitleBars(bool on)523 void FancyMainWindow::setAutoHideTitleBars(bool on)
524 {
525 d->m_autoHideTitleBars.setChecked(on);
526 }
527
isCentralWidgetShown() const528 bool FancyMainWindow::isCentralWidgetShown() const
529 {
530 return d->m_showCentralWidget.isChecked();
531 }
532
showCentralWidget(bool on)533 void FancyMainWindow::showCentralWidget(bool on)
534 {
535 d->m_showCentralWidget.setChecked(on);
536 }
537
addDockActionsToMenu(QMenu * menu)538 void FancyMainWindow::addDockActionsToMenu(QMenu *menu)
539 {
540 QList<QAction *> actions;
541 QList<QDockWidget *> dockwidgets = findChildren<QDockWidget *>();
542 for (int i = 0; i < dockwidgets.size(); ++i) {
543 QDockWidget *dockWidget = dockwidgets.at(i);
544 if (dockWidget->property("managed_dockwidget").isNull()
545 && dockWidget->parentWidget() == this) {
546 QAction *action = dockWidget->toggleViewAction();
547 action->setText(action->property("original_title").toString());
548 actions.append(action);
549 }
550 }
551 Utils::sort(actions, [](const QAction *action1, const QAction *action2) {
552 QTC_ASSERT(action1, return true);
553 QTC_ASSERT(action2, return false);
554 return stripAccelerator(action1->text()).toLower() < stripAccelerator(action2->text()).toLower();
555 });
556 for (QAction *action : qAsConst(actions))
557 menu->addAction(action);
558 menu->addAction(&d->m_showCentralWidget);
559 menu->addAction(&d->m_menuSeparator1);
560 menu->addAction(&d->m_autoHideTitleBars);
561 menu->addAction(&d->m_menuSeparator2);
562 menu->addAction(&d->m_resetLayoutAction);
563 }
564
menuSeparator1() const565 QAction *FancyMainWindow::menuSeparator1() const
566 {
567 return &d->m_menuSeparator1;
568 }
569
autoHideTitleBarsAction() const570 QAction *FancyMainWindow::autoHideTitleBarsAction() const
571 {
572 return &d->m_autoHideTitleBars;
573 }
574
menuSeparator2() const575 QAction *FancyMainWindow::menuSeparator2() const
576 {
577 return &d->m_menuSeparator2;
578 }
579
resetLayoutAction() const580 QAction *FancyMainWindow::resetLayoutAction() const
581 {
582 return &d->m_resetLayoutAction;
583 }
584
showCentralWidgetAction() const585 QAction *FancyMainWindow::showCentralWidgetAction() const
586 {
587 return &d->m_showCentralWidget;
588 }
589
setDockActionsVisible(bool v)590 void FancyMainWindow::setDockActionsVisible(bool v)
591 {
592 for (const QDockWidget *dockWidget : dockWidgets())
593 dockWidget->toggleViewAction()->setVisible(v);
594 d->m_showCentralWidget.setVisible(v);
595 d->m_autoHideTitleBars.setVisible(v);
596 d->m_menuSeparator1.setVisible(v);
597 d->m_menuSeparator2.setVisible(v);
598 d->m_resetLayoutAction.setVisible(v);
599 }
600
601 } // namespace Utils
602