Update ToolWindowManager to 4c259a67f36491a7c1f92cfea9a424c94e0e364b

* From my fork - https://github.com/baldurk/toolwindowmanager
* Contains many changes and improvements to make the docking solution
  more usable.
This commit is contained in:
baldurk
2017-05-29 13:19:46 +01:00
parent 7b5ca0f4b6
commit 4bc361fc1a
9 changed files with 1601 additions and 418 deletions
+26 -8
View File
@@ -1,18 +1,36 @@
ToolWindowManager
=================
ToolWindowManager is a Qt based tool window manager.
ToolWindowManager is a Qt based tool window manager. This allows Qt projects to use docking functionality similar to QDockWidget, but since it's a separate component it's easier to customise and extend and so has a number of improvements over the built-in docking system.
This project implements docking tool behavior that is similar to tool windows mechanism in Visual Studio or Eclipse. User can arrange tool windows in tabs, dock it to any border, split with vertical and horizontal splitters, tabify them together and detach to floating windows.
This is a fork from https://github.com/riateche/toolwindowmanager, specifically from [5244be3f9ac680ac568a6eff8156a520ce08ecf1](https://github.com/baldurk/toolwindowmanager/tree/original_impl) where the license is clearly MIT. After that point there was a re-implementation that may have relicensed under LGPL and the author has not clarified.
[API documentation](http://riateche.github.io/toolwindowmanager/doc/class_tool_window_manager.html)
Also this fork contains a number of changes and improvements to make it useful for RenderDoc and perhaps other projects. Notable highlights:
Demo (animated GIF):
* Additional customisability like arbitrary data tagged with saved states, callbacks to check before closing, and allowing/disallowing tab reorder or float windows
* Fixes for having multiple nested TWMs
* Render a preview overlay for drop locations
* Use hotspots icons and specific locations to determine drop sites, not the old 'cycle through suggestions' method
* Allow dragging/dropping whole floating windows together
![demo](doc/0.gif)
The original README.md [can be found here](https://github.com/baldurk/toolwindowmanager/blob/original_impl/README.md)
More screenshots:
Screenshots
===========
![screenshot](doc/1.png)
Windows:
![screenshot](doc/2.png)
![Windows](docs/windows.png)
Linux (Ubuntu 17.04 Unity);
![Ubuntu 17.04 Unity](docs/unity.png)
Linux (Ubuntu 17.04 KDE Plasma):
![Ubuntu 17.04 KDE Plasma](docs/plasma.png)
OS X:
![OS X](docs/osx.png)
File diff suppressed because it is too large Load Diff
+62 -75
View File
@@ -26,11 +26,6 @@
#define TOOLWINDOWMANAGER_H
#include <QWidget>
#include <QSplitter>
#include <QTabWidget>
#include <QTabBar>
#include <QTimer>
#include <QRubberBand>
#include <QHash>
#include <QVariant>
#include <QLabel>
@@ -40,6 +35,8 @@
class ToolWindowManagerArea;
class ToolWindowManagerWrapper;
class QLabel;
class QSplitter;
/*!
* \brief The ToolWindowManager class provides docking tool behavior.
@@ -53,37 +50,6 @@ class ToolWindowManagerWrapper;
*/
class ToolWindowManager : public QWidget {
Q_OBJECT
/*!
* \brief The delay between showing the next suggestion of drop location in milliseconds.
*
* When user starts a tool window drag and moves mouse pointer to a position, there can be
* an ambiguity in new position of the tool window. If user holds the left mouse button and
* stops mouse movements, all possible suggestions will be indicated periodically, one at a time.
*
* Default value is 1000 (i.e. 1 second).
*
* Access functions: suggestionSwitchInterval, setSuggestionSwitchInterval.
*
*/
Q_PROPERTY(int suggestionSwitchInterval READ suggestionSwitchInterval WRITE setSuggestionSwitchInterval)
/*!
* \brief Maximal distance in pixels between mouse position and area border that allows
* to display a suggestion.
*
* Default value is 12.
*
* Access functions: borderSensitivity, setBorderSensitivity.
*/
Q_PROPERTY(int borderSensitivity READ borderSensitivity WRITE setBorderSensitivity)
/*!
* \brief Visible width of rubber band line that is used to display drop suggestions.
*
* Default value is the same as QSplitter::handleWidth default value on current platform.
*
* Access functions: rubberBandLineWidth, setRubberBandLineWidth.
*
*/
Q_PROPERTY(int rubberBandLineWidth READ rubberBandLineWidth WRITE setRubberBandLineWidth)
/*!
* \brief Whether or not to allow floating windows to be created.
*
@@ -94,6 +60,26 @@ class ToolWindowManager : public QWidget {
*/
Q_PROPERTY(int allowFloatingWindow READ allowFloatingWindow WRITE setAllowFloatingWindow)
/*!
* \brief How much of a margin should be placed between drop hotspots.
*
* Default value is 4.
*
* Access functions: dropHotspotMargin, setDropHotspotMargin.
*
*/
Q_PROPERTY(int dropHotspotMargin READ dropHotspotMargin WRITE setDropHotspotMargin)
/*!
* \brief How wide and heigh each drop hotspot icon should be drawn at, in pixels.
*
* Default value is 32.
*
* Access functions: dropHotspotDimension, setDropHotspotDimension.
*
*/
Q_PROPERTY(int dropHotspotDimension READ dropHotspotDimension WRITE setDropHotspotDimension)
public:
/*!
* \brief Creates a manager with given \a parent.
@@ -138,7 +124,17 @@ public:
//! New area to the top of the area specified in AreaReference argument.
TopOf,
//! New area to the bottom of the area specified in AreaReference argument.
BottomOf
BottomOf,
//! New area to the left of the window containing the specified in AreaReference argument.
LeftWindowSide,
//! New area to the right of the window containing the specified in AreaReference argument.
RightWindowSide,
//! New area to the top of the window containing the specified in AreaReference argument.
TopWindowSide,
//! New area to the bottom of the window containing the specified in AreaReference argument.
BottomWindowSide,
//! Invalid value, just indicates the number of types available
NumReferenceTypes
};
/*!
@@ -160,6 +156,7 @@ public:
AreaReferenceType m_type;
QWidget* m_widget;
float m_percentage;
bool dragResult;
QWidget* widget() const { return m_widget; }
float percentage() const { return m_percentage; }
AreaReference(AreaReferenceType type, QWidget* widget);
@@ -255,29 +252,19 @@ public:
void setToolWindowCreateCallback(const CreateCallback &cb) { m_createCallback = cb; }
QWidget *createToolWindow(const QString& objectName);
bool checkValidSplitter(QWidget *w);
void setHotspotPixmap(AreaReferenceType ref, const QPixmap &pix) { m_pixmaps[ref] = pix; }
void setDropHotspotMargin(int pixels);
bool dropHotspotMargin() { return m_dropHotspotMargin; }
void setDropHotspotDimension(int pixels);
bool dropHotspotDimension() { return m_dropHotspotDimension; }
/*! \cond PRIVATE */
void setSuggestionSwitchInterval(int msec);
int suggestionSwitchInterval();
int borderSensitivity() { return m_borderSensitivity; }
void setBorderSensitivity(int pixels);
void setRubberBandLineWidth(int pixels);
int rubberBandLineWidth() { return m_rubberBandLineWidth; }
void setAllowFloatingWindow(bool pixels);
bool allowFloatingWindow() { return m_allowFloatingWindow; }
/*! \endcond */
/*!
* Returns the widget that is used to display rectangular drop suggestions.
*/
QRubberBand* rectRubberBand() { return m_rectRubberBand; }
/*!
* Returns the widget that is used to display line drop suggestions.
*/
QRubberBand* lineRubberBand() { return m_lineRubberBand; }
signals:
/*!
@@ -291,38 +278,42 @@ private:
QHash<QWidget*, ToolWindowProperty> m_toolWindowProperties; // all tool window properties
QList<ToolWindowManagerArea*> m_areas; // all areas for this manager
QList<ToolWindowManagerWrapper*> m_wrappers; // all wrappers for this manager
int m_borderSensitivity;
int m_rubberBandLineWidth;
// list of tool windows that are currently dragged, or empty list if there is no current drag
QList<QWidget*> m_draggedToolWindows;
QLabel* m_dragIndicator; // label used to display dragged content
ToolWindowManagerWrapper* m_draggedWrapper; // the wrapper if a whole float window is being dragged
ToolWindowManagerArea* m_hoverArea; // the area currently being hovered over in a drag
// a semi-transparent preview of where the dragged toolwindow(s) will be docked
QWidget* m_previewOverlay;
QWidget* m_previewTabOverlay;
QWidget* m_dropHotspotsOverlay; // an overlay parent where we add drop hotspots.
QLabel* m_dropHotspots[NumReferenceTypes];
QPixmap m_pixmaps[NumReferenceTypes];
QRubberBand* m_rectRubberBand; // placeholder objects used for displaying drop suggestions
QRubberBand* m_lineRubberBand;
bool m_allowFloatingWindow; // Allow floating windows from this docking area
QList<AreaReference> m_suggestions; //full list of suggestions for current cursor position
int m_dropCurrentSuggestionIndex; // index of currently displayed drop suggestion
// (e.g. always 0 if there is only one possible drop location)
QTimer m_dropSuggestionSwitchTimer; // used for switching drop suggestions
int m_dropHotspotMargin; // The pixels between drop hotspot icons
int m_dropHotspotDimension; // The pixel dimension of the hotspot icons
CreateCallback m_createCallback;
ToolWindowManagerWrapper* wrapperOf(QWidget* toolWindow);
void drawHotspotPixmaps();
// last widget used for adding tool windows, or 0 if there isn't one
// (warning: may contain pointer to deleted object)
ToolWindowManagerArea* m_lastUsedArea;
void handleNoSuggestions();
//remove tool window from its area (if any) and set parent to 0
void releaseToolWindow(QWidget* toolWindow);
void simplifyLayout(); //remove constructions that became useless
void startDrag(const QList<QWidget*>& toolWindows);
void startDrag(const QList<QWidget*>& toolWindows, ToolWindowManagerWrapper *wrapper);
QVariantMap saveSplitterState(QSplitter* splitter);
QSplitter* restoreSplitterState(const QVariantMap& data);
void findSuggestions(ToolWindowManagerWrapper *wrapper);
QRect sideSensitiveArea(QWidget* widget, AreaReferenceType side);
QRect sidePlaceHolderRect(QWidget* widget, AreaReferenceType side);
AreaReferenceType currentHotspot();
void updateDragPosition();
void abortDrag();
void finishDrag();
bool dragInProgress() { return !m_draggedToolWindows.isEmpty(); }
@@ -330,6 +321,9 @@ private:
friend class ToolWindowManagerWrapper;
protected:
//! Event filter for grabbing and processing drag aborts.
virtual bool eventFilter(QObject *object, QEvent *event);
/*!
* \brief Creates new splitter and sets its default properties. You may reimplement
* this function to change properties of all splitters used by this class.
@@ -340,15 +334,8 @@ protected:
* this function to change properties of all tab widgets used by this class.
*/
virtual ToolWindowManagerArea *createArea();
/*!
* \brief Generates a pixmap that is used to represent the data in a drag and drop operation
* near the mouse cursor.
* You may reimplement this function to use different pixmaps.
*/
virtual QPixmap generateDragPixmap(const QList<QWidget *> &toolWindows);
private slots:
void showNextDropSuggestion();
void tabCloseRequested(int index);
void windowTitleChanged(const QString &title);
@@ -23,10 +23,12 @@
*
*/
#include "ToolWindowManagerArea.h"
#include "ToolWindowManagerTabBar.h"
#include "ToolWindowManagerWrapper.h"
#include "ToolWindowManager.h"
#include <QApplication>
#include <QMouseEvent>
#include <QDebug>
#include <algorithm>
static void showCloseButton(QTabBar *bar, int index, bool show) {
QWidget *button = bar->tabButton(index, QTabBar::RightSide);
@@ -41,31 +43,39 @@ ToolWindowManagerArea::ToolWindowManagerArea(ToolWindowManager *manager, QWidget
QTabWidget(parent)
, m_manager(manager)
{
m_tabBar = new ToolWindowManagerTabBar(this);
setTabBar(m_tabBar);
m_tabBar->setTabsClosable(true);
m_dragCanStart = false;
m_tabDragCanStart = false;
m_inTabMoved = false;
m_userCanDrop = true;
setMovable(true);
setTabsClosable(true);
setDocumentMode(true);
tabBar()->installEventFilter(this);
m_manager->m_areas << this;
QObject::connect(tabBar(), &QTabBar::tabMoved, this, &ToolWindowManagerArea::tabMoved);
QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &ToolWindowManagerArea::tabClosing);
QObject::connect(tabBar(), &QTabBar::tabCloseRequested, this, &QTabWidget::tabCloseRequested);
QObject::connect(this, &QTabWidget::currentChanged, this, &ToolWindowManagerArea::tabSelected);
}
ToolWindowManagerArea::~ToolWindowManagerArea() {
m_manager->m_areas.removeOne(this);
}
void ToolWindowManagerArea::addToolWindow(QWidget *toolWindow) {
addToolWindows(QList<QWidget*>() << toolWindow);
void ToolWindowManagerArea::addToolWindow(QWidget *toolWindow, int insertIndex) {
addToolWindows(QList<QWidget*>() << toolWindow, insertIndex);
}
void ToolWindowManagerArea::addToolWindows(const QList<QWidget *> &toolWindows) {
void ToolWindowManagerArea::addToolWindows(const QList<QWidget *> &toolWindows, int insertIndex) {
int index = 0;
foreach(QWidget* toolWindow, toolWindows) {
index = addTab(toolWindow, toolWindow->windowIcon(), toolWindow->windowTitle());
index = insertTab(insertIndex, toolWindow, toolWindow->windowIcon(), toolWindow->windowTitle());
insertIndex = index+1;
if(m_manager->toolWindowProperties(toolWindow) & ToolWindowManager::HideCloseButton) {
showCloseButton(tabBar(), index, false);
}
@@ -97,6 +107,7 @@ void ToolWindowManagerArea::updateToolWindow(QWidget* toolWindow) {
void ToolWindowManagerArea::mousePressEvent(QMouseEvent *) {
if (qApp->mouseButtons() == Qt::LeftButton) {
m_dragCanStart = true;
m_dragCanStartPos = QCursor::pos();
}
}
@@ -114,7 +125,9 @@ bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseButtonPress &&
qApp->mouseButtons() == Qt::LeftButton) {
int tabIndex = tabBar()->tabAt(static_cast<QMouseEvent*>(event)->pos());
QPoint pos = static_cast<QMouseEvent*>(event)->pos();
int tabIndex = tabBar()->tabAt(pos);
// can start tab drag only if mouse is at some tab, not at empty tabbar space
if (tabIndex >= 0) {
@@ -125,8 +138,9 @@ bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) {
} else {
setMovable(true);
}
} else {
} else if (m_tabBar == NULL || !m_tabBar->inButton(pos)) {
m_dragCanStart = true;
m_dragCanStartPos = QCursor::pos();
}
} else if (event->type() == QEvent::MouseButtonPress &&
qApp->mouseButtons() == Qt::MiddleButton) {
@@ -163,7 +177,7 @@ bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) {
static_cast<QMouseEvent*>(event)->pos(),
Qt::LeftButton, Qt::LeftButton, 0);
qApp->sendEvent(tabBar(), releaseEvent);
m_manager->startDrag(QList<QWidget*>() << toolWindow);
m_manager->startDrag(QList<QWidget*>() << toolWindow, NULL);
} else if (m_dragCanStart) {
check_mouse_move();
}
@@ -172,6 +186,66 @@ bool ToolWindowManagerArea::eventFilter(QObject *object, QEvent *event) {
return QTabWidget::eventFilter(object, event);
}
void ToolWindowManagerArea::tabInserted(int index) {
// update the select order. Increment any existing index after the insertion point to keep the
// indices in the list up to date.
for (int &idx : m_tabSelectOrder) {
if (idx >= index)
idx++;
}
// if the tab inserted is the current index (most likely) then add it at the end, otherwise
// add it next-to-end (to keep the most recent tab the same).
if (currentIndex() == index || m_tabSelectOrder.isEmpty())
m_tabSelectOrder.append(index);
else
m_tabSelectOrder.insert(m_tabSelectOrder.count()-1, index);
QTabWidget::tabInserted(index);
}
void ToolWindowManagerArea::tabRemoved(int index) {
// update the select order. Remove the index that just got deleted, and decrement any index
// greater than it to remap to their new indices
m_tabSelectOrder.removeOne(index);
for (int &idx : m_tabSelectOrder) {
if (idx > index)
idx--;
}
QTabWidget::tabRemoved(index);
}
void ToolWindowManagerArea::tabSelected(int index) {
// move this tab to the end of the select order, as long as we have it - if it's a new index then
// ignore and leave it to be handled in tabInserted()
if (m_tabSelectOrder.contains(index)) {
m_tabSelectOrder.removeOne(index);
m_tabSelectOrder.append(index);
}
ToolWindowManagerWrapper* wrapper = m_manager->wrapperOf(this);
if (wrapper)
wrapper->updateTitle();
}
void ToolWindowManagerArea::tabClosing(int index) {
// before closing this index, switch the current index to the next tab in succession.
// should never get here but let's check this
if (m_tabSelectOrder.isEmpty())
return;
// when closing the last tab there's nothing to do
if (m_tabSelectOrder.count() == 1)
return;
// if the last in the select order is being closed, switch to the next most selected tab
if (m_tabSelectOrder.last() == index)
setCurrentIndex(m_tabSelectOrder.at(m_tabSelectOrder.count()-2));
}
QVariantMap ToolWindowManagerArea::saveState() {
QVariantMap result;
result[QStringLiteral("type")] = QStringLiteral("area");
@@ -220,9 +294,8 @@ void ToolWindowManagerArea::restoreState(const QVariantMap &savedData) {
void ToolWindowManagerArea::check_mouse_move() {
m_manager->updateDragPosition();
if (qApp->mouseButtons() == Qt::LeftButton &&
!rect().contains(mapFromGlobal(QCursor::pos())) &&
m_dragCanStart) {
if (m_dragCanStart &&
(QCursor::pos() - m_dragCanStartPos).manhattanLength() > 10) {
m_dragCanStart = false;
QList<QWidget*> toolWindows;
for(int i = 0; i < count(); i++) {
@@ -233,13 +306,24 @@ void ToolWindowManagerArea::check_mouse_move() {
toolWindows << toolWindow;
}
}
m_manager->startDrag(toolWindows);
m_manager->startDrag(toolWindows, NULL);
}
}
void ToolWindowManagerArea::tabMoved(int from, int to) {
if(m_inTabMoved) return;
// update the select order.
// This amounts to just a swap - any indices other than the pair in question are unaffected since
// one tab is removed (above/below) and added (below/above) so the indices themselves remain the
// same.
for (int &idx : m_tabSelectOrder) {
if (idx == from)
idx = to;
else if (idx == to)
idx = from;
}
QWidget *a = widget(from);
QWidget *b = widget(to);
@@ -29,6 +29,7 @@
#include <QVariantMap>
class ToolWindowManager;
class ToolWindowManagerTabBar;
/*!
* \brief The ToolWindowManagerArea class is a tab widget used to store tool windows.
@@ -45,12 +46,12 @@ public:
/*!
* Add \a toolWindow to this area.
*/
void addToolWindow(QWidget* toolWindow);
void addToolWindow(QWidget* toolWindow, int insertIndex = -1);
/*!
* Add \a toolWindows to this area.
*/
void addToolWindows(const QList<QWidget*>& toolWindows);
void addToolWindows(const QList<QWidget*>& toolWindows, int insertIndex = -1);
void enableUserDrop() { m_userCanDrop = true; }
void disableUserDrop() { m_userCanDrop = false; }
@@ -79,11 +80,18 @@ protected:
//! Reimplemented from QTabWidget::eventFilter.
virtual bool eventFilter(QObject *object, QEvent *event);
//! Reimplemented from QTabWidget::tabInserted.
virtual void tabInserted(int index);
//! Reimplemented from QTabWidget::tabRemoved.
virtual void tabRemoved(int index);
private:
ToolWindowManager* m_manager;
ToolWindowManagerTabBar* m_tabBar;
bool m_dragCanStart; // indicates that user has started mouse movement on QTabWidget
// that can be considered as dragging it if the cursor will leave
// its area
QPoint m_dragCanStartPos; // the position the cursor was at
bool m_tabDragCanStart; // indicates that user has started mouse movement on QTabWidget
// that can be considered as dragging current tab
@@ -94,6 +102,10 @@ private:
bool m_inTabMoved; // if we're in the tabMoved() function (so if we call tabMove to cancel
// the movement, we shouldn't re-check the tabMoved behaviour)
QVector<int> m_tabSelectOrder; // This is the 'history' order of the tabs as they were selected,
// with most recently selected index last. Any time a tab is closed
// we select the last one on the list.
QVariantMap saveState(); // dump contents to variable
void restoreState(const QVariantMap& data); //restore contents from given variable
@@ -105,6 +117,8 @@ private:
private slots:
void tabMoved(int from, int to);
void tabSelected(int index);
void tabClosing(int index);
};
#endif // TOOLWINDOWMANAGERAREA_H
@@ -0,0 +1,306 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#include "ToolWindowManagerTabBar.h"
#include "ToolWindowManagerArea.h"
#include "ToolWindowManagerWrapper.h"
#include <QMouseEvent>
#include <QStyle>
#include <QStylePainter>
#include <QStyleOption>
ToolWindowManagerTabBar::ToolWindowManagerTabBar(QWidget *parent) :
QTabBar(parent)
{
m_tabsClosable = false;
setMouseTracking(true);
// Workaround for extremely dodgy KDE behaviour - by default the KDE theme will install event
// filters on various widgets such as QTabBar and any descendents, and if a click is detected on
// them that isn't on a tab it will immediately start moving the window, interfering with our own
// click-to-drag behaviour.
setProperty("_kde_no_window_grab", true);
QStyleOptionToolButton buttonOpt;
int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
buttonOpt.initFrom(parentWidget());
buttonOpt.iconSize = QSize(size, size);
buttonOpt.subControls = 0;
buttonOpt.activeSubControls = 0;
buttonOpt.features = QStyleOptionToolButton::None;
buttonOpt.arrowType = Qt::NoArrow;
buttonOpt.state |= QStyle::State_AutoRaise;
// TODO make our own pin icon, that is pinned/unpinned
m_pin.icon = style()->standardIcon(QStyle::SP_TitleBarNormalButton, &buttonOpt, this);
m_close.icon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &buttonOpt, this);
m_pin.hover = m_pin.clicked = false;
m_close.hover = m_close.clicked = false;
}
ToolWindowManagerTabBar::~ToolWindowManagerTabBar() {
}
QSize ToolWindowManagerTabBar::sizeHint() const {
if (count() == 1) {
if (floatingWindowChild())
return QSize(0, 0);
QFontMetrics fm = fontMetrics();
int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this);
int h = qMax(fm.height(), iconSize) + 2*mw;
return QSize(h, h);
}
return QTabBar::sizeHint();
}
QSize ToolWindowManagerTabBar::minimumSizeHint() const {
if (count() == 1) {
return sizeHint();
}
return QTabBar::minimumSizeHint();
}
bool ToolWindowManagerTabBar::inButton(QPoint pos) {
return m_pin.rect.contains(pos) || m_close.rect.contains(pos);
}
void ToolWindowManagerTabBar::paintEvent(QPaintEvent *event) {
if (count() == 1) {
if (floatingWindowChild())
return;
QStylePainter p(this);
QStyleOptionDockWidget option;
option.initFrom(parentWidget());
option.rect = m_titleRect;
option.title = tabText(0);
option.closable = m_tabsClosable;
option.movable = false;
// we only set floatable true so we can hijack the float button for our own pin/auto-hide button
option.floatable = true;
Shape s = shape();
option.verticalTitleBar = s == RoundedEast || s == TriangularEast ||
s == RoundedWest || s == TriangularWest;
p.drawControl(QStyle::CE_DockWidgetTitle, option);
int size = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
QStyleOptionToolButton buttonOpt;
buttonOpt.initFrom(parentWidget());
buttonOpt.iconSize = QSize(size, size);
buttonOpt.subControls = 0;
buttonOpt.activeSubControls = 0;
buttonOpt.features = QStyleOptionToolButton::None;
buttonOpt.arrowType = Qt::NoArrow;
buttonOpt.state = QStyle::State_Active|QStyle::State_Enabled|QStyle::State_AutoRaise;
buttonOpt.rect = m_pin.rect;
buttonOpt.icon = m_pin.icon;
QStyle::State prevState = buttonOpt.state;
if(m_pin.clicked)
buttonOpt.state |= QStyle::State_Sunken;
else if(m_pin.hover)
buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver;
if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) {
style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this);
}
style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this);
if (m_tabsClosable) {
buttonOpt.rect = m_close.rect;
buttonOpt.icon = m_close.icon;
buttonOpt.state = prevState;
if(m_close.clicked)
buttonOpt.state |= QStyle::State_Sunken;
else if(m_close.hover)
buttonOpt.state |= QStyle::State_Raised | QStyle::State_MouseOver;
if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) {
style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this);
}
style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this);
}
return;
}
QTabBar::paintEvent(event);
}
void ToolWindowManagerTabBar::resizeEvent(QResizeEvent *event) {
QTabBar::resizeEvent(event);
if (count() > 1 || floatingWindowChild())
return;
m_titleRect = QRect(0, 0, size().width(), sizeHint().height());
QStyleOptionDockWidget option;
option.initFrom(parentWidget());
option.rect = m_titleRect;
option.closable = m_tabsClosable;
option.movable = false;
// we only set floatable true so we can hijack the float button for our own pin/auto-hide button
option.floatable = true;
m_pin.rect = style()->subElementRect(QStyle::SE_DockWidgetFloatButton, &option, this);
m_close.rect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this);
}
void ToolWindowManagerTabBar::mousePressEvent(QMouseEvent *event) {
QTabBar::mousePressEvent(event);
if (count() > 1 || floatingWindowChild())
return;
ButtonData prevPin = m_pin;
ButtonData prevClose = m_close;
if (m_pin.rect.contains(mapFromGlobal(QCursor::pos())) &&
event->buttons() & Qt::LeftButton) {
m_pin.clicked = true;
} else {
m_pin.clicked = false;
}
if (m_close.rect.contains(mapFromGlobal(QCursor::pos())) &&
event->buttons() & Qt::LeftButton) {
m_close.clicked = true;
} else {
m_close.clicked = false;
}
if (prevPin != m_pin || prevClose != m_close)
update();
event->accept();
}
void ToolWindowManagerTabBar::mouseMoveEvent(QMouseEvent *event) {
QTabBar::mouseMoveEvent(event);
if (count() > 1 || floatingWindowChild())
return;
ButtonData prevPin = m_pin;
ButtonData prevClose = m_close;
if (m_pin.rect.contains(mapFromGlobal(QCursor::pos()))) {
m_pin.hover = true;
if (event->buttons() & Qt::LeftButton)
m_pin.clicked = true;
} else {
m_pin.hover = false;
m_pin.clicked = false;
}
if (m_close.rect.contains(mapFromGlobal(QCursor::pos()))) {
m_close.hover = true;
if (event->buttons() & Qt::LeftButton)
m_close.clicked = true;
} else {
m_close.hover = false;
m_close.clicked = false;
}
if (prevPin != m_pin || prevClose != m_close)
update();
}
void ToolWindowManagerTabBar::mouseReleaseEvent(QMouseEvent *event) {
QTabBar::mouseReleaseEvent(event);
if (count() > 1 || floatingWindowChild())
return;
if (m_pin.rect.contains(mapFromGlobal(QCursor::pos()))) {
// process a pin of these tabs
m_pin.clicked = false;
update();
event->accept();
}
if (m_close.rect.contains(mapFromGlobal(QCursor::pos()))) {
ToolWindowManagerArea *area = qobject_cast<ToolWindowManagerArea *>(parentWidget());
if (area)
area->tabCloseRequested(0);
m_close.clicked = false;
update();
event->accept();
}
}
void ToolWindowManagerTabBar::tabInserted(int) {
updateClosable();
}
void ToolWindowManagerTabBar::tabRemoved(int) {
updateClosable();
}
void ToolWindowManagerTabBar::updateClosable() {
QTabBar::setTabsClosable(m_tabsClosable && count() > 1);
}
bool ToolWindowManagerTabBar::floatingWindowChild() const {
ToolWindowManagerArea *area = qobject_cast<ToolWindowManagerArea *>(parentWidget());
if (area) {
ToolWindowManagerWrapper *wrapper = qobject_cast<ToolWindowManagerWrapper *>(area->parentWidget());
if (wrapper && wrapper->floating())
return true;
}
return false;
}
@@ -0,0 +1,95 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2017 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
#ifndef TOOLWINDOWMANAGERTABBAR_H
#define TOOLWINDOWMANAGERTABBAR_H
#include <QTabBar>
#include <QIcon>
class ToolWindowManager;
/*!
* \brief The ToolWindowManagerArea class is a tab widget used to store tool windows.
* It implements dragging of its tab or the whole tab widget.
*/
class ToolWindowManagerTabBar : public QTabBar {
Q_OBJECT
public:
//! Creates new tab bar.
explicit ToolWindowManagerTabBar(QWidget *parent = 0);
//! Destroys the tab bar.
virtual ~ToolWindowManagerTabBar();
bool tabsClosable() const { return m_tabsClosable; }
void setTabsClosable(bool closable) { m_tabsClosable = closable; updateClosable(); }
//! Reimplemented from QTabWidget::QTabBar to custom size for the single tab case.
QSize sizeHint() const Q_DECL_OVERRIDE;
QSize minimumSizeHint() const Q_DECL_OVERRIDE;
//! is this point in a custom titlebar button
bool inButton(QPoint pos);
protected:
//! Reimplemented from QTabWidget::QTabBar to custom paint for the single tab case.
void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
//! Reimplemented from QTabWidget::QTabBar to cache painting parameters
void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
//! Reimplemented from QTabWidget::QTabBar to implement hover/click status of buttons
void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;
//! Reimplemented from QTabWidget::QTabBar to enable/disable 'real' closable tabs.
virtual void tabInserted(int index) Q_DECL_OVERRIDE;
virtual void tabRemoved(int index) Q_DECL_OVERRIDE;
bool m_tabsClosable;
struct ButtonData
{
QRect rect;
QIcon icon;
bool clicked;
bool hover;
bool operator ==(const ButtonData &o) {
return rect == o.rect && clicked == o.clicked && hover == o.hover;
}
bool operator !=(const ButtonData &o) {
return !(*this == o);
}
} m_close, m_pin;
QRect m_titleRect;
void updateClosable();
bool floatingWindowChild() const;
};
#endif // TOOLWINDOWMANAGERTABBAR_H
@@ -30,23 +30,124 @@
#include <QMimeData>
#include <QDebug>
#include <QApplication>
#include <QSplitter>
#include <QTimer>
#include <QStylePainter>
#include <QStyleOption>
ToolWindowManagerWrapper::ToolWindowManagerWrapper(ToolWindowManager *manager) :
ToolWindowManagerWrapper::ToolWindowManagerWrapper(ToolWindowManager *manager, bool floating) :
QWidget(manager)
, m_manager(manager)
{
setWindowFlags(windowFlags() | Qt::Tool);
Qt::WindowFlags flags = Qt::Tool;
#if defined(Q_OS_WIN32)
flags |= Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint;
#else
flags |= Qt::FramelessWindowHint;
#endif
setMouseTracking(true);
setWindowFlags(flags);
setWindowTitle(QStringLiteral(" "));
m_dragReady = false;
m_dragActive = false;
m_dragDirection = ResizeDirection::Count;
m_floating = floating;
QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setMargin(0);
mainLayout->setSpacing(0);
m_manager->m_wrappers << this;
m_moveTimeout = new QTimer(this);
m_moveTimeout->setInterval(100);
m_moveTimeout->stop();
QObject::connect(m_moveTimeout, &QTimer::timeout, this, &ToolWindowManagerWrapper::moveTimeout);
m_closeButtonSize = 0;
m_frameWidth = 0;
m_titleHeight = 0;
if (floating && (flags & Qt::FramelessWindowHint)) {
m_closeButtonSize = style()->pixelMetric(QStyle::PM_SmallIconSize, 0, this);
QFontMetrics titleFontMetrics = fontMetrics();
int mw = style()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, 0, this);
m_titleHeight = qMax(m_closeButtonSize + 2, titleFontMetrics.height() + 2*mw);
m_frameWidth = style()->pixelMetric(QStyle::PM_DockWidgetFrameWidth, 0, this);
mainLayout->setContentsMargins(QMargins(m_frameWidth+4, m_frameWidth+4 + m_titleHeight,
m_frameWidth+4, m_frameWidth+4));
}
if (floating) {
installEventFilter(this);
updateTitle();
}
}
ToolWindowManagerWrapper::~ToolWindowManagerWrapper() {
m_manager->m_wrappers.removeOne(this);
}
void ToolWindowManagerWrapper::updateTitle() {
if (!m_floating)
return;
// find the best candidate for a 'title' for this floating window.
if (layout()->count() > 0) {
QWidget *child = layout()->itemAt(0)->widget();
while (child) {
// if we've found an area, use its currently selected tab's text
if (ToolWindowManagerArea* area = qobject_cast<ToolWindowManagerArea*>(child)) {
setWindowTitle(area->tabText(area->currentIndex()));
return;
}
// otherwise we should have a splitter
if (QSplitter* splitter = qobject_cast<QSplitter*>(child)) {
// if it's empty, just bail
if (splitter->count() == 0)
break;
// if it's vertical, we pick the first child and recurse
if (splitter->orientation() == Qt::Vertical) {
child = splitter->widget(0);
continue;
}
// if it's horizontal there's ambiguity so we just pick the biggest one by size, with a
// tie-break for the leftmost one
QList<int> sizes = splitter->sizes();
int maxIdx = 0;
int maxSize = sizes[0];
for (int i=1; i < sizes.count(); i++) {
if (sizes[i] > maxSize) {
maxSize = sizes[i];
maxIdx = i;
}
}
child = splitter->widget(maxIdx);
continue;
}
// if not, use this object's window title
setWindowTitle(child->windowTitle());
return;
}
}
setWindowTitle(QStringLiteral("Tool Window"));
}
void ToolWindowManagerWrapper::closeEvent(QCloseEvent *) {
QList<QWidget*> toolWindows;
foreach(ToolWindowManagerArea* tabWidget, findChildren<ToolWindowManagerArea*>()) {
@@ -55,8 +156,226 @@ void ToolWindowManagerWrapper::closeEvent(QCloseEvent *) {
m_manager->moveToolWindows(toolWindows, ToolWindowManager::NoArea);
}
bool ToolWindowManagerWrapper::eventFilter(QObject *object, QEvent *event) {
const Qt::CursorShape shapes[(int)ResizeDirection::Count] = {
Qt::SizeFDiagCursor,
Qt::SizeBDiagCursor,
Qt::SizeBDiagCursor,
Qt::SizeFDiagCursor,
Qt::SizeVerCursor,
Qt::SizeHorCursor,
Qt::SizeVerCursor,
Qt::SizeHorCursor,
};
if (object == this) {
if (event->type() == QEvent::MouseButtonRelease ||
event->type() == QEvent::NonClientAreaMouseButtonRelease) {
m_dragReady = false;
m_dragDirection = ResizeDirection::Count;
if (!m_dragActive && m_closeRect.contains(mapFromGlobal(QCursor::pos()))) {
// catch clicks on the close button
close();
} else {
// if the mouse button is released, let the manager finish the drag and don't call any more
// updates for any further move events
m_dragActive = false;
m_manager->updateDragPosition();
}
} else if (event->type() == QEvent::MouseMove ||
event->type() == QEvent::NonClientAreaMouseMove) {
// if we're ready to start a drag, check how far we've moved and start the drag if past a
// certain pixel threshold.
if (m_dragReady) {
if ((QCursor::pos() - m_dragStartCursor).manhattanLength() > 10) {
m_dragActive = true;
m_dragReady = false;
QList<QWidget*> toolWindows;
foreach(ToolWindowManagerArea* tabWidget, findChildren<ToolWindowManagerArea*>()) {
toolWindows << tabWidget->toolWindows();
}
m_manager->startDrag(toolWindows, this);
}
}
// if the drag is active, update it in the manager.
if (m_dragActive) {
m_manager->updateDragPosition();
// on non-windows we have no native title bar, so we need to move the window ourselves
#if !defined(Q_OS_WIN32)
move(QCursor::pos() - (m_dragStartCursor - m_dragStartGeometry.topLeft()));
#endif
}
if (titleRect().contains(mapFromGlobal(QCursor::pos()))) {
// if we're in the title bar, repaint to pick up motion over the close button
update();
}
ResizeDirection dir = checkResize();
if (m_dragDirection != ResizeDirection::Count) {
dir = m_dragDirection;
QRect g = geometry();
switch (dir) {
case ResizeDirection::NW:
g.setTopLeft(QCursor::pos());
break;
case ResizeDirection::NE:
g.setTopRight(QCursor::pos());
break;
case ResizeDirection::SW:
g.setBottomLeft(QCursor::pos());
break;
case ResizeDirection::SE:
g.setBottomRight(QCursor::pos());
break;
case ResizeDirection::N:
g.setTop(QCursor::pos().y());
break;
case ResizeDirection::E:
g.setRight(QCursor::pos().x());
break;
case ResizeDirection::S:
g.setBottom(QCursor::pos().y());
break;
case ResizeDirection::W:
g.setLeft(QCursor::pos().x());
break;
case ResizeDirection::Count:
break;
}
setGeometry(g);
}
if (dir != ResizeDirection::Count) {
setCursor(shapes[(int)dir]);
QObjectList children = this->children();
for (int i = 0; i < children.size(); ++i) {
if (QWidget *w = qobject_cast<QWidget*>(children.at(i))) {
if (!w->testAttribute(Qt::WA_SetCursor)) {
w->setCursor(Qt::ArrowCursor);
}
}
}
} else {
unsetCursor();
}
} else if (event->type() == QEvent::MouseButtonPress) {
ResizeDirection dir = checkResize();
m_dragStartCursor = QCursor::pos();
m_dragStartGeometry = geometry();
if (dir == ResizeDirection::Count)
m_dragReady = true;
else
m_dragDirection = dir;
} else if (event->type() == QEvent::NonClientAreaMouseButtonPress) {
m_dragActive = true;
m_dragReady = false;
m_dragStartCursor = QCursor::pos();
m_dragStartGeometry = geometry();
QList<QWidget*> toolWindows;
foreach(ToolWindowManagerArea* tabWidget, findChildren<ToolWindowManagerArea*>()) {
toolWindows << tabWidget->toolWindows();
}
m_manager->startDrag(toolWindows, this);
} else if (event->type() == QEvent::Move && m_dragActive) {
m_manager->updateDragPosition();
m_moveTimeout->start();
} else if (event->type() == QEvent::Leave) {
unsetCursor();
} else if (event->type() == QEvent::MouseButtonDblClick &&
titleRect().contains(mapFromGlobal(QCursor::pos()))) {
if (isMaximized()) {
showNormal();
} else {
showMaximized();
}
}
}
return QWidget::eventFilter(object, event);
}
void ToolWindowManagerWrapper::paintEvent(QPaintEvent *) {
if (!m_floating || m_titleHeight == 0)
return;
{
QStylePainter p(this);
QStyleOptionFrame frameOptions;
frameOptions.init(this);
p.drawPrimitive(QStyle::PE_FrameDockWidget, frameOptions);
// Title must be painted after the frame, since the areas overlap, and
// the title may wish to extend out to all sides (eg. XP style)
QStyleOptionDockWidget titlebarOptions;
titlebarOptions.initFrom(this);
titlebarOptions.rect = titleRect();
titlebarOptions.title = windowTitle();
titlebarOptions.closable = true;
titlebarOptions.movable = true;
titlebarOptions.floatable = false;
titlebarOptions.verticalTitleBar = false;
p.drawControl(QStyle::CE_DockWidgetTitle, titlebarOptions);
QStyleOptionToolButton buttonOpt;
buttonOpt.initFrom(this);
buttonOpt.iconSize = QSize(m_closeButtonSize, m_closeButtonSize);
buttonOpt.subControls = 0;
buttonOpt.activeSubControls = 0;
buttonOpt.features = QStyleOptionToolButton::None;
buttonOpt.arrowType = Qt::NoArrow;
buttonOpt.state = QStyle::State_Active|QStyle::State_Enabled|QStyle::State_AutoRaise;
if (m_closeRect.contains(mapFromGlobal(QCursor::pos()))) {
buttonOpt.state |= QStyle::State_MouseOver|QStyle::State_Raised;
}
buttonOpt.rect = m_closeRect;
buttonOpt.icon = m_closeIcon;
if (style()->styleHint(QStyle::SH_DockWidget_ButtonsHaveFrame, 0, this)) {
style()->drawPrimitive(QStyle::PE_PanelButtonTool, &buttonOpt, &p, this);
}
style()->drawComplexControl(QStyle::CC_ToolButton, &buttonOpt, &p, this);
}
}
void ToolWindowManagerWrapper::resizeEvent(QResizeEvent *)
{
QStyleOptionDockWidget option;
option.initFrom(this);
option.rect = titleRect();
option.closable = true;
option.movable = true;
option.floatable = true;
m_closeRect = style()->subElementRect(QStyle::SE_DockWidgetCloseButton, &option, this);
m_closeIcon = style()->standardIcon(QStyle::SP_TitleBarCloseButton, &option, this);
}
QRect ToolWindowManagerWrapper::titleRect()
{
QRect ret;
ret.setTopLeft(QPoint(m_frameWidth, m_frameWidth));
ret.setSize(QSize(geometry().width() - (m_frameWidth * 2), m_titleHeight));
return ret;
}
QVariantMap ToolWindowManagerWrapper::saveState() {
if (layout()->count() > 1) {
if (layout()->count() > 2) {
qWarning("too many children for wrapper");
return QVariantMap();
}
@@ -83,7 +402,7 @@ QVariantMap ToolWindowManagerWrapper::saveState() {
void ToolWindowManagerWrapper::restoreState(const QVariantMap &savedData) {
restoreGeometry(QByteArray::fromBase64(savedData[QStringLiteral("geometry")].toByteArray()));
if (layout()->count() > 0) {
if (layout()->count() > 1) {
qWarning("wrapper is not empty");
return;
}
@@ -95,3 +414,48 @@ void ToolWindowManagerWrapper::restoreState(const QVariantMap &savedData) {
layout()->addWidget(area);
}
}
void ToolWindowManagerWrapper::moveTimeout() {
m_manager->updateDragPosition();
if (!m_manager->dragInProgress()) {
m_moveTimeout->stop();
}
}
ToolWindowManagerWrapper::ResizeDirection ToolWindowManagerWrapper::checkResize() {
if (m_titleHeight == 0)
return ResizeDirection::Count;
// check if we should offer to resize
QRect rect = this->rect();
QPoint testPos = mapFromGlobal(QCursor::pos());
if (m_closeRect.contains(testPos))
return ResizeDirection::Count;
const int resizeMargin = 4;
if (rect.contains(testPos)) {
// check corners first, then horizontal/vertical
if (testPos.x() < rect.x() + resizeMargin*4 && testPos.y() < rect.y() + resizeMargin*4) {
return ResizeDirection::NW;
} else if (testPos.x() > rect.width() - resizeMargin*4 && testPos.y() < rect.y() + resizeMargin*4) {
return ResizeDirection::NE;
} else if (testPos.x() < rect.x() + resizeMargin*4 && testPos.y() > rect.height() - resizeMargin*4) {
return ResizeDirection::SW;
} else if (testPos.x() > rect.width() - resizeMargin*4 && testPos.y() > rect.height() - resizeMargin*4) {
return ResizeDirection::SE;
} else if (testPos.x() < rect.x() + resizeMargin) {
return ResizeDirection::W;
} else if (testPos.x() > rect.width() - resizeMargin) {
return ResizeDirection::E;
} else if (testPos.y() < rect.y() + resizeMargin) {
return ResizeDirection::N;
} else if (testPos.y() > rect.height() - resizeMargin) {
return ResizeDirection::S;
}
}
return ResizeDirection::Count;
}
@@ -25,10 +25,12 @@
#ifndef TOOLWINDOWMANAGERWRAPPER_H
#define TOOLWINDOWMANAGERWRAPPER_H
#include <QIcon>
#include <QWidget>
#include <QVariantMap>
class ToolWindowManager;
class QLabel;
/*!
* \brief The ToolWindowManagerWrapper class is used by ToolWindowManager to wrap its content.
@@ -40,19 +42,60 @@ class ToolWindowManagerWrapper : public QWidget {
Q_OBJECT
public:
//! Creates new wrapper.
explicit ToolWindowManagerWrapper(ToolWindowManager* manager);
explicit ToolWindowManagerWrapper(ToolWindowManager* manager, bool floating);
//! Removes the wrapper.
virtual ~ToolWindowManagerWrapper();
ToolWindowManager* manager() { return m_manager; }
bool floating() { return m_floating; }
void updateTitle();
protected:
//! Reimplemented to register hiding of contained tool windows when user closes the floating window.
virtual void closeEvent(QCloseEvent *);
virtual void closeEvent(QCloseEvent *) Q_DECL_OVERRIDE;
//! Event filter for grabbing and processing mouse drags as toolwindow drags.
virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE;
//! Painting and resizing for custom-rendered widget frames
virtual void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE;
virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE;
private:
ToolWindowManager* m_manager;
enum class ResizeDirection {
NW,
NE,
SW,
SE,
N,
E,
S,
W,
Count,
};
QRect titleRect();
ResizeDirection checkResize();
QRect m_closeRect;
QIcon m_closeIcon;
int m_closeButtonSize;
int m_titleHeight;
int m_frameWidth;
bool m_floating;
QTimer* m_moveTimeout;
bool m_dragReady; // we've clicked and started moving but haven't moved enough yet
QPoint m_dragStartCursor; // cursor at the click to start a drag
QRect m_dragStartGeometry; // window geometry at the click to start a drag
bool m_dragActive; // whether a drag currently on-going
ResizeDirection m_dragDirection; // the current direction being dragged
//dump content's layout to variable
QVariantMap saveState();
@@ -61,6 +104,8 @@ private:
friend class ToolWindowManager;
private slots:
void moveTimeout();
};
#endif // TOOLWINDOWMANAGERWRAPPER_H