diff --git a/qrenderdoc/3rdparty/toolwindowmanager/README.md b/qrenderdoc/3rdparty/toolwindowmanager/README.md index 82e4b08d8..f70ebad28 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/README.md +++ b/qrenderdoc/3rdparty/toolwindowmanager/README.md @@ -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) diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.cpp b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.cpp index 5d09c06fd..da935608c 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.cpp +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.cpp @@ -36,6 +36,8 @@ #include #include #include +#include +#include template T findClosestParent(QWidget* widget) { @@ -51,30 +53,71 @@ T findClosestParent(QWidget* widget) { ToolWindowManager::ToolWindowManager(QWidget *parent) : QWidget(parent) { - m_borderSensitivity = 12; - QSplitter* testSplitter = new QSplitter(); - m_rubberBandLineWidth = testSplitter->handleWidth(); - delete testSplitter; - m_dragIndicator = new QLabel(0, Qt::ToolTip ); - m_dragIndicator->setAttribute(Qt::WA_ShowWithoutActivating); QVBoxLayout* mainLayout = new QVBoxLayout(this); mainLayout->setContentsMargins(0, 0, 0, 0); - ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this); + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, false); wrapper->setWindowFlags(wrapper->windowFlags() & ~Qt::Tool); mainLayout->addWidget(wrapper); - connect(&m_dropSuggestionSwitchTimer, SIGNAL(timeout()), - this, SLOT(showNextDropSuggestion())); - m_dropSuggestionSwitchTimer.setInterval(1000); - m_dropCurrentSuggestionIndex = 0; m_allowFloatingWindow = true; m_createCallback = NULL; m_lastUsedArea = NULL; - m_rectRubberBand = new QRubberBand(QRubberBand::Rectangle, this); - m_lineRubberBand = new QRubberBand(QRubberBand::Line, this); + m_draggedWrapper = NULL; + m_hoverArea = NULL; + + QPalette pal = palette(); + pal.setColor(QPalette::Background, pal.color(QPalette::Highlight)); + + m_previewOverlay = new QWidget(NULL); + m_previewOverlay->setAutoFillBackground(true); + m_previewOverlay->setPalette(pal); + m_previewOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_previewOverlay->setWindowOpacity(0.3); + m_previewOverlay->setAttribute(Qt::WA_ShowWithoutActivating); + m_previewOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); + m_previewOverlay->hide(); + + m_previewTabOverlay = new QWidget(NULL); + m_previewTabOverlay->setAutoFillBackground(true); + m_previewTabOverlay->setPalette(pal); + m_previewTabOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_previewTabOverlay->setWindowOpacity(0.3); + m_previewTabOverlay->setAttribute(Qt::WA_ShowWithoutActivating); + m_previewTabOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); + m_previewTabOverlay->hide(); + + m_dropHotspotsOverlay = new QWidget(NULL); + m_dropHotspotsOverlay->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint); + m_dropHotspotsOverlay->setAttribute(Qt::WA_NoSystemBackground); + m_dropHotspotsOverlay->setAttribute(Qt::WA_TranslucentBackground); + m_dropHotspotsOverlay->setAttribute(Qt::WA_TransparentForMouseEvents); + m_dropHotspotsOverlay->setAttribute(Qt::WA_ShowWithoutActivating); + m_dropHotspotsOverlay->setAttribute(Qt::WA_AlwaysStackOnTop); + m_dropHotspotsOverlay->hide(); + + for (int i=0; i < NumReferenceTypes; i++) + m_dropHotspots[i] = NULL; + + m_dropHotspotDimension = 32; + m_dropHotspotMargin = 4; + + drawHotspotPixmaps(); + + for (AreaReferenceType type : { AddTo, + TopOf, LeftOf, + RightOf, BottomOf, + TopWindowSide, LeftWindowSide, + RightWindowSide, BottomWindowSide }) { + m_dropHotspots[type] = new QLabel(m_dropHotspotsOverlay); + m_dropHotspots[type]->setPixmap(m_pixmaps[type]); + m_dropHotspots[type]->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); + } } ToolWindowManager::~ToolWindowManager() { + delete m_previewOverlay; + delete m_previewTabOverlay; + delete m_dropHotspotsOverlay; while(!m_areas.isEmpty()) { delete m_areas.first(); } @@ -121,20 +164,28 @@ ToolWindowManagerArea *ToolWindowManager::areaOf(QWidget *toolWindow) { return findClosestParent(toolWindow); } +ToolWindowManagerWrapper *ToolWindowManager::wrapperOf(QWidget *toolWindow) { + return findClosestParent(toolWindow); +} + void ToolWindowManager::moveToolWindow(QWidget *toolWindow, AreaReference area) { moveToolWindows(QList() << toolWindow, area); } void ToolWindowManager::moveToolWindows(QList toolWindows, ToolWindowManager::AreaReference area) { + QList wrappersToUpdate; foreach(QWidget* toolWindow, toolWindows) { if (!m_toolWindows.contains(toolWindow)) { qWarning("unknown tool window"); return; } + ToolWindowManagerWrapper *oldWrapper = wrapperOf(toolWindow); if (toolWindow->parentWidget() != 0) { releaseToolWindow(toolWindow); } + if (oldWrapper && !wrappersToUpdate.contains(oldWrapper)) + wrappersToUpdate.push_back(oldWrapper); } if (area.type() == LastUsedArea && !m_lastUsedArea) { ToolWindowManagerArea* foundArea = findChild(); @@ -150,12 +201,55 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, } else if (area.type() == NewFloatingArea) { ToolWindowManagerArea* floatArea = createArea(); floatArea->addToolWindows(toolWindows); - ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this); + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, true); wrapper->layout()->addWidget(floatArea); wrapper->move(QCursor::pos()); + wrapper->updateTitle(); wrapper->show(); } else if (area.type() == AddTo) { - area.area()->addToolWindows(toolWindows); + int idx = -1; + if (area.dragResult) { + idx = area.area()->tabBar()->tabAt(area.area()->tabBar()->mapFromGlobal(QCursor::pos())); + } + area.area()->addToolWindows(toolWindows, idx); + } else if (area.type() == LeftWindowSide || area.type() == RightWindowSide || + area.type() == TopWindowSide || area.type() == BottomWindowSide) { + ToolWindowManagerWrapper* wrapper = findClosestParent(area.area()); + if (!wrapper) { + qWarning("couldn't find wrapper"); + return; + } + + if (wrapper->layout()->count() > 1) + { + qWarning("wrapper has multiple direct children"); + return; + } + + QLayoutItem* item = wrapper->layout()->takeAt(0); + + QSplitter* splitter = createSplitter(); + if (area.type() == TopWindowSide || area.type() == BottomWindowSide) { + splitter->setOrientation(Qt::Vertical); + } else { + splitter->setOrientation(Qt::Horizontal); + } + + splitter->addWidget(item->widget()); + area.widget()->show(); + + delete item; + + ToolWindowManagerArea* newArea = createArea(); + newArea->addToolWindows(toolWindows); + + if (area.type() == TopWindowSide || area.type() == LeftWindowSide) { + splitter->insertWidget(0, newArea); + } else { + splitter->addWidget(newArea); + } + + wrapper->layout()->addWidget(splitter); } else if (area.type() == LeftOf || area.type() == RightOf || area.type() == TopOf || area.type() == BottomOf) { QSplitter* parentSplitter = qobject_cast(area.widget()->parentWidget()); @@ -166,14 +260,10 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, } bool useParentSplitter = false; int indexInParentSplitter = 0; - QList parentSplitterGeometries; QList parentSplitterSizes; if (parentSplitter) { indexInParentSplitter = parentSplitter->indexOf(area.widget()); parentSplitterSizes = parentSplitter->sizes(); - for(int i = 0; i < parentSplitter->count(); i++) { - parentSplitterGeometries.push_back(parentSplitter->widget(i)->geometry()); - } if (parentSplitter->orientation() == Qt::Vertical) { useParentSplitter = area.type() == TopOf || area.type() == BottomOf; } else { @@ -181,15 +271,19 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, } } if (useParentSplitter) { + int insertIndex = indexInParentSplitter; if (area.type() == BottomOf || area.type() == RightOf) { - indexInParentSplitter++; + insertIndex++; } ToolWindowManagerArea* newArea = createArea(); newArea->addToolWindows(toolWindows); - parentSplitter->insertWidget(indexInParentSplitter, newArea); + parentSplitter->insertWidget(insertIndex, newArea); - for(int i = 0; i < qMin(parentSplitter->count(), parentSplitterGeometries.count()); i++) { - parentSplitter->widget(i)->setGeometry(parentSplitterGeometries[i]); + if(parentSplitterSizes.count() > indexInParentSplitter && parentSplitterSizes[0] != 0) { + parentSplitterSizes[indexInParentSplitter] /= 2; + parentSplitterSizes.insert(indexInParentSplitter, parentSplitterSizes[indexInParentSplitter]); + + parentSplitter->setSizes(parentSplitterSizes); } } else { area.widget()->hide(); @@ -223,19 +317,9 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, indexInSplitter = 1; } - // Convert area percentage desired to a stretch factor. - const int totalStretch = 100; - int pct = int(totalStretch*area.percentage()); - splitter->setStretchFactor(indexInSplitter, pct); - splitter->setStretchFactor(1-indexInSplitter, totalStretch-pct); - if (parentSplitter) { parentSplitter->insertWidget(indexInParentSplitter, splitter); - for (int i = 0; i < qMin(parentSplitter->count(), parentSplitterGeometries.count()); i++) { - parentSplitter->widget(i)->setGeometry(parentSplitterGeometries[i]); - } - if (parentSplitterSizes.count() > 0 && parentSplitterSizes[0] != 0) { parentSplitter->setSizes(parentSplitterSizes); } @@ -248,6 +332,12 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, area.widget()->setGeometry(areaGeometry); newArea->setGeometry(newGeometry); + + // Convert area percentage desired to relative sizes. + const int totalStretch = 100; + int pct = int(totalStretch*area.percentage()); + + splitter->setSizes({pct, totalStretch-pct}); } } else if (area.type() == EmptySpace) { ToolWindowManagerArea* newArea = createArea(); @@ -261,6 +351,12 @@ void ToolWindowManager::moveToolWindows(QList toolWindows, simplifyLayout(); foreach(QWidget* toolWindow, toolWindows) { emit toolWindowVisibilityChanged(toolWindow, toolWindow->parent() != 0); + ToolWindowManagerWrapper* wrapper = wrapperOf(toolWindow); + if (wrapper && !wrappersToUpdate.contains(wrapper)) + wrappersToUpdate.push_back(wrapper); + } + foreach(ToolWindowManagerWrapper* wrapper, wrappersToUpdate) { + wrapper->updateTitle(); } } @@ -336,20 +432,18 @@ QWidget* ToolWindowManager::createToolWindow(const QString& objectName) return NULL; } -void ToolWindowManager::setSuggestionSwitchInterval(int msec) { - m_dropSuggestionSwitchTimer.setInterval(msec); +void ToolWindowManager::setDropHotspotMargin(int pixels) { + m_dropHotspotMargin = pixels; + drawHotspotPixmaps(); } -int ToolWindowManager::suggestionSwitchInterval() { - return m_dropSuggestionSwitchTimer.interval(); -} +void ToolWindowManager::setDropHotspotDimension(int pixels) { + m_dropHotspotDimension = pixels; -void ToolWindowManager::setBorderSensitivity(int pixels) { - m_borderSensitivity = pixels; -} - -void ToolWindowManager::setRubberBandLineWidth(int pixels) { - m_rubberBandLineWidth = pixels; + for (QLabel *hotspot : m_dropHotspots) { + if(hotspot) + hotspot->setFixedSize(m_dropHotspotDimension, m_dropHotspotDimension); + } } void ToolWindowManager::setAllowFloatingWindow(bool allow) { @@ -389,8 +483,9 @@ void ToolWindowManager::restoreState(const QVariantMap &dataMap) { mainWrapper->restoreState(dataMap[QStringLiteral("mainWrapper")].toMap()); QVariantList floatWins = dataMap[QStringLiteral("floatingWindows")].toList(); foreach(QVariant windowData, floatWins) { - ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this); + ToolWindowManagerWrapper* wrapper = new ToolWindowManagerWrapper(this, true); wrapper->restoreState(windowData.toMap()); + wrapper->updateTitle(); wrapper->show(); if(wrapper->windowState() && Qt::WindowMaximized) { @@ -411,19 +506,6 @@ ToolWindowManagerArea *ToolWindowManager::createArea() { return area; } - -void ToolWindowManager::handleNoSuggestions() { - m_rectRubberBand->hide(); - m_lineRubberBand->hide(); - m_lineRubberBand->setParent(this); - m_rectRubberBand->setParent(this); - m_suggestions.clear(); - m_dropCurrentSuggestionIndex = 0; - if (m_dropSuggestionSwitchTimer.isActive()) { - m_dropSuggestionSwitchTimer.stop(); - } -} - void ToolWindowManager::releaseToolWindow(QWidget *toolWindow) { ToolWindowManagerArea* previousTabWidget = findClosestParent(toolWindow); if (!previousTabWidget) { @@ -496,7 +578,8 @@ void ToolWindowManager::simplifyLayout() { } } -void ToolWindowManager::startDrag(const QList &toolWindows) { +void ToolWindowManager::startDrag(const QList &toolWindows, + ToolWindowManagerWrapper *wrapper) { if (dragInProgress()) { qWarning("ToolWindowManager::execDrag: drag is already in progress"); return; @@ -505,10 +588,11 @@ void ToolWindowManager::startDrag(const QList &toolWindows) { if(toolWindowProperties(toolWindow) & DisallowUserDocking) { return; } } if (toolWindows.isEmpty()) { return; } + + m_draggedWrapper = wrapper; m_draggedToolWindows = toolWindows; - m_dragIndicator->setPixmap(generateDragPixmap(toolWindows)); updateDragPosition(); - m_dragIndicator->show(); + qApp->installEventFilter(this); } QVariantMap ToolWindowManager::saveSplitterState(QSplitter *splitter) { @@ -560,220 +644,6 @@ QSplitter *ToolWindowManager::restoreSplitterState(const QVariantMap &savedData) return splitter; } -QPixmap ToolWindowManager::generateDragPixmap(const QList &toolWindows) { - QTabBar widget; - widget.setDocumentMode(true); - foreach(QWidget* toolWindow, toolWindows) { - widget.addTab(toolWindow->windowIcon(), toolWindow->windowTitle()); - } -#if QT_VERSION >= 0x050000 // Qt5 - return widget.grab(); -#else //Qt4 - return QPixmap::grabWidget(&widget); -#endif -} - -void ToolWindowManager::showNextDropSuggestion() { - if (m_suggestions.isEmpty()) { - qWarning("showNextDropSuggestion called but no suggestions"); - return; - } - m_dropCurrentSuggestionIndex++; - if (m_dropCurrentSuggestionIndex >= m_suggestions.count()) { - m_dropCurrentSuggestionIndex = 0; - } - const AreaReference& suggestion = m_suggestions[m_dropCurrentSuggestionIndex]; - if (suggestion.type() == AddTo || suggestion.type() == EmptySpace) { - QWidget* widget; - if (suggestion.type() == EmptySpace) { - widget = findChild(); - } else { - widget = suggestion.widget(); - } - QWidget* placeHolderParent; - if (widget->topLevelWidget() == topLevelWidget()) { - placeHolderParent = this; - } else { - placeHolderParent = widget->topLevelWidget(); - } - QRect placeHolderGeometry = widget->rect(); - placeHolderGeometry.moveTopLeft(widget->mapTo(placeHolderParent, - placeHolderGeometry.topLeft())); - m_rectRubberBand->setGeometry(placeHolderGeometry); - m_rectRubberBand->setParent(placeHolderParent); - m_rectRubberBand->show(); - m_lineRubberBand->hide(); - } else if (suggestion.type() == LeftOf || suggestion.type() == RightOf || - suggestion.type() == TopOf || suggestion.type() == BottomOf) { - QWidget* placeHolderParent; - if (suggestion.widget()->topLevelWidget() == topLevelWidget()) { - placeHolderParent = this; - } else { - placeHolderParent = suggestion.widget()->topLevelWidget(); - } - QRect placeHolderGeometry = sidePlaceHolderRect(suggestion.widget(), suggestion.type()); - placeHolderGeometry.moveTopLeft(suggestion.widget()->mapTo(placeHolderParent, - placeHolderGeometry.topLeft())); - - m_lineRubberBand->setGeometry(placeHolderGeometry); - m_lineRubberBand->setParent(placeHolderParent); - m_lineRubberBand->show(); - m_rectRubberBand->hide(); - } else { - qWarning("unsupported suggestion type"); - } -} - -void ToolWindowManager::findSuggestions(ToolWindowManagerWrapper* wrapper) { - m_suggestions.clear(); - m_dropCurrentSuggestionIndex = -1; - QPoint globalPos = QCursor::pos(); - QList candidates; - foreach(QSplitter* splitter, wrapper->findChildren()) { - // make sure this is one of our layout splitters, not a proper widget or a splitter - // from another manager. We walk the parents, expecting either a QSplitter or QTabWidget - // at each time until we reach an area. - - QWidget *w = splitter; - - bool valid = false; - - while(w) - { - QWidget *parent = w->parentWidget(); - - QSplitter *parentSplitter = qobject_cast(parent); - QTabWidget *parentTab = qobject_cast(parent); - - // keep recursing up what looks like our hierarchy - if(parentSplitter || parentTab) - { - w = parent; - continue; - } - - ToolWindowManagerArea* areaParent = qobject_cast(parent); - ToolWindowManagerWrapper* wrapperParent = qobject_cast(parent); - - // if it's an area or wrapper, check if it's ours - if(areaParent) - valid = areaParent->manager() == this; - else if(wrapperParent) - valid = wrapperParent->manager() == this; - - // we're done now, whether we checked for validity, or if we - // found something that's none of the above - break; - } - - if(valid) - candidates << splitter; - } - foreach(ToolWindowManagerArea* area, m_areas) { - if (area->topLevelWidget() == wrapper->topLevelWidget()) { - candidates << area; - } - } - for(QWidget* widget : candidates) { - QSplitter* splitter = qobject_cast(widget); - ToolWindowManagerArea* area = qobject_cast(widget); - if (!splitter && !area) { - qWarning("unexpected widget type"); - continue; - } - QSplitter* parentSplitter = qobject_cast(widget->parentWidget()); - bool lastInSplitter = parentSplitter && - parentSplitter->indexOf(widget) == parentSplitter->count() - 1; - - QList allowedSides; - if (!splitter || splitter->orientation() == Qt::Vertical) { - allowedSides << LeftOf; - } - if (!splitter || splitter->orientation() == Qt::Horizontal) { - allowedSides << TopOf; - } - if (!parentSplitter || parentSplitter->orientation() == Qt::Vertical || lastInSplitter) { - if (!splitter || splitter->orientation() == Qt::Vertical) { - allowedSides << RightOf; - } - } - if (!parentSplitter || parentSplitter->orientation() == Qt::Horizontal || lastInSplitter) { - if (!splitter || splitter->orientation() == Qt::Horizontal) { - allowedSides << BottomOf; - } - } - for(AreaReferenceType side : allowedSides) { - if (sideSensitiveArea(widget, side).contains(widget->mapFromGlobal(globalPos))) { - m_suggestions << AreaReference(side, widget); - } - } - if (area && area->allowUserDrop() && area->rect().contains(area->mapFromGlobal(globalPos))) { - m_suggestions << AreaReference(AddTo, area); - } - } - if (candidates.isEmpty()) { - m_suggestions << EmptySpace; - } - - if (m_suggestions.isEmpty()) { - handleNoSuggestions(); - } else { - showNextDropSuggestion(); - } -} - -QRect ToolWindowManager::sideSensitiveArea(QWidget *widget, ToolWindowManager::AreaReferenceType side) { - QRect widgetRect = widget->rect(); - if (side == TopOf) { - return QRect(QPoint(widgetRect.left(), widgetRect.top() - m_borderSensitivity), - QSize(widgetRect.width(), m_borderSensitivity * 2)); - } else if (side == LeftOf) { - return QRect(QPoint(widgetRect.left() - m_borderSensitivity, widgetRect.top()), - QSize(m_borderSensitivity * 2, widgetRect.height())); - - } else if (side == BottomOf) { - return QRect(QPoint(widgetRect.left(), widgetRect.top() + widgetRect.height() - m_borderSensitivity), - QSize(widgetRect.width(), m_borderSensitivity * 2)); - } else if (side == RightOf) { - return QRect(QPoint(widgetRect.left() + widgetRect.width() - m_borderSensitivity, widgetRect.top()), - QSize(m_borderSensitivity * 2, widgetRect.height())); - } else { - qWarning("invalid side"); - return QRect(); - } -} - -QRect ToolWindowManager::sidePlaceHolderRect(QWidget *widget, ToolWindowManager::AreaReferenceType side) { - QRect widgetRect = widget->rect(); - QSplitter* parentSplitter = qobject_cast(widget->parentWidget()); - if (parentSplitter && parentSplitter->indexOf(widget) > 0) { - int delta = parentSplitter->handleWidth() / 2 + m_rubberBandLineWidth / 2; - if (side == TopOf && parentSplitter->orientation() == Qt::Vertical) { - return QRect(QPoint(widgetRect.left(), widgetRect.top() - delta), - QSize(widgetRect.width(), m_rubberBandLineWidth)); - } else if (side == LeftOf && parentSplitter->orientation() == Qt::Horizontal) { - return QRect(QPoint(widgetRect.left() - delta, widgetRect.top()), - QSize(m_rubberBandLineWidth, widgetRect.height())); - } - } - if (side == TopOf) { - return QRect(QPoint(widgetRect.left(), widgetRect.top()), - QSize(widgetRect.width(), m_rubberBandLineWidth)); - } else if (side == LeftOf) { - return QRect(QPoint(widgetRect.left(), widgetRect.top()), - QSize(m_rubberBandLineWidth, widgetRect.height())); - } else if (side == BottomOf) { - return QRect(QPoint(widgetRect.left(), widgetRect.top() + widgetRect.height() - m_rubberBandLineWidth), - QSize(widgetRect.width(), m_rubberBandLineWidth)); - } else if (side == RightOf) { - return QRect(QPoint(widgetRect.left() + widgetRect.width() - m_rubberBandLineWidth, widgetRect.top()), - QSize(m_rubberBandLineWidth, widgetRect.height())); - } else { - qWarning("invalid side"); - return QRect(); - } -} - void ToolWindowManager::updateDragPosition() { if (!dragInProgress()) { return; } if (!(qApp->mouseButtons() & Qt::LeftButton)) { @@ -782,29 +652,292 @@ void ToolWindowManager::updateDragPosition() { } QPoint pos = QCursor::pos(); - m_dragIndicator->move(pos + QPoint(1, 1)); - bool foundWrapper = false; + m_hoverArea = NULL; + ToolWindowManagerWrapper* hoverWrapper = NULL; - QWidget* window = qApp->topLevelAt(pos); - foreach(ToolWindowManagerWrapper* wrapper, m_wrappers) { - if (wrapper->window() == window) { - if (wrapper->rect().contains(wrapper->mapFromGlobal(pos))) { - findSuggestions(wrapper); - if (!m_suggestions.isEmpty()) { - //starting or restarting timer - if (m_dropSuggestionSwitchTimer.isActive()) { - m_dropSuggestionSwitchTimer.stop(); - } - m_dropSuggestionSwitchTimer.start(); - foundWrapper = true; - } - } + foreach(ToolWindowManagerArea* area, m_areas) { + // don't allow dragging a whole wrapper into a subset of itself + if (m_draggedWrapper && area->window() == m_draggedWrapper->window()) { + continue; + } + if (area->rect().contains(area->mapFromGlobal(pos))) { + m_hoverArea = area; break; } } - if (!foundWrapper) { - handleNoSuggestions(); + + if (m_hoverArea == NULL) { + foreach(ToolWindowManagerWrapper* wrapper, m_wrappers) { + // don't allow dragging a whole wrapper into a subset of itself + if (wrapper == m_draggedWrapper) { + continue; + } + if (wrapper->rect().contains(wrapper->mapFromGlobal(pos))) { + hoverWrapper = wrapper; + break; + } + } + + // if we found a wrapper and it's not empty, then we fill into a gap between two areas in a + // splitter. Search down the hierarchy until we find a splitter whose handle intersects the + // cursor and pick an area to map to. + if (hoverWrapper) { + QSplitter* splitter = qobject_cast(hoverWrapper->layout()->itemAt(0)->widget()); + + while (splitter) { + QSplitter* previous = splitter; + + for (int h=1; h < splitter->count(); h++) { + QSplitterHandle* handle = splitter->handle(h); + + if (handle->rect().contains(handle->mapFromGlobal(pos))) { + QWidget* a = splitter->widget(h); + QWidget* b = splitter->widget(h+1); + + // try the first widget, if it's an area stop + m_hoverArea = qobject_cast(a); + if (m_hoverArea) + break; + + // then the second widget + m_hoverArea = qobject_cast(b); + if (m_hoverArea) + break; + + // neither widget is an area - let's search for a splitter to recurse to + splitter = qobject_cast(a); + if (splitter) + break; + + splitter = qobject_cast(b); + if (splitter) + break; + + // neither side is an area or a splitter - should be impossible, but stop recursing + // and treat this like a floating window + qWarning("Couldn't find splitter or area at terminal side of splitter"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + + // if we still have a splitter, and didn't find an area, find which widget contains the + // cursor and recurse to that splitter + if (previous == splitter && !m_hoverArea) + { + for (int w=0; w < splitter->count(); w++) { + QWidget* widget = splitter->widget(w); + + if (widget->rect().contains(widget->mapFromGlobal(pos))) { + splitter = qobject_cast(widget); + if (splitter) + break; + + // if this isn't a splitter, and it's not an area (since that would have been found + // before any of this started) then bail out + qWarning("cursor inside unknown child widget that isn't a splitter or area"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + } + + // we found an area to use! stop now + if (m_hoverArea) + break; + + // if we still haven't found anything, bail out + if (previous == splitter) + { + qWarning("Couldn't find cursor inside any child of wrapper"); + splitter = NULL; + hoverWrapper = NULL; + break; + } + } + } } + + if (m_hoverArea || hoverWrapper) { + ToolWindowManagerWrapper* wrapper = hoverWrapper; + if (m_hoverArea) + wrapper = findClosestParent(m_hoverArea); + QRect wrapperGeometry; + wrapperGeometry.setSize(wrapper->rect().size()); + wrapperGeometry.moveTo(wrapper->mapToGlobal(QPoint(0,0))); + m_dropHotspotsOverlay->setGeometry(wrapperGeometry); + + const int margin = m_dropHotspotMargin; + + const int size = m_dropHotspotDimension; + const int hsize = size / 2; + + if (m_hoverArea) { + QRect areaClientRect; + + // calculate the rect of the area relative to m_dropHotspotsOverlay + areaClientRect.setTopLeft(m_hoverArea->mapToGlobal(QPoint(0,0)) - m_dropHotspotsOverlay->pos()); + areaClientRect.setSize(m_hoverArea->rect().size()); + + // subtract the rect for the tab bar. + areaClientRect.adjust(0, m_hoverArea->tabBar()->rect().height(), 0, 0); + + QPoint c = areaClientRect.center(); + + m_dropHotspots[AddTo]->move(c + QPoint(-hsize, -hsize)); + m_dropHotspots[AddTo]->show(); + + m_dropHotspots[TopOf]->move(c + QPoint(-hsize, -hsize-margin-size)); + m_dropHotspots[TopOf]->show(); + + m_dropHotspots[LeftOf]->move(c + QPoint(-hsize-margin-size, -hsize)); + m_dropHotspots[LeftOf]->show(); + + m_dropHotspots[RightOf]->move(c + QPoint( hsize+margin, -hsize)); + m_dropHotspots[RightOf]->show(); + + m_dropHotspots[BottomOf]->move(c + QPoint(-hsize, hsize+margin)); + m_dropHotspots[BottomOf]->show(); + + QRect wrapperClientRect = m_dropHotspotsOverlay->rect(); + c = wrapperClientRect.center(); + QSize s = wrapperClientRect.size(); + + m_dropHotspots[TopWindowSide]->move(QPoint(c.x() - hsize, margin * 2)); + m_dropHotspots[TopWindowSide]->show(); + + m_dropHotspots[LeftWindowSide]->move(QPoint(margin * 2, c.y() - hsize)); + m_dropHotspots[LeftWindowSide]->show(); + + m_dropHotspots[RightWindowSide]->move(QPoint(s.width() - size - margin * 2, c.y() - hsize)); + m_dropHotspots[RightWindowSide]->show(); + + m_dropHotspots[BottomWindowSide]->move(QPoint(c.x() - hsize, s.height() - size - margin * 2)); + m_dropHotspots[BottomWindowSide]->show(); + } else { + m_dropHotspots[AddTo]->move(m_dropHotspotsOverlay->rect().center() + QPoint(-hsize, -hsize)); + m_dropHotspots[AddTo]->show(); + + m_dropHotspots[TopOf]->hide(); + m_dropHotspots[LeftOf]->hide(); + m_dropHotspots[RightOf]->hide(); + m_dropHotspots[BottomOf]->hide(); + + m_dropHotspots[TopWindowSide]->hide(); + m_dropHotspots[LeftWindowSide]->hide(); + m_dropHotspots[RightWindowSide]->hide(); + m_dropHotspots[BottomWindowSide]->hide(); + } + + m_dropHotspotsOverlay->show(); + } else { + m_dropHotspotsOverlay->hide(); + } + + AreaReferenceType hotspot = currentHotspot(); + if ((m_hoverArea || hoverWrapper) && + (hotspot == AddTo || + hotspot == LeftOf || hotspot == RightOf || + hotspot == TopOf || hotspot == BottomOf)) { + QWidget *parent = m_hoverArea; + if(parent == NULL) + parent = hoverWrapper; + + QRect g = parent->geometry(); + g.moveTopLeft(parent->parentWidget()->mapToGlobal(g.topLeft())); + + if (hotspot == LeftOf) + g.adjust(0, 0, -g.width()/2, 0); + else if (hotspot == RightOf) + g.adjust(g.width()/2, 0, 0, 0); + else if (hotspot == TopOf) + g.adjust(0, 0, 0, -g.height()/2); + else if (hotspot == BottomOf) + g.adjust(0, g.height()/2, 0, 0); + + QRect tabGeom; + + if (hotspot == AddTo && m_hoverArea && m_hoverArea->count() > 1) { + QTabBar* tb = m_hoverArea->tabBar(); + g.adjust(0, tb->rect().height(), 0, 0); + + int idx = tb->tabAt(tb->mapFromGlobal(pos)); + + if (idx == -1) { + tabGeom = tb->tabRect(m_hoverArea->count()-1); + tabGeom.moveTo(tb->mapToGlobal(QPoint(0,0)) + tabGeom.topLeft()); + + // move the tab one to the right, to indicate the tab is being added after the last one. + tabGeom.moveLeft(tabGeom.left() + tabGeom.width()); + + // clamp from the right, to ensure we don't display any tab off the end of the range + if(tabGeom.right() > g.right()) + tabGeom.moveLeft(g.right() - tabGeom.width()); + } else { + tabGeom = tb->tabRect(idx); + tabGeom.moveTo(tb->mapToGlobal(QPoint(0,0)) + tabGeom.topLeft()); + } + } + + m_previewOverlay->setGeometry(g); + + m_previewTabOverlay->setGeometry(tabGeom); + } else if((m_hoverArea || hoverWrapper) && + (hotspot == LeftWindowSide || hotspot == RightWindowSide || + hotspot == TopWindowSide || hotspot == BottomWindowSide)) { + ToolWindowManagerWrapper* wrapper = hoverWrapper; + if (m_hoverArea) + wrapper = findClosestParent(m_hoverArea); + + QRect g; + g.moveTopLeft(wrapper->mapToGlobal(QPoint())); + g.setSize(wrapper->rect().size()); + + if(hotspot == LeftWindowSide) + g.adjust(0, 0, -(g.width()*5)/6, 0); + else if(hotspot == RightWindowSide) + g.adjust((g.width()*5)/6, 0, 0, 0); + else if(hotspot == TopWindowSide) + g.adjust(0, 0, 0, -(g.height()*3)/4); + else if(hotspot == BottomWindowSide) + g.adjust(0, (g.height()*3)/4, 0, 0); + + m_previewOverlay->setGeometry(g); + m_previewTabOverlay->setGeometry(QRect()); + } else { + // no hotspot highlighted, draw geometry for a float window if previewing a tear-off, or draw + // nothing if we're dragging a float window as it moves itself. + if (m_draggedWrapper) { + m_previewOverlay->setGeometry(QRect()); + } else { + QRect r; + for (QWidget *w : m_draggedToolWindows) { + if (w->isVisible()) + r = r.united(w->rect()); + } + m_previewOverlay->setGeometry(pos.x(), pos.y(), r.width(), r.height()); + } + m_previewTabOverlay->setGeometry(QRect()); + } + + m_previewOverlay->show(); + m_previewTabOverlay->show(); + if (m_dropHotspotsOverlay->isVisible()) + m_dropHotspotsOverlay->raise(); +} + +void ToolWindowManager::abortDrag() { + if (!dragInProgress()) + return; + + m_previewOverlay->hide(); + m_previewTabOverlay->hide(); + m_dropHotspotsOverlay->hide(); + m_draggedToolWindows.clear(); + m_draggedWrapper = NULL; + qApp->removeEventFilter(this); } void ToolWindowManager::finishDrag() { @@ -812,37 +945,172 @@ void ToolWindowManager::finishDrag() { qWarning("unexpected finishDrag"); return; } - if (m_suggestions.isEmpty()) { - bool allowFloat = m_allowFloatingWindow; + qApp->removeEventFilter(this); - for(QWidget *w : m_draggedToolWindows) - allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); + // move these locally to prevent re-entrancy + QList draggedToolWindows = m_draggedToolWindows; + ToolWindowManagerWrapper* draggedWrapper = m_draggedWrapper; - if (m_allowFloatingWindow) - { - QRect r; - for(QWidget *w : m_draggedToolWindows) - r = r.united(w->rect()); + m_draggedToolWindows.clear(); + m_draggedWrapper = NULL; - moveToolWindows(m_draggedToolWindows, NewFloatingArea); + AreaReferenceType hotspot = currentHotspot(); - ToolWindowManagerArea *area = areaOf(m_draggedToolWindows[0]); + m_previewOverlay->hide(); + m_previewTabOverlay->hide(); + m_dropHotspotsOverlay->hide(); - area->parentWidget()->resize(r.size()); + if (hotspot == NewFloatingArea) { + // check if we're dragging a whole float window, if so we don't do anything as it's already moved + if (!draggedWrapper) { + bool allowFloat = m_allowFloatingWindow; + + for (QWidget *w : draggedToolWindows) + allowFloat &= !(toolWindowProperties(w) & DisallowFloatWindow); + + if (m_allowFloatingWindow) + { + QRect r; + for (QWidget *w : draggedToolWindows) { + if (w->isVisible()) + r = r.united(w->rect()); + } + + moveToolWindows(draggedToolWindows, NewFloatingArea); + + ToolWindowManagerArea *area = areaOf(draggedToolWindows[0]); + + area->parentWidget()->resize(r.size()); + } } } else { - if (m_dropCurrentSuggestionIndex >= m_suggestions.count()) { - qWarning("invalid m_dropCurrentSuggestionIndex"); - return; + if (m_hoverArea) { + AreaReference ref(hotspot, m_hoverArea); + ref.dragResult = true; + moveToolWindows(draggedToolWindows, ref); + } else { + moveToolWindows(draggedToolWindows, AreaReference(EmptySpace)); + } + } +} + +void ToolWindowManager::drawHotspotPixmaps() { + for (AreaReferenceType ref : { AddTo, LeftOf, TopOf, RightOf, BottomOf }) { + m_pixmaps[ref] = QPixmap(m_dropHotspotDimension, m_dropHotspotDimension); + + QPainter p(&m_pixmaps[ref]); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.setRenderHint(QPainter::Antialiasing); + p.setRenderHint(QPainter::HighQualityAntialiasing); + + QRectF rect(0, 0, m_dropHotspotDimension, m_dropHotspotDimension); + + p.fillRect(rect, Qt::transparent); + + rect = rect.marginsAdded(QMarginsF(-1, -1, -1, -1)); + + p.setPen(QPen(QBrush(Qt::darkGray), 1.5)); + p.setBrush(QBrush(Qt::lightGray)); + p.drawRoundedRect(rect, 1.5, 1.5, Qt::AbsoluteSize); + + rect = rect.marginsAdded(QMarginsF(-4, -4, -4, -4)); + + QRectF fullRect = rect; + + if (ref == LeftOf) + rect = rect.marginsAdded(QMarginsF(0, 0, -12, 0)); + else if (ref == TopOf) + rect = rect.marginsAdded(QMarginsF(0, 0, 0, -12)); + else if (ref == RightOf) + rect = rect.marginsAdded(QMarginsF(-12, 0, 0, 0)); + else if (ref == BottomOf) + rect = rect.marginsAdded(QMarginsF(0, -12, 0, 0)); + + p.setPen(QPen(QBrush(Qt::black), 1.0)); + p.setBrush(QBrush(Qt::white)); + p.drawRect(rect); + + // add a little title bar + rect.setHeight(3); + p.fillRect(rect, Qt::SolidPattern); + + // for the sides, add an arrow. + if (ref != AddTo) { + QPainterPath path; + + if (ref == LeftOf) { + QPointF tip = fullRect.center() + QPointF( 4, 0); + + path.addPolygon(QPolygonF({tip, + tip + QPoint( 3, 3), + tip + QPoint( 3, -3), + })); + } else if (ref == TopOf) { + QPointF tip = fullRect.center() + QPointF( 0, 4); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, 3), + tip + QPointF( 3, 3), + })); + } else if (ref == RightOf) { + QPointF tip = fullRect.center() + QPointF(-4, 0); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, 3), + tip + QPointF(-3, -3), + })); + } else if (ref == BottomOf) { + QPointF tip = fullRect.center() + QPointF( 0, -4); + + path.addPolygon(QPolygonF({tip, + tip + QPointF(-3, -3), + tip + QPointF( 3, -3), + })); + } + + p.fillPath(path, QBrush(Qt::black)); } - ToolWindowManager::AreaReference suggestion = m_suggestions[m_dropCurrentSuggestionIndex]; - handleNoSuggestions(); - moveToolWindows(m_draggedToolWindows, suggestion); } + // duplicate these pixmaps by default + m_pixmaps[LeftWindowSide] = m_pixmaps[LeftOf]; + m_pixmaps[RightWindowSide] = m_pixmaps[RightOf]; + m_pixmaps[TopWindowSide] = m_pixmaps[TopOf]; + m_pixmaps[BottomWindowSide] = m_pixmaps[BottomOf]; +} - m_dragIndicator->hide(); - m_draggedToolWindows.clear(); +ToolWindowManager::AreaReferenceType ToolWindowManager::currentHotspot() { + QPoint pos = m_dropHotspotsOverlay->mapFromGlobal(QCursor::pos()); + + for (int i=0; i < NumReferenceTypes; i++) { + if (m_dropHotspots[i] && m_dropHotspots[i]->isVisible() && + m_dropHotspots[i]->geometry().contains(pos)) { + return (ToolWindowManager::AreaReferenceType)i; + } + } + + if (m_hoverArea) { + QTabBar* tb = m_hoverArea->tabBar(); + if (tb->rect().contains(tb->mapFromGlobal(QCursor::pos()))) + return AddTo; + } + + return NewFloatingArea; +} + +bool ToolWindowManager::eventFilter(QObject *object, QEvent *event) { + if (event->type() == QEvent::MouseButtonRelease) { + // right clicking aborts any drag in progress + if (static_cast(event)->button() == Qt::RightButton) + abortDrag(); + } else if (event->type() == QEvent::KeyPress) { + // pressing escape any drag in progress + QKeyEvent *ke = (QKeyEvent *)event; + if(ke->key() == Qt::Key_Escape) { + abortDrag(); + } + } + return QWidget::eventFilter(object, event); } void ToolWindowManager::tabCloseRequested(int index) { @@ -873,7 +1141,7 @@ void ToolWindowManager::tabCloseRequested(int index) { removeToolWindow(toolWindow); } -void ToolWindowManager::windowTitleChanged(const QString &title) { +void ToolWindowManager::windowTitleChanged(const QString &) { QWidget* toolWindow = qobject_cast(sender()); if(!toolWindow) { return; @@ -893,6 +1161,7 @@ QSplitter *ToolWindowManager::createSplitter() { ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, ToolWindowManagerArea *area, float percentage) { m_type = type; m_percentage = percentage; + dragResult = false; setWidget(area); } @@ -924,5 +1193,6 @@ ToolWindowManagerArea *ToolWindowManager::AreaReference::area() const { ToolWindowManager::AreaReference::AreaReference(ToolWindowManager::AreaReferenceType type, QWidget *widget) { m_type = type; + dragResult = false; setWidget(widget); } diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.h b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.h index 5ca2323ee..1305f69ba 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.h +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManager.h @@ -26,11 +26,6 @@ #define TOOLWINDOWMANAGER_H #include -#include -#include -#include -#include -#include #include #include #include @@ -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 m_toolWindowProperties; // all tool window properties QList m_areas; // all areas for this manager QList 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 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 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& toolWindows); + void startDrag(const QList& 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 &toolWindows); private slots: - void showNextDropSuggestion(); void tabCloseRequested(int index); void windowTitleChanged(const QString &title); diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.cpp b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.cpp index 83ec54d8f..8b3c23268 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.cpp +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.cpp @@ -23,10 +23,12 @@ * */ #include "ToolWindowManagerArea.h" +#include "ToolWindowManagerTabBar.h" +#include "ToolWindowManagerWrapper.h" #include "ToolWindowManager.h" #include #include -#include +#include 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() << toolWindow); +void ToolWindowManagerArea::addToolWindow(QWidget *toolWindow, int insertIndex) { + addToolWindows(QList() << toolWindow, insertIndex); } -void ToolWindowManagerArea::addToolWindows(const QList &toolWindows) { +void ToolWindowManagerArea::addToolWindows(const QList &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(event)->pos()); + QPoint pos = static_cast(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(event)->pos(), Qt::LeftButton, Qt::LeftButton, 0); qApp->sendEvent(tabBar(), releaseEvent); - m_manager->startDrag(QList() << toolWindow); + m_manager->startDrag(QList() << 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 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); diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.h b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.h index ca474f203..18ce1ded9 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.h +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerArea.h @@ -29,6 +29,7 @@ #include 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& toolWindows); + void addToolWindows(const QList& 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 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 diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.cpp b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.cpp new file mode 100644 index 000000000..6d183acf4 --- /dev/null +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.cpp @@ -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 +#include +#include +#include + +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(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(parentWidget()); + + if (area) { + ToolWindowManagerWrapper *wrapper = qobject_cast(area->parentWidget()); + + if (wrapper && wrapper->floating()) + return true; + } + + return false; +} diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.h b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.h new file mode 100644 index 000000000..b8745fd32 --- /dev/null +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerTabBar.h @@ -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 +#include + +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 diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.cpp b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.cpp index 0f9fe6161..32896e899 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.cpp +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.cpp @@ -30,23 +30,124 @@ #include #include #include +#include +#include +#include +#include -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(child)) { + setWindowTitle(area->tabText(area->currentIndex())); + return; + } + // otherwise we should have a splitter + if (QSplitter* splitter = qobject_cast(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 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 toolWindows; foreach(ToolWindowManagerArea* tabWidget, findChildren()) { @@ -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 toolWindows; + foreach(ToolWindowManagerArea* tabWidget, findChildren()) { + 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(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 toolWindows; + foreach(ToolWindowManagerArea* tabWidget, findChildren()) { + 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; +} diff --git a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.h b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.h index 1f9cc0c01..339d7cf25 100644 --- a/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.h +++ b/qrenderdoc/3rdparty/toolwindowmanager/ToolWindowManagerWrapper.h @@ -25,10 +25,12 @@ #ifndef TOOLWINDOWMANAGERWRAPPER_H #define TOOLWINDOWMANAGERWRAPPER_H +#include #include #include 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