fancymainwindow.cpp 14.4 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
7
**
hjk's avatar
hjk committed
8 9 10 11 12 13 14
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
15 16
**
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17 18 19 20 21 22
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
hjk's avatar
hjk committed
24 25
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
con's avatar
con committed
26 27
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
28
****************************************************************************/
29 30 31

#include "fancymainwindow.h"

32 33
#include "qtcassert.h"

34
#include <QApplication>
35
#include <QContextMenuEvent>
36
#include <QDebug>
37
#include <QDockWidget>
38 39 40 41 42
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMenu>
#include <QPainter>
43
#include <QSettings>
44 45
#include <QStyle>
#include <QStyleOption>
46
#include <QTimer>
47
#include <QToolButton>
48

49
static const char stateKeyC[] = "State";
50
static const int settingsVersion = 2;
51
static const char dockWidgetActiveState[] = "DockWidgetActiveState";
52

53 54
namespace Utils {

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
// Stolen from QDockWidgetTitleButton
class DockWidgetTitleButton : public QAbstractButton
{
public:
    DockWidgetTitleButton(QWidget *parent)
        : QAbstractButton(parent)
    {
        setFocusPolicy(Qt::NoFocus);
    }

    QSize sizeHint() const
    {
        ensurePolished();

        int size = 2*style()->pixelMetric(QStyle::PM_DockWidgetTitleBarButtonMargin, 0, this);
        if (!icon().isNull()) {
            int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
            QSize sz = icon().actualSize(QSize(iconSize, iconSize));
            size += qMax(sz.width(), sz.height());
        }

        return QSize(size, size);
    }

    QSize minimumSizeHint() const { return sizeHint(); }

    void enterEvent(QEvent *event)
    {
        if (isEnabled()) update();
        QAbstractButton::enterEvent(event);
    }

    void leaveEvent(QEvent *event)
    {
        if (isEnabled()) update();
        QAbstractButton::leaveEvent(event);
    }

    void paintEvent(QPaintEvent *event);
};

void DockWidgetTitleButton::paintEvent(QPaintEvent *)
{
    QPainter p(this);

    QStyleOptionToolButton opt;
    opt.init(this);
    opt.state |= QStyle::State_AutoRaise;
    opt.icon = icon();
    opt.subControls = 0;
    opt.activeSubControls = 0;
    opt.features = QStyleOptionToolButton::None;
    opt.arrowType = Qt::NoArrow;
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
    opt.iconSize = QSize(size, size);
    style()->drawComplexControl(QStyle::CC_ToolButton, &opt, &p, this);
}


class TitleBarWidget : public QWidget
{
public:
    TitleBarWidget(QDockWidget *parent, const QStyleOptionDockWidget &opt)
      : QWidget(parent), q(parent), m_active(true)
    {
        m_titleLabel = new QLabel(this);

        m_floatButton = new DockWidgetTitleButton(this);
        m_floatButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarNormalButton, &opt, q));
        m_floatButton->setAccessibleName(QDockWidget::tr("Float"));
        m_floatButton->setAccessibleDescription(QDockWidget::tr("Undocks and re-attaches the dock widget"));

        m_closeButton = new DockWidgetTitleButton(this);
        m_closeButton->setIcon(q->style()->standardIcon(QStyle::SP_TitleBarCloseButton, &opt, q));
        m_closeButton->setAccessibleName(QDockWidget::tr("Close"));
        m_closeButton->setAccessibleDescription(QDockWidget::tr("Closes the dock widget"));

        setActive(false);

        const int minWidth = 10;
        const int maxWidth = 10000;
136
        const int inactiveHeight = 0;
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
        const int activeHeight = m_closeButton->sizeHint().height() + 2;

        m_minimumInactiveSize = QSize(minWidth, inactiveHeight);
        m_maximumInactiveSize = QSize(maxWidth, inactiveHeight);
        m_minimumActiveSize   = QSize(minWidth, activeHeight);
        m_maximumActiveSize   = QSize(maxWidth, activeHeight);

        auto layout = new QHBoxLayout(this);
        layout->setMargin(0);
        layout->setSpacing(0);
        layout->setContentsMargins(4, 0, 0, 0);
        layout->addWidget(m_titleLabel);
        layout->addStretch();
        layout->addWidget(m_floatButton);
        layout->addWidget(m_closeButton);
        setLayout(layout);
    }

    void leaveEvent(QEvent *event)
    {
        if (!q->isFloating())
            setActive(false);
        QWidget::leaveEvent(event);
    }

    void setActive(bool on)
    {
        if (m_active == on)
            return;
        m_active = on;
        m_titleLabel->setVisible(on);
        m_floatButton->setVisible(on);
        m_closeButton->setVisible(on);
        update();
    }

    QSize sizeHint() const
    {
        ensurePolished();
        return m_active ? m_maximumActiveSize : m_maximumInactiveSize;
    }

    QSize minimumSizeHint() const
    {
        ensurePolished();
        return m_active ? m_minimumActiveSize : m_minimumInactiveSize;
    }

public:
    QDockWidget *q;
    bool m_active;
    QSize m_minimumActiveSize;
    QSize m_maximumActiveSize;
    QSize m_minimumInactiveSize;
    QSize m_maximumInactiveSize;

    QLabel *m_titleLabel;
    DockWidgetTitleButton *m_floatButton;
    DockWidgetTitleButton *m_closeButton;
};


class DockWidget : public QDockWidget
{
public:
    DockWidget(QWidget *inner, QWidget *parent)
203
        : QDockWidget(parent), m_inner(inner)
204 205 206 207 208
    {
        setWidget(inner);
        setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable);
        setObjectName(inner->objectName() + QLatin1String("DockWidget"));
        setWindowTitle(inner->windowTitle());
209
        setMouseTracking(true);
210 211 212

        QStyleOptionDockWidget opt;
        initStyleOption(&opt);
213 214 215 216 217 218 219
        m_titleBar = new TitleBarWidget(this, opt);
        m_titleBar->m_titleLabel->setText(inner->windowTitle());
        setTitleBarWidget(m_titleBar);

        m_timer.setSingleShot(true);
        m_timer.setInterval(500);

hjk's avatar
hjk committed
220 221 222
        connect(&m_timer, &QTimer::timeout, this, &DockWidget::handleMouseTimeout);

        connect(this, &QDockWidget::topLevelChanged, this, &DockWidget::handleToplevelChanged);
223

hjk's avatar
hjk committed
224 225 226 227 228
        connect(toggleViewAction(), &QAction::triggered,
                [this]() {
                    if (isVisible())
                        raise();
                });
229 230

        auto origFloatButton = findChild<QAbstractButton *>(QLatin1String("qt_dockwidget_floatbutton"));
hjk's avatar
hjk committed
231 232
        connect(m_titleBar->m_floatButton, &QAbstractButton::clicked,
                origFloatButton, &QAbstractButton::clicked);
233 234

        auto origCloseButton = findChild<QAbstractButton *>(QLatin1String("qt_dockwidget_closebutton"));
hjk's avatar
hjk committed
235 236
        connect(m_titleBar->m_closeButton, &QAbstractButton::clicked,
                origCloseButton, &QAbstractButton::clicked);
237
    }
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256

    bool eventFilter(QObject *, QEvent *event)
    {
        if (event->type() == QEvent::MouseMove) {
            QMouseEvent *me = static_cast<QMouseEvent *>(event);
            int y = me->pos().y();
            int x = me->pos().x();
            int h = m_titleBar->m_floatButton->height();
            if (!isFloating() && 0 <= x && x < m_inner->width() && 0 <= y && y <= h) {
                m_timer.start();
                m_startPos = mapToGlobal(me->pos());
            }
        }
        return false;
    }

    void enterEvent(QEvent *event)
    {
        QApplication::instance()->installEventFilter(this);
257
        QDockWidget::enterEvent(event);
258 259 260 261 262 263 264 265
    }

    void leaveEvent(QEvent *event)
    {
        QApplication::instance()->removeEventFilter(this);
        QDockWidget::leaveEvent(event);
    }

hjk's avatar
hjk committed
266
    void handleMouseTimeout()
267 268 269 270 271 272 273
    {
        QPoint dist = m_startPos - QCursor::pos();
        if (!isFloating() && dist.manhattanLength() < 4) {
            m_titleBar->setActive(true);
        }
    }

hjk's avatar
hjk committed
274
    void handleToplevelChanged(bool floating)
275 276 277 278 279 280 281 282 283 284
    {
        if (!floating)
            m_titleBar->setActive(false);
    }

private:
    QPoint m_startPos;
    QWidget *m_inner;
    TitleBarWidget *m_titleBar;
    QTimer m_timer;
285 286
};

287 288
/*! \class Utils::FancyMainWindow

289 290
    \brief The FancyMainWindow class is a MainWindow with dock widgets and
    additional "lock" functionality
291 292 293 294 295 296
    (locking the dock widgets in place) and "reset layout" functionality.

    The dock actions and the additional actions should be accessible
    in a Window-menu.
*/

297
class FancyMainWindowPrivate
hjk's avatar
hjk committed
298
{
299
public:
hjk's avatar
hjk committed
300
    FancyMainWindowPrivate();
301

Eike Ziller's avatar
Eike Ziller committed
302
    bool m_handleDockVisibilityChanges;
303

304
    QAction m_menuSeparator;
hjk's avatar
hjk committed
305
    QAction m_resetLayoutAction;
306
    QDockWidget *m_toolBarDockWidget;
307 308
};

hjk's avatar
hjk committed
309 310
FancyMainWindowPrivate::FancyMainWindowPrivate() :
    m_handleDockVisibilityChanges(true),
311
    m_menuSeparator(0),
hjk's avatar
hjk committed
312
    m_resetLayoutAction(FancyMainWindow::tr("Reset to Default Layout"), 0),
313
    m_toolBarDockWidget(0)
314
{
315
    m_menuSeparator.setSeparator(true);
316
}
317

318
FancyMainWindow::FancyMainWindow(QWidget *parent) :
hjk's avatar
hjk committed
319
    QMainWindow(parent), d(new FancyMainWindowPrivate)
320
{
hjk's avatar
hjk committed
321 322
    connect(&d->m_resetLayoutAction, &QAction::triggered,
            this, &FancyMainWindow::resetLayout);
323 324
}

325 326 327 328 329
FancyMainWindow::~FancyMainWindow()
{
    delete d;
}

330 331
QDockWidget *FancyMainWindow::addDockForWidget(QWidget *widget)
{
332 333 334 335 336
    QTC_ASSERT(widget, return 0);
    QTC_CHECK(widget->objectName().size());
    QTC_CHECK(widget->windowTitle().size());

    auto dockWidget = new DockWidget(widget, this);
337

hjk's avatar
hjk committed
338 339 340 341 342
    connect(dockWidget, &QDockWidget::visibilityChanged,
        [this, dockWidget](bool visible) {
            if (d->m_handleDockVisibilityChanges)
                dockWidget->setProperty(dockWidgetActiveState, visible);
        });
343 344


hjk's avatar
hjk committed
345 346 347
    dockWidget->setProperty(dockWidgetActiveState, true);

    return dockWidget;
348 349 350 351 352
}

void FancyMainWindow::setTrackingEnabled(bool enabled)
{
    if (enabled) {
353
        d->m_handleDockVisibilityChanges = true;
354 355
        foreach (QDockWidget *dockWidget, dockWidgets())
            dockWidget->setProperty(dockWidgetActiveState, dockWidget->isVisible());
356
    } else {
357
        d->m_handleDockVisibilityChanges = false;
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372
    }
}

void FancyMainWindow::hideEvent(QHideEvent *event)
{
    Q_UNUSED(event)
    handleVisibilityChanged(false);
}

void FancyMainWindow::showEvent(QShowEvent *event)
{
    Q_UNUSED(event)
    handleVisibilityChanged(true);
}

373 374
void FancyMainWindow::contextMenuEvent(QContextMenuEvent *event)
{
375 376 377
    QMenu menu;
    addDockActionsToMenu(&menu);
    menu.exec(event->globalPos());
378 379
}

380 381
void FancyMainWindow::handleVisibilityChanged(bool visible)
{
382
    d->m_handleDockVisibilityChanges = false;
383
    foreach (QDockWidget *dockWidget, dockWidgets()) {
384
        if (dockWidget->isFloating()) {
385 386
            dockWidget->setVisible(visible
                && dockWidget->property(dockWidgetActiveState).toBool());
387 388 389
        }
    }
    if (visible)
390
        d->m_handleDockVisibilityChanges = true;
391 392 393 394
}

void FancyMainWindow::saveSettings(QSettings *settings) const
{
395 396 397 398 399
    QHash<QString, QVariant> hash = saveSettings();
    QHashIterator<QString, QVariant> it(hash);
    while (it.hasNext()) {
        it.next();
        settings->setValue(it.key(), it.value());
400 401 402
    }
}

403
void FancyMainWindow::restoreSettings(const QSettings *settings)
404
{
405 406 407 408 409 410 411 412 413 414
    QHash<QString, QVariant> hash;
    foreach (const QString &key, settings->childKeys()) {
        hash.insert(key, settings->value(key));
    }
    restoreSettings(hash);
}

QHash<QString, QVariant> FancyMainWindow::saveSettings() const
{
    QHash<QString, QVariant> settings;
415
    settings.insert(QLatin1String(stateKeyC), saveState(settingsVersion));
416
    foreach (QDockWidget *dockWidget, dockWidgets()) {
417
        settings.insert(dockWidget->objectName(),
418
                dockWidget->property(dockWidgetActiveState));
419 420 421 422 423 424
    }
    return settings;
}

void FancyMainWindow::restoreSettings(const QHash<QString, QVariant> &settings)
{
425
    QByteArray ba = settings.value(QLatin1String(stateKeyC), QByteArray()).toByteArray();
426
    if (!ba.isEmpty())
427
        restoreState(ba, settingsVersion);
428 429 430
    foreach (QDockWidget *widget, dockWidgets()) {
        widget->setProperty(dockWidgetActiveState,
            settings.value(widget->objectName(), false));
431 432
    }
}
433 434 435

QList<QDockWidget *> FancyMainWindow::dockWidgets() const
{
436
    return findChildren<QDockWidget *>();
437 438
}

439 440 441 442 443 444 445
static bool actionLessThan(const QAction *action1, const QAction *action2)
{
    QTC_ASSERT(action1, return true);
    QTC_ASSERT(action2, return false);
    return action1->text().toLower() < action2->text().toLower();
}

446
void FancyMainWindow::addDockActionsToMenu(QMenu *menu)
447
{
448
    QList<QAction *> actions;
449
    QList<QDockWidget *> dockwidgets = findChildren<QDockWidget *>();
450 451 452 453
    for (int i = 0; i < dockwidgets.size(); ++i) {
        QDockWidget *dockWidget = dockwidgets.at(i);
        if (dockWidget->property("managed_dockwidget").isNull()
                && dockWidget->parentWidget() == this) {
454
            actions.append(dockwidgets.at(i)->toggleViewAction());
455 456
        }
    }
457 458 459
    qSort(actions.begin(), actions.end(), actionLessThan);
    foreach (QAction *action, actions)
        menu->addAction(action);
460
    menu->addAction(&d->m_menuSeparator);
hjk's avatar
hjk committed
461
    menu->addAction(&d->m_resetLayoutAction);
462 463
}

464
QAction *FancyMainWindow::menuSeparator() const
465
{
466
    return &d->m_menuSeparator;
467 468 469 470
}

QAction *FancyMainWindow::resetLayoutAction() const
{
hjk's avatar
hjk committed
471
    return &d->m_resetLayoutAction;
472 473 474 475
}

void FancyMainWindow::setDockActionsVisible(bool v)
{
hjk's avatar
hjk committed
476
    foreach (const QDockWidget *dockWidget, dockWidgets())
477
        dockWidget->toggleViewAction()->setVisible(v);
478
    d->m_menuSeparator.setVisible(v);
hjk's avatar
hjk committed
479
    d->m_resetLayoutAction.setVisible(v);
480 481
}

482 483 484 485 486 487 488 489 490 491
QDockWidget *FancyMainWindow::toolBarDockWidget() const
{
    return d->m_toolBarDockWidget;
}

void FancyMainWindow::setToolBarDockWidget(QDockWidget *dock)
{
    d->m_toolBarDockWidget = dock;
}

492
} // namespace Utils