diff --git a/LICENSE b/LICENSE index 3b867eef..3db05837 100644 --- a/LICENSE +++ b/LICENSE @@ -7,3 +7,5 @@ The above copyright notice and this permission notice shall be included in all c 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. VST is a trademark of Steinberg Media Technologies GmbH + +Code in the editor/widgets/dock folder is based on the "Qt Advanced Docking System" (https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System), and so is licensed under LGPL v2.1. See the LICENSE file in that folder for details. diff --git a/editor/model/LibraryEntry.cpp b/editor/model/LibraryEntry.cpp index 07b1b7f8..53a33102 100644 --- a/editor/model/LibraryEntry.cpp +++ b/editor/model/LibraryEntry.cpp @@ -1,7 +1,7 @@ #include "LibraryEntry.h" #include "PoolOperators.h" -#include "objects/RootSurface.h" +#include "objects/ModuleSurface.h" using namespace AxiomModel; @@ -12,8 +12,9 @@ LibraryEntry::LibraryEntry(QString name, const QUuid &baseUuid, const QUuid &mod _modificationDateTime(modificationDateTime), _tags(std::move(tags)), _root(std::move(root)) { auto rootSurfaces = findChildren(_root->nodeSurfaces().sequence(), QUuid()); assert(rootSurfaces.size() == 1); - _rootSurface = dynamic_cast(*AxiomCommon::takeAt(std::move(rootSurfaces), 0)); + _rootSurface = dynamic_cast(*AxiomCommon::takeAt(rootSurfaces, 0)); assert(_rootSurface); + _rootSurface->setEntry(this); _root->history().stackChanged.connect(this, &LibraryEntry::modified); } @@ -27,7 +28,7 @@ std::unique_ptr LibraryEntry::create(QString name, const QUuid &ba std::unique_ptr LibraryEntry::create(QString name, std::set tags) { auto newRoot = std::make_unique(); - newRoot->pool().registerObj(std::make_unique(QUuid::createUuid(), QPointF(0, 0), 0, 0, newRoot.get())); + newRoot->pool().registerObj(std::make_unique(QUuid::createUuid(), QPointF(0, 0), 0, newRoot.get())); return create(std::move(name), QUuid::createUuid(), QUuid::createUuid(), QDateTime::currentDateTimeUtc(), std::move(tags), std::move(newRoot)); } diff --git a/editor/model/LibraryEntry.h b/editor/model/LibraryEntry.h index 42703ada..fd185799 100644 --- a/editor/model/LibraryEntry.h +++ b/editor/model/LibraryEntry.h @@ -11,7 +11,7 @@ namespace AxiomModel { class Library; - class RootSurface; + class ModuleSurface; class LibraryEntry : public AxiomCommon::TrackedObject { public: @@ -51,7 +51,7 @@ namespace AxiomModel { ModelRoot *root() const { return _root.get(); } - RootSurface *rootSurface() const { return _rootSurface; } + ModuleSurface *rootSurface() const { return _rootSurface; } void modified(); @@ -64,6 +64,6 @@ namespace AxiomModel { QDateTime _modificationDateTime; std::set _tags; std::unique_ptr _root; - RootSurface *_rootSurface; + ModuleSurface *_rootSurface; }; } diff --git a/editor/model/actions/DeleteObjectAction.cpp b/editor/model/actions/DeleteObjectAction.cpp index 454adb73..1f1c4c4b 100644 --- a/editor/model/actions/DeleteObjectAction.cpp +++ b/editor/model/actions/DeleteObjectAction.cpp @@ -49,7 +49,7 @@ void DeleteObjectAction::backward() { QDataStream stream(&_buffer, QIODevice::ReadOnly); IdentityReferenceMapper ref; auto addedObjects = - ModelObjectSerializer::deserializeChunk(stream, ProjectSerializer::schemaVersion, root(), QUuid(), &ref); + ModelObjectSerializer::deserializeChunk(stream, ProjectSerializer::schemaVersion, root(), QUuid(), &ref, false); _buffer.clear(); } diff --git a/editor/model/actions/PasteBufferAction.cpp b/editor/model/actions/PasteBufferAction.cpp index 062a962d..55be8c89 100644 --- a/editor/model/actions/PasteBufferAction.cpp +++ b/editor/model/actions/PasteBufferAction.cpp @@ -44,13 +44,13 @@ void PasteBufferAction::forward(bool) { if (_isBufferFormatted) { IdentityReferenceMapper ref; used = ModelObjectSerializer::deserializeChunk(stream, ProjectSerializer::schemaVersion, root(), _surfaceUuid, - &ref); + &ref, false); } else { CloneReferenceMapper ref; ref.setUuid(_surfaceUuid, _surfaceUuid); ref.setPos(_surfaceUuid, _center - objectCenter); used = ModelObjectSerializer::deserializeChunk(stream, ProjectSerializer::schemaVersion, root(), _surfaceUuid, - &ref); + &ref, false); } _isBufferFormatted = true; diff --git a/editor/model/objects/CMakeLists.txt b/editor/model/objects/CMakeLists.txt index 9f8bac3c..99814431 100644 --- a/editor/model/objects/CMakeLists.txt +++ b/editor/model/objects/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/Control.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ControlSurface.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MidiControl.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ModuleSurface.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/Node.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/CustomNode.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/ExtractControl.cpp" diff --git a/editor/model/objects/ModuleSurface.cpp b/editor/model/objects/ModuleSurface.cpp new file mode 100644 index 00000000..6b648cd5 --- /dev/null +++ b/editor/model/objects/ModuleSurface.cpp @@ -0,0 +1,22 @@ +#include "ModuleSurface.h" + +#include "../LibraryEntry.h" + +using namespace AxiomModel; + +ModuleSurface::ModuleSurface(const QUuid &uuid, QPointF pan, float zoom, AxiomModel::ModelRoot *root) + : NodeSurface(uuid, QUuid(), pan, zoom, root) {} + +QString ModuleSurface::name() { + return _entry->name(); +} + +QString ModuleSurface::debugName() { + return "ModuleSurface"; +} + +void ModuleSurface::setEntry(AxiomModel::LibraryEntry *entry) { + assert(!_entry); + _entry = entry; + _entry->nameChanged.forward(&nameChanged); +} diff --git a/editor/model/objects/ModuleSurface.h b/editor/model/objects/ModuleSurface.h new file mode 100644 index 00000000..c1505c09 --- /dev/null +++ b/editor/model/objects/ModuleSurface.h @@ -0,0 +1,30 @@ +#pragma once + +#include "NodeSurface.h" + +namespace AxiomModel { + + class LibraryEntry; + + class ModuleSurface : public NodeSurface { + public: + ModuleSurface(const QUuid &uuid, QPointF pan, float zoom, AxiomModel::ModelRoot *root); + + QString name() override; + + QString debugName() override; + + bool canExposeControl() const override { return false; } + + bool canHavePortals() const override { return false; } + + uint64_t getRuntimeId() override { return 0; } + + void setEntry(AxiomModel::LibraryEntry *entry); + + AxiomModel::LibraryEntry *entry() const { return _entry; } + + private: + AxiomModel::LibraryEntry *_entry = nullptr; + }; +} diff --git a/editor/model/serialize/LibrarySerializer.cpp b/editor/model/serialize/LibrarySerializer.cpp index f21f44a3..a7e34409 100644 --- a/editor/model/serialize/LibrarySerializer.cpp +++ b/editor/model/serialize/LibrarySerializer.cpp @@ -65,7 +65,7 @@ std::unique_ptr LibrarySerializer::deserializeEntry(QDataStream &s // 0.4.0 (schema version 5) removed history from being serialized in the library auto deserializeHistory = version < 5; - auto root = ModelObjectSerializer::deserializeRoot(stream, deserializeHistory, version); + auto root = ModelObjectSerializer::deserializeRoot(stream, deserializeHistory, true, version); return LibraryEntry::create(std::move(name), baseUuid, modificationUuid, modificationDateTime, std::move(tags), std::move(root)); } diff --git a/editor/model/serialize/ModelObjectSerializer.cpp b/editor/model/serialize/ModelObjectSerializer.cpp index 84cfb481..ab27c69f 100644 --- a/editor/model/serialize/ModelObjectSerializer.cpp +++ b/editor/model/serialize/ModelObjectSerializer.cpp @@ -26,7 +26,7 @@ void ModelObjectSerializer::serializeRoot(AxiomModel::ModelRoot *root, bool incl std::vector ModelObjectSerializer::deserializeChunk(QDataStream &stream, uint32_t version, ModelRoot *root, const QUuid &parent, - AxiomModel::ReferenceMapper *ref) { + AxiomModel::ReferenceMapper *ref, bool isLibrary) { std::vector usedObjects; uint32_t objectCount; @@ -37,7 +37,7 @@ std::vector ModelObjectSerializer::deserializeChunk(QDataStream & stream >> objectBuffer; QDataStream objectStream(&objectBuffer, QIODevice::ReadOnly); - auto newObject = deserialize(objectStream, version, root, parent, ref); + auto newObject = deserialize(objectStream, version, root, parent, ref, isLibrary); usedObjects.push_back(newObject.get()); root->pool().registerObj(std::move(newObject)); } @@ -46,10 +46,10 @@ std::vector ModelObjectSerializer::deserializeChunk(QDataStream & } std::unique_ptr ModelObjectSerializer::deserializeRoot(QDataStream &stream, bool includeHistory, - uint32_t version) { + bool isLibrary, uint32_t version) { auto modelRoot = std::make_unique(); IdentityReferenceMapper ref; - deserializeChunk(stream, version, modelRoot.get(), QUuid(), &ref); + deserializeChunk(stream, version, modelRoot.get(), QUuid(), &ref, isLibrary); if (includeHistory) { modelRoot->setHistory(HistorySerializer::deserialize(stream, version, modelRoot.get())); } @@ -65,7 +65,7 @@ void ModelObjectSerializer::serialize(AxiomModel::ModelObject *obj, QDataStream std::unique_ptr ModelObjectSerializer::deserialize(QDataStream &stream, uint32_t version, AxiomModel::ModelRoot *root, const QUuid &parent, - AxiomModel::ReferenceMapper *ref) { + AxiomModel::ReferenceMapper *ref, bool isLibrary) { QUuid uuid; stream >> uuid; uuid = ref->mapUuid(uuid); @@ -80,7 +80,7 @@ std::unique_ptr ModelObjectSerializer::deserialize(QDataStream &str uint8_t typeInt; stream >> typeInt; - return deserializeInner(stream, version, root, (ModelObject::ModelType) typeInt, uuid, parentUuid, ref); + return deserializeInner(stream, version, root, (ModelObject::ModelType) typeInt, uuid, parentUuid, ref, isLibrary); } void ModelObjectSerializer::serializeInner(AxiomModel::ModelObject *obj, QDataStream &stream) { @@ -98,10 +98,10 @@ std::unique_ptr ModelObjectSerializer::deserializeInner(QDataStream AxiomModel::ModelRoot *root, AxiomModel::ModelObject::ModelType type, const QUuid &uuid, const QUuid &parent, - AxiomModel::ReferenceMapper *ref) { + AxiomModel::ReferenceMapper *ref, bool isLibrary) { switch (type) { case ModelObject::ModelType::NODE_SURFACE: - return NodeSurfaceSerializer::deserialize(stream, version, uuid, parent, ref, root); + return NodeSurfaceSerializer::deserialize(stream, version, uuid, parent, ref, root, isLibrary); case ModelObject::ModelType::NODE: return NodeSerializer::deserialize(stream, version, uuid, parent, ref, root); case ModelObject::ModelType::CONTROL_SURFACE: diff --git a/editor/model/serialize/ModelObjectSerializer.h b/editor/model/serialize/ModelObjectSerializer.h index 79f93cf2..e0f36329 100644 --- a/editor/model/serialize/ModelObjectSerializer.h +++ b/editor/model/serialize/ModelObjectSerializer.h @@ -12,22 +12,23 @@ namespace AxiomModel { namespace ModelObjectSerializer { std::vector deserializeChunk(QDataStream &stream, uint32_t version, ModelRoot *root, - const QUuid &parent, ReferenceMapper *ref); + const QUuid &parent, ReferenceMapper *ref, bool isLibrary); void serializeRoot(ModelRoot *root, bool includeHistory, QDataStream &stream); - std::unique_ptr deserializeRoot(QDataStream &stream, bool includeHistory, uint32_t version); + std::unique_ptr deserializeRoot(QDataStream &stream, bool includeHistory, bool isLibrary, + uint32_t version); void serialize(ModelObject *obj, QDataStream &stream, const QUuid &parent); std::unique_ptr deserialize(QDataStream &stream, uint32_t version, ModelRoot *root, - const QUuid &parent, ReferenceMapper *ref); + const QUuid &parent, ReferenceMapper *ref, bool isLibrary); void serializeInner(ModelObject *obj, QDataStream &stream); std::unique_ptr deserializeInner(QDataStream &stream, uint32_t version, ModelRoot *root, ModelObject::ModelType type, const QUuid &uuid, - const QUuid &parent, ReferenceMapper *ref); + const QUuid &parent, ReferenceMapper *ref, bool isLibrary); template void serializeChunk(QDataStream &stream, const QUuid &parent, T objects) { diff --git a/editor/model/serialize/NodeSurfaceSerializer.cpp b/editor/model/serialize/NodeSurfaceSerializer.cpp index cc4a00d0..808929fd 100644 --- a/editor/model/serialize/NodeSurfaceSerializer.cpp +++ b/editor/model/serialize/NodeSurfaceSerializer.cpp @@ -1,6 +1,7 @@ #include "NodeSurfaceSerializer.h" #include "../objects/GroupSurface.h" +#include "../objects/ModuleSurface.h" #include "../objects/NodeSurface.h" #include "../objects/RootSurface.h" @@ -11,20 +12,24 @@ void NodeSurfaceSerializer::serialize(AxiomModel::NodeSurface *surface, QDataStr stream << surface->zoom(); if (auto rootSurface = dynamic_cast(surface)) { - stream << (quint64)rootSurface->nextPortalId(); + stream << (quint64) rootSurface->nextPortalId(); } } std::unique_ptr NodeSurfaceSerializer::deserialize(QDataStream &stream, uint32_t version, const QUuid &uuid, const QUuid &parentUuid, AxiomModel::ReferenceMapper *ref, - AxiomModel::ModelRoot *root) { + AxiomModel::ModelRoot *root, bool isLibrary) { QPointF pan; stream >> pan; float zoom; stream >> zoom; if (parentUuid.isNull()) { + if (isLibrary) { + return std::make_unique(uuid, pan, zoom, root); + } + // unique portal IDs were added in 0.4.0, corresponding to schema version 5 quint64 nextPortalId = 0; if (version >= 5) { diff --git a/editor/model/serialize/NodeSurfaceSerializer.h b/editor/model/serialize/NodeSurfaceSerializer.h index 8a2baefb..ecba7055 100644 --- a/editor/model/serialize/NodeSurfaceSerializer.h +++ b/editor/model/serialize/NodeSurfaceSerializer.h @@ -13,6 +13,7 @@ namespace AxiomModel { void serialize(NodeSurface *surface, QDataStream &stream); std::unique_ptr deserialize(QDataStream &stream, uint32_t version, const QUuid &uuid, - const QUuid &parentUuid, ReferenceMapper *ref, ModelRoot *root); + const QUuid &parentUuid, ReferenceMapper *ref, ModelRoot *root, + bool isLibrary); } } diff --git a/editor/model/serialize/ProjectSerializer.cpp b/editor/model/serialize/ProjectSerializer.cpp index 89236405..2ac055d2 100644 --- a/editor/model/serialize/ProjectSerializer.cpp +++ b/editor/model/serialize/ProjectSerializer.cpp @@ -42,7 +42,7 @@ std::unique_ptr ProjectSerializer::deserialize(QDataStream &stream, uin if (versionOut) *versionOut = version; auto linkedFile = getLinkedFile(stream, version); - auto modelRoot = ModelObjectSerializer::deserializeRoot(stream, true, version); + auto modelRoot = ModelObjectSerializer::deserializeRoot(stream, true, false, version); auto project = std::make_unique(linkedFile, std::move(modelRoot)); // Before schema version 5, the module library was included in the project file. To ensure modules aren't lost, diff --git a/editor/resources/icons/dock-close.png b/editor/resources/icons/dock-close.png new file mode 100644 index 00000000..230b66b9 Binary files /dev/null and b/editor/resources/icons/dock-close.png differ diff --git a/editor/resources/icons/dock-menu.png b/editor/resources/icons/dock-menu.png new file mode 100644 index 00000000..e4d5329b Binary files /dev/null and b/editor/resources/icons/dock-menu.png differ diff --git a/editor/resources/res.qrc b/editor/resources/res.qrc index 3b146e27..c291b138 100644 --- a/editor/resources/res.qrc +++ b/editor/resources/res.qrc @@ -5,6 +5,7 @@ logo.png styles/MainStyles.qss + styles/Dock.qss styles/ModuleBrowserPanel.qss styles/ModulePreviewList.qss styles/SchematicPanel.qss @@ -19,6 +20,8 @@ icons/output-midi-portal.png icons/output-num-portal.png icons/automation-portal.png + icons/dock-close.png + icons/dock-menu.png default.axl diff --git a/editor/resources/styles/Dock.qss b/editor/resources/styles/Dock.qss new file mode 100644 index 00000000..a13a9635 --- /dev/null +++ b/editor/resources/styles/Dock.qss @@ -0,0 +1,53 @@ +ads--CDockContainerWidget { + background: #292929; +} + +ads--CDockContainerWidget QSplitter::handle { + border-right: 2px solid #292929; + border-bottom: 2px solid #292929; +} + +ads--CDockAreaWidget { + background: #292929; +} + +ads--CDockAreaWidget #tabsContainerWidget, ads--CDockAreaWidget #dockAreaTitleBar { + background: #292929; +} + +ads--CDockAreaWidget #tabsMenuButton::menu-indicator { + image: none; +} + +ads--CDockWidgetTitleBar { + background: transparent; + padding: 8px 10px; +} + +ads--CDockWidgetTitleBar:hover, ads--CDockWidgetTitleBar[activeTab="true"] { + background: #444; +} + +ads--CDockWidgetTitleBar QLabel { + background: transparent; +} + +ads--CDockWidget { + border-color: #444; + border-style: solid; + border-width: 2px 0 0 0; +} + +QPushButton#closeButton, QPushButton#tabsMenuButton { + padding: 2px; + background: transparent; + border: none; +} + +QPushButton#closeButton:hover, QPushButton#tabsMenuButton:hover { + background: #444; +} + +QPushButton#closeButton:pressed, QPushButton#tabsMenuButton:pressed { + background: #444; +} diff --git a/editor/resources/styles/MainStyles.qss b/editor/resources/styles/MainStyles.qss index 889f9246..965def96 100644 --- a/editor/resources/styles/MainStyles.qss +++ b/editor/resources/styles/MainStyles.qss @@ -1,6 +1,6 @@ * { color: #EEEEEE; - background-color: #111;/*#0A0A0A;*/ + background-color: #111; } /* form control styling */ diff --git a/editor/widgets/dock/CMakeLists.txt b/editor/widgets/dock/CMakeLists.txt index 048adcab..48902b21 100644 --- a/editor/widgets/dock/CMakeLists.txt +++ b/editor/widgets/dock/CMakeLists.txt @@ -1,6 +1,14 @@ cmake_minimum_required(VERSION 3.4.3) -set(SOURCE_FILES - "${CMAKE_CURRENT_SOURCE_DIR}/DockPanel.cpp") + set(SOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/ads_globals.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockAreaWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockContainerWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockManager.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockOverlay.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockSplitter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockStateSerialization.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockWidget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/DockWidgetTitleBar.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/FloatingDockContainer.cpp") -target_sources(axiom_widgets PRIVATE ${SOURCE_FILES}) + target_sources(axiom_widgets PRIVATE ${SOURCE_FILES}) diff --git a/editor/widgets/dock/DockAreaWidget.cpp b/editor/widgets/dock/DockAreaWidget.cpp new file mode 100644 index 00000000..4a96d676 --- /dev/null +++ b/editor/widgets/dock/DockAreaWidget.cpp @@ -0,0 +1,566 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockAreaWidget.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of CDockAreaWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockAreaWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockContainerWidget.h" +#include "DockManager.h" +#include "DockOverlay.h" +#include "DockWidget.h" +#include "DockWidgetTitleBar.h" +#include "FloatingDockContainer.h" + +namespace ads { + static const char *const INDEX_PROPERTY = "index"; + static const char *const ACTION_PROPERTY = "action"; + static const int APPEND = -1; + + /** + * Custom scroll bar implementation for dock area tab bar + * This scroll area enables floating of a whole dock area including all + * dock widgets + */ + class CTabsScrollArea : public QScrollArea { + private: + QPoint m_DragStartMousePos; + CDockAreaWidget *m_DockArea; + CFloatingDockContainer *m_FloatingWidget = nullptr; + + public: + CTabsScrollArea(CDockAreaWidget *parent) : QScrollArea(parent), m_DockArea(parent) { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Ignored); + setFrameStyle(QFrame::NoFrame); + setWidgetResizable(true); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + } + + protected: + virtual void wheelEvent(QWheelEvent *Event) override { + Event->accept(); + const int direction = Event->angleDelta().y(); + if (direction < 0) { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 20); + } else { + horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 20); + } + } + + /** + * Stores mouse position to detect dragging + */ + virtual void mousePressEvent(QMouseEvent *ev) override { + if (ev->button() == Qt::LeftButton) { + ev->accept(); + m_DragStartMousePos = ev->pos(); + return; + } + QScrollArea::mousePressEvent(ev); + } + + /** + * Stores mouse position to detect dragging + */ + virtual void mouseReleaseEvent(QMouseEvent *ev) override { + if (ev->button() == Qt::LeftButton) { + ev->accept(); + m_FloatingWidget = nullptr; + m_DragStartMousePos = QPoint(); + return; + } + QScrollArea::mouseReleaseEvent(ev); + } + + /** + * Starts floating the complete docking area including all dock widgets, + * if it is not the last dock area in a floating widget + */ + virtual void mouseMoveEvent(QMouseEvent *ev) override { + QScrollArea::mouseMoveEvent(ev); + if (ev->buttons() != Qt::LeftButton) { + return; + } + + if (m_FloatingWidget) { + m_FloatingWidget->moveFloating(); + return; + } + + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one + // empty + if (m_DockArea->dockContainer()->isFloating() && m_DockArea->dockContainer()->visibleDockAreaCount() == 1) { + return; + } + + if (!this->geometry().contains(ev->pos())) { + startFloating(m_DragStartMousePos); + auto Overlay = m_DockArea->dockManager()->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + } + + return; + } + + /** + * Double clicking the title bar also starts floating of the complete area + */ + virtual void mouseDoubleClickEvent(QMouseEvent *event) override { + // If this is the last dock area in a dock container it does not make + // sense to move it to a new floating widget and leave this one + // empty + if (m_DockArea->dockContainer()->isFloating() && m_DockArea->dockContainer()->dockAreaCount() == 1) { + return; + } + startFloating(event->pos()); + } + + /** + * Starts floating + */ + void startFloating(const QPoint &Pos) { + QSize Size = m_DockArea->size(); + CFloatingDockContainer *FloatingWidget = new CFloatingDockContainer(m_DockArea); + FloatingWidget->startFloating(Pos, Size); + m_FloatingWidget = FloatingWidget; + } + }; // class CTabsScrollArea + + /** + * Private data class of CDockAreaWidget class (pimpl) + */ + struct DockAreaWidgetPrivate { + CDockAreaWidget *_this; + QBoxLayout *Layout; + QFrame *TitleBar; + QBoxLayout *TopLayout; + QStackedLayout *ContentsLayout; + QScrollArea *TabsScrollArea; + QWidget *TabsContainerWidget; + QBoxLayout *TabsLayout; + QPushButton *TabsMenuButton; + QPushButton *CloseButton; + int TabsLayoutInitCount; + CDockManager *DockManager = nullptr; + + /** + * Private data constructor + */ + DockAreaWidgetPrivate(CDockAreaWidget *_public); + + /** + * Creates the layout for top area with tabs and close button + */ + void createTabBar(); + + /** + * Returns the dock widget with the given index + */ + CDockWidget *dockWidgetAt(int index) { return dynamic_cast(ContentsLayout->widget(index)); } + + /** + * Convenience function to ease title widget access by index + */ + CDockWidgetTitleBar *titleWidgetAt(int index) { return dockWidgetAt(index)->titleBar(); } + + /** + * Adds a tabs menu entry for the given dock widget + * If menu is 0, a menu entry is added to the menu of the TabsMenuButton + * member. If menu is a valid menu pointer, the entry will be added to + * the given menu + */ + void addTabsMenuEntry(CDockWidget *DockWidget, int Index = -1, QMenu *menu = 0); + + /** + * Returns the tab action of the given dock widget + */ + QAction *dockWidgetTabAction(CDockWidget *DockWidget) const { + return qvariant_cast(DockWidget->property(ACTION_PROPERTY)); + } + + /** + * Returns the index of the given dock widget + */ + int dockWidgetIndex(CDockWidget *DockWidget) const { return DockWidget->property(INDEX_PROPERTY).toInt(); } + + /** + * Update the tabs menu if dock widget order changed or if dock widget has + * been removed + */ + void updateTabsMenu(); + + /** + * Updates the tab bar visibility depending on the number of dock widgets + * in this area + */ + void updateTabBar(); + }; + // struct DockAreaWidgetPrivate + + //============================================================================ + DockAreaWidgetPrivate::DockAreaWidgetPrivate(CDockAreaWidget *_public) : _this(_public) {} + + //============================================================================ + void DockAreaWidgetPrivate::createTabBar() { + TitleBar = new QFrame(_this); + TitleBar->setObjectName("dockAreaTitleBar"); + TopLayout = new QBoxLayout(QBoxLayout::LeftToRight); + TopLayout->setContentsMargins(0, 0, 0, 0); + TopLayout->setSpacing(0); + TitleBar->setLayout(TopLayout); + Layout->addWidget(TitleBar); + + TabsScrollArea = new CTabsScrollArea(_this); + TopLayout->addWidget(TabsScrollArea, 1); + + TabsContainerWidget = new QWidget(); + TabsContainerWidget->setObjectName("tabsContainerWidget"); + TabsScrollArea->setWidget(TabsContainerWidget); + + TabsLayout = new QBoxLayout(QBoxLayout::LeftToRight); + TabsLayout->setContentsMargins(0, 0, 0, 0); + TabsLayout->setSpacing(0); + TabsLayout->addStretch(1); + TabsContainerWidget->setLayout(TabsLayout); + + TabsMenuButton = new QPushButton(); + TabsMenuButton->setObjectName("tabsMenuButton"); + TabsMenuButton->setFlat(true); + TabsMenuButton->setIcon(QIcon(":/icons/dock-menu.png")); + TabsMenuButton->setMaximumWidth(TabsMenuButton->iconSize().width()); + TabsMenuButton->setMenu(new QMenu(TabsMenuButton)); + TopLayout->addWidget(TabsMenuButton, 0); + _this->connect(TabsMenuButton->menu(), SIGNAL(triggered(QAction *)), + SLOT(onTabsMenuActionTriggered(QAction *))); + + CloseButton = new QPushButton(); + CloseButton->setObjectName("closeButton"); + CloseButton->setFlat(true); + CloseButton->setIcon(QIcon(":/icons/dock-close.png")); + CloseButton->setToolTip(_this->tr("Close")); + CloseButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + TopLayout->addWidget(CloseButton, 0); + _this->connect(CloseButton, SIGNAL(clicked()), SLOT(onCloseButtonClicked())); + + TabsLayoutInitCount = TabsLayout->count(); + } + + //============================================================================ + void DockAreaWidgetPrivate::updateTabBar() { + CDockContainerWidget *Container = _this->dockContainer(); + if (!Container) { + return; + } + + if (Container->isFloating() && (Container->dockAreaCount() == 1) && (_this->count() == 1)) { + TitleBar->setVisible(false); + } else { + TitleBar->setVisible(true); + } + } + + //============================================================================ + void DockAreaWidgetPrivate::addTabsMenuEntry(CDockWidget *DockWidget, int Index, QMenu *menu) { + menu = menu ? menu : TabsMenuButton->menu(); + QAction *Action; + if (Index >= 0 && Index < menu->actions().count()) { + Action = new QAction(DockWidget->windowTitle()); + menu->insertAction(menu->actions().at(Index), Action); + } else { + Action = menu->addAction(DockWidget->windowTitle()); + } + QVariant vAction = QVariant::fromValue(Action); + DockWidget->setProperty(ACTION_PROPERTY, vAction); + } + + //============================================================================ + void DockAreaWidgetPrivate::updateTabsMenu() { + QMenu *menu = TabsMenuButton->menu(); + menu->clear(); + for (int i = 0; i < ContentsLayout->count(); ++i) { + addTabsMenuEntry(dockWidgetAt(i), APPEND, menu); + } + } + + //============================================================================ + CDockAreaWidget::CDockAreaWidget(CDockManager *DockManager, CDockContainerWidget *parent) + : QFrame(parent), d(new DockAreaWidgetPrivate(this)) { + d->DockManager = DockManager; + d->Layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + + d->createTabBar(); + + d->ContentsLayout = new QStackedLayout(); + d->ContentsLayout->setContentsMargins(0, 0, 0, 0); + d->ContentsLayout->setSpacing(0); + d->Layout->addLayout(d->ContentsLayout, 1); + } + + //============================================================================ + CDockAreaWidget::~CDockAreaWidget() { delete d; } + + //============================================================================ + CDockManager *CDockAreaWidget::dockManager() const { return d->DockManager; } + + //============================================================================ + CDockContainerWidget *CDockAreaWidget::dockContainer() const { + QWidget *Parent = parentWidget(); + while (Parent) { + CDockContainerWidget *Container = dynamic_cast(Parent); + if (Container) { + return Container; + } + Parent = Parent->parentWidget(); + } + + return 0; + } + + //============================================================================ + void CDockAreaWidget::addDockWidget(CDockWidget *DockWidget) { + insertDockWidget(d->ContentsLayout->count(), DockWidget); + } + + //============================================================================ + void CDockAreaWidget::insertDockWidget(int index, CDockWidget *DockWidget, bool Activate) { + d->ContentsLayout->insertWidget(index, DockWidget); + DockWidget->titleBar()->setDockAreaWidget(this); + auto TitleBar = DockWidget->titleBar(); + d->TabsLayout->insertWidget(index, TitleBar); + TitleBar->show(); + connect(TitleBar, SIGNAL(clicked()), this, SLOT(onDockWidgetTitleClicked())); + DockWidget->setProperty(INDEX_PROPERTY, index); + if (Activate) { + setCurrentIndex(index); + } + d->addTabsMenuEntry(DockWidget, index); + DockWidget->setDockArea(this); + } + + //============================================================================ + void CDockAreaWidget::removeDockWidget(CDockWidget *DockWidget) { + d->ContentsLayout->removeWidget(DockWidget); + auto TitleBar = DockWidget->titleBar(); + TitleBar->hide(); + d->TabsLayout->removeWidget(TitleBar); + disconnect(TitleBar, SIGNAL(clicked()), this, SLOT(onDockWidgetTitleClicked())); + setCurrentIndex(d->ContentsLayout->currentIndex()); + d->updateTabsMenu(); + + CDockContainerWidget *DockContainer = dockContainer(); + if (d->ContentsLayout->isEmpty()) { + dockContainer()->removeDockArea(this); + this->deleteLater(); + ; + } + + d->updateTabBar(); + DockWidget->setDockArea(nullptr); + DockContainer->dumpLayout(); + } + + //============================================================================ + void CDockAreaWidget::onDockWidgetTitleClicked() { + CDockWidgetTitleBar *TitleWidget = qobject_cast(sender()); + if (!TitleWidget) { + return; + } + + int index = d->TabsLayout->indexOf(TitleWidget); + setCurrentIndex(index); + } + + //============================================================================ + void CDockAreaWidget::onCloseButtonClicked() { currentDockWidget()->toggleView(false); } + + //============================================================================ + CDockWidget *CDockAreaWidget::currentDockWidget() const { return dockWidget(currentIndex()); } + + //============================================================================ + void CDockAreaWidget::setCurrentDockWidget(CDockWidget *DockWidget) { + int Index = tabIndex(DockWidget); + if (Index < 0) { + return; + } + setCurrentIndex(Index); + } + + //============================================================================ + void CDockAreaWidget::setCurrentIndex(int index) { + if (index < 0 || index > (d->TabsLayout->count() - 1)) { + qWarning() << Q_FUNC_INFO << "Invalid index" << index; + return; + } + + // Set active TAB and update all other tabs to be inactive + for (int i = 0; i < d->TabsLayout->count(); ++i) { + QLayoutItem *item = d->TabsLayout->itemAt(i); + if (!item->widget()) { + continue; + } + + auto TitleWidget = dynamic_cast(item->widget()); + if (!TitleWidget) { + continue; + } + + if (i == index) { + TitleWidget->show(); + TitleWidget->setActiveTab(true); + d->TabsScrollArea->ensureWidgetVisible(TitleWidget); + auto Features = TitleWidget->dockWidget()->features(); + d->CloseButton->setVisible(Features.testFlag(CDockWidget::DockWidgetClosable)); + } else { + TitleWidget->setActiveTab(false); + } + } + + d->ContentsLayout->setCurrentIndex(index); + d->ContentsLayout->currentWidget()->show(); + emit currentChanged(index); + } + + //============================================================================ + int CDockAreaWidget::currentIndex() const { return d->ContentsLayout->currentIndex(); } + + //============================================================================ + QRect CDockAreaWidget::titleAreaGeometry() const { return d->TopLayout->geometry(); } + + //============================================================================ + QRect CDockAreaWidget::contentAreaGeometry() const { return d->ContentsLayout->geometry(); } + + //============================================================================ + int CDockAreaWidget::tabIndex(CDockWidget *DockWidget) { return d->ContentsLayout->indexOf(DockWidget); } + + //============================================================================ + QList CDockAreaWidget::dockWidgets() const { + QList DockWidgetList; + for (int i = 0; i < d->ContentsLayout->count(); ++i) { + DockWidgetList.append(dockWidget(i)); + } + return DockWidgetList; + } + + //============================================================================ + QList CDockAreaWidget::openedDockWidgets() const { + QList DockWidgetList; + for (int i = 0; i < d->ContentsLayout->count(); ++i) { + CDockWidget *DockWidget = dockWidget(i); + if (!DockWidget->isClosed()) { + DockWidgetList.append(dockWidget(i)); + } + } + return DockWidgetList; + } + + //============================================================================ + int CDockAreaWidget::indexOfContentByTitlePos(const QPoint &p, QWidget *exclude) const { + for (int i = 0; i < d->ContentsLayout->count(); ++i) { + auto TitleWidget = d->titleWidgetAt(i); + if (TitleWidget->geometry().contains(p) && (!exclude || TitleWidget != exclude)) { + return i; + } + } + return -1; + } + + //============================================================================ + int CDockAreaWidget::count() const { return d->ContentsLayout->count(); } + + //============================================================================ + CDockWidget *CDockAreaWidget::dockWidget(int Index) const { + return dynamic_cast(d->ContentsLayout->widget(Index)); + } + + //============================================================================ + void CDockAreaWidget::reorderDockWidget(int fromIndex, int toIndex) { + if (fromIndex >= d->ContentsLayout->count() || fromIndex < 0 || toIndex >= d->ContentsLayout->count() || + toIndex < 0 || fromIndex == toIndex) { + d->TabsLayout->update(); + return; + } + + CDockWidget *DockWidget = dockWidget(fromIndex); + + // reorder tabs menu action to match new order of contents + auto Menu = d->TabsMenuButton->menu(); + auto TabsAction = d->dockWidgetTabAction(DockWidget); + Menu->removeAction(TabsAction); + if (toIndex >= Menu->actions().count()) { + Menu->addAction(TabsAction); + } else { + Menu->insertAction(Menu->actions().at(toIndex), TabsAction); + } + + // now reorder contents and title bars + QLayoutItem *liFrom = nullptr; + liFrom = d->TabsLayout->takeAt(fromIndex); + d->TabsLayout->insertItem(toIndex, liFrom); + liFrom = d->ContentsLayout->takeAt(fromIndex); + d->ContentsLayout->insertWidget(toIndex, liFrom->widget()); + delete liFrom; + } + + //============================================================================ + void CDockAreaWidget::onTabsMenuActionTriggered(QAction *Action) { + int Index = d->TabsMenuButton->menu()->actions().indexOf(Action); + setCurrentIndex(Index); + } + + //============================================================================ + void CDockAreaWidget::updateDockArea() { d->updateTabBar(); } + + //============================================================================ + void CDockAreaWidget::saveState(QXmlStreamWriter &s) const { + s.writeStartElement("DockAreaWidget"); + s.writeAttribute("Tabs", QString::number(d->ContentsLayout->count())); + s.writeAttribute("CurrentIndex", QString::number(d->ContentsLayout->currentIndex())); + for (int i = 0; i < d->ContentsLayout->count(); ++i) { + dockWidget(i)->saveState(s); + } + s.writeEndElement(); + } + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockAreaWidget.cpp diff --git a/editor/widgets/dock/DockAreaWidget.h b/editor/widgets/dock/DockAreaWidget.h new file mode 100644 index 00000000..ffef1a58 --- /dev/null +++ b/editor/widgets/dock/DockAreaWidget.h @@ -0,0 +1,193 @@ +#ifndef DockAreaWidgetH +#define DockAreaWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockAreaWidget.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of CDockAreaWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +class QXmlStreamWriter; + +namespace ads { + struct DockAreaWidgetPrivate; + class CDockManager; + class CDockContainerWidget; + class CDockWidget; + + /** + * DockAreaWidget manages multiple instances of DckWidgets. + * It displays a title tab, which is clickable and will switch to + * the contents associated to the title when clicked. + */ + class CDockAreaWidget : public QFrame { + Q_OBJECT + private: + DockAreaWidgetPrivate *d; ///< private data (pimpl) + friend struct DockAreaWidgetPrivate; + + private slots: + void onDockWidgetTitleClicked(); + void onTabsMenuActionTriggered(QAction *Action); + void onCloseButtonClicked(); + + public: + /** + * Default Constructor + */ + CDockAreaWidget(CDockManager *DockManager, CDockContainerWidget *parent); + + /** + * Virtual Destructor + */ + virtual ~CDockAreaWidget(); + + /** + * Returns the dock manager object this dock area belongs to + */ + CDockManager *dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if there is no + */ + CDockContainerWidget *dockContainer() const; + + /** + * Inserts a dock widget into dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs. + * The index indicates the index of the new dockwidget in the tabbar and + * in the stacked layout. If the Activate parameter is true, the new + * DockWidget will be the active one in the stacked layout + */ + void insertDockWidget(int index, CDockWidget *DockWidget, bool Activate = true); + + /** + * Add a new dock widget to dock area. + * All dockwidgets in the dock area tabified in a stacked layout with tabs + */ + void addDockWidget(CDockWidget *DockWidget); + + /** + * Removes the given dock widget from the dock area + */ + void removeDockWidget(CDockWidget *DockWidget); + + /** + * Returns the rectangle of the title area + */ + QRect titleAreaGeometry() const; + + /** + * Returns the rectangle of the content + */ + QRect contentAreaGeometry() const; + + /** + * Returns the tab index of the given DockWidget + */ + int tabIndex(CDockWidget *DockWidget); + + /** + * Returns the index of contents of the title widget that is located at + * mouse position pos + */ + int indexOfContentByTitlePos(const QPoint &pos, QWidget *exclude = nullptr) const; + + /** + * Returns a list of all dock widgets in this dock area. + * This list contains open and closed dock widgets. + */ + QList dockWidgets() const; + + /** + * Returns a list of dock widgets that are not closed + */ + QList openedDockWidgets() const; + + /** + * Returns the number of dock widgets in this area + */ + int count() const; + + /** + * Returns a dock widget by its index + */ + CDockWidget *dockWidget(int Index) const; + + /** + * Reorder the index position of DockWidget at fromIndx to toIndex. + */ + void reorderDockWidget(int fromIndex, int toIndex); + + /** + * Returns the index of the current active dock widget + */ + int currentIndex() const; + + /** + * Returns the current active dock widget + */ + CDockWidget *currentDockWidget() const; + + /** + * Shows the tab with tghe given dock widget + */ + void setCurrentDockWidget(CDockWidget *DockWidget); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &Stream) const; + + public slots: + /** + * This sets the index position of the current tab page. + */ + void setCurrentIndex(int index); + + /** + * Updates the dock area layout and components visibility + */ + void updateDockArea(); + + signals: + /** + * This signal is emitted when user clicks on a tab at an index. + */ + void tabBarClicked(int index); + + /** + * This signal is emitted when the tab bar's current tab changes. The new + * current has the given index, or -1 if there isn't a new one + * @param index + */ + void currentChanged(int index); + }; // class DockAreaWidget +} +// namespace ads +//----------------------------------------------------------------------------- +#endif // DockAreaWidgetH diff --git a/editor/widgets/dock/DockContainerWidget.cpp b/editor/widgets/dock/DockContainerWidget.cpp new file mode 100644 index 00000000..f34302dc --- /dev/null +++ b/editor/widgets/dock/DockContainerWidget.cpp @@ -0,0 +1,800 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockContainerWidget.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of CDockContainerWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockContainerWidget.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "DockOverlay.h" +#include "DockSplitter.h" +#include "DockStateSerialization.h" +#include "DockWidget.h" +#include "FloatingDockContainer.h" +#include "ads_globals.h" + +namespace ads { + static unsigned int zOrderCounter = 0; + + /** + * Helper function to ease insertion of dock area into splitter + */ + static void insertWidgetIntoSplitter(QSplitter *Splitter, QWidget *widget, bool Append) { + if (Append) { + Splitter->addWidget(widget); + } else { + Splitter->insertWidget(0, widget); + } + } + + /** + * Private data class of CDockContainerWidget class (pimpl) + */ + struct DockContainerWidgetPrivate { + CDockContainerWidget *_this; + QPointer DockManager; + unsigned int zOrderIndex = 0; + QList DockAreas; + QGridLayout *Layout = nullptr; + QSplitter *RootSplitter; + bool isFloating = false; + + /** + * Private data constructor + */ + DockContainerWidgetPrivate(CDockContainerWidget *_public); + + /** + * Adds dock widget to container and returns the dock area that contains + * the inserted dock widget + */ + CDockAreaWidget *dockWidgetIntoContainer(DockWidgetArea area, CDockWidget *Dockwidget); + + /** + * Adds dock widget to a existing DockWidgetArea + */ + CDockAreaWidget *dockWidgetIntoDockArea(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *TargetDockArea); + + /** + * Add dock area to this container + */ + void addDockArea(CDockAreaWidget *NewDockWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Drop floating widget into container + */ + void dropIntoContainer(CFloatingDockContainer *FloatingWidget, DockWidgetArea area); + + /** + * Drop floating widget into dock area + */ + void dropIntoSection(CFloatingDockContainer *FloatingWidget, CDockAreaWidget *TargetArea, DockWidgetArea area); + + /** + * Adds new dock areas to the internal dock area list + */ + void addDockAreasToList(const QList NewDockAreas); + + /** + * Save state of child nodes + */ + void saveChildNodesState(QXmlStreamWriter &Stream, QWidget *Widget); + + /** + * Restore state of child nodes. + * \param[in] Stream The data stream that contains the serialized state + * \param[out] CreatedWidget The widget created from parsed data or 0 if + * the parsed widget was an empty splitter + * \param[in] Testing If Testing is true, only the stream data is + * parsed without modifiying anything. + */ + bool restoreChildNodes(QXmlStreamReader &Stream, QWidget *&CreatedWidget, bool Testing); + + /** + * Restores a splitter. + * \see restoreChildNodes() for details + */ + bool restoreSplitter(QXmlStreamReader &Stream, QWidget *&CreatedWidget, bool Testing); + + /** + * Restores a dock area. + * \see restoreChildNodes() for details + */ + bool restoreDockArea(QXmlStreamReader &Stream, QWidget *&CreatedWidget, bool Testing); + + /** + * Helper function for recursive dumping of layout + */ + void dumpRecursive(int level, QWidget *widget); + }; // struct DockContainerWidgetPrivate + + //============================================================================ + DockContainerWidgetPrivate::DockContainerWidgetPrivate(CDockContainerWidget *_public) : _this(_public) {} + + //============================================================================ + void DockContainerWidgetPrivate::dropIntoContainer(CFloatingDockContainer *FloatingWidget, DockWidgetArea area) { + auto InsertParam = internal::dockAreaInsertParameters(area); + auto NewDockAreas = + FloatingWidget->dockContainer()->findChildren(QString(), Qt::FindChildrenRecursively); + CDockWidget *DockWidget = FloatingWidget->dockContainer()->findChild(); + QSplitter *Splitter = RootSplitter; + + if (DockAreas.count() <= 1) { + Splitter->setOrientation(InsertParam.orientation()); + } else if (Splitter->orientation() != InsertParam.orientation()) { + QSplitter *NewSplitter = internal::newSplitter(InsertParam.orientation()); + QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + Splitter = NewSplitter; + delete li; + } + + // Now we can insert the floating widget content into this container + auto FloatingSplitter = FloatingWidget->dockContainer()->rootSplitter(); + if (FloatingSplitter->count() == 1) { + insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append()); + } else if (FloatingSplitter->orientation() == InsertParam.orientation()) { + while (FloatingSplitter->count()) { + insertWidgetIntoSplitter(Splitter, FloatingSplitter->widget(0), InsertParam.append()); + } + } else { + insertWidgetIntoSplitter(Splitter, FloatingSplitter, InsertParam.append()); + } + + RootSplitter = Splitter; + addDockAreasToList(NewDockAreas); + FloatingWidget->deleteLater(); + if (DockWidget) { + DockWidget->toggleView(true); + } + _this->dumpLayout(); + } + + //============================================================================ + void DockContainerWidgetPrivate::dropIntoSection(CFloatingDockContainer *FloatingWidget, + CDockAreaWidget *TargetArea, DockWidgetArea area) { + CDockContainerWidget *FloatingContainer = FloatingWidget->dockContainer(); + if (area == CenterDockWidgetArea) { + auto NewDockWidgets = + FloatingContainer->findChildren(QString(), Qt::FindChildrenRecursively); + for (auto DockWidget : NewDockWidgets) { + TargetArea->insertDockWidget(0, DockWidget, false); + } + TargetArea->setCurrentIndex(0); // make the topmost widget active + FloatingWidget->deleteLater(); + TargetArea->updateDockArea(); + return; + } + + auto InsertParam = internal::dockAreaInsertParameters(area); + auto NewDockAreas = + FloatingWidget->dockContainer()->findChildren(QString(), Qt::FindChildrenRecursively); + QSplitter *TargetAreaSplitter = internal::findParent(TargetArea); + + if (!TargetAreaSplitter) { + QSplitter *Splitter = internal::newSplitter(InsertParam.orientation()); + Layout->replaceWidget(TargetArea, Splitter); + Splitter->addWidget(TargetArea); + TargetAreaSplitter = Splitter; + } + + int AreaIndex = TargetAreaSplitter->indexOf(TargetArea); + auto Widget = FloatingWidget->dockContainer()->findChild(QString(), Qt::FindDirectChildrenOnly); + auto FloatingSplitter = dynamic_cast(Widget); + + if (TargetAreaSplitter->orientation() == InsertParam.orientation()) { + if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { + TargetAreaSplitter->insertWidget(AreaIndex + InsertParam.insertOffset(), Widget); + } else { + int InsertIndex = AreaIndex + InsertParam.insertOffset(); + while (FloatingSplitter->count()) { + TargetAreaSplitter->insertWidget(InsertIndex++, FloatingSplitter->widget(0)); + } + } + } else { + QSplitter *NewSplitter = internal::newSplitter(InsertParam.orientation()); + if ((FloatingSplitter->orientation() != InsertParam.orientation()) && FloatingSplitter->count() > 1) { + NewSplitter->addWidget(Widget); + } else { + while (FloatingSplitter->count()) { + NewSplitter->addWidget(FloatingSplitter->widget(0)); + } + } + + insertWidgetIntoSplitter(NewSplitter, TargetArea, !InsertParam.append()); + TargetAreaSplitter->insertWidget(AreaIndex, NewSplitter); + } + + FloatingWidget->deleteLater(); + addDockAreasToList(NewDockAreas); + _this->dumpLayout(); + } + + //============================================================================ + void DockContainerWidgetPrivate::addDockAreasToList(const QList NewDockAreas) { + int CountBefore = DockAreas.count(); + int NewAreaCount = NewDockAreas.count(); + DockAreas.append(NewDockAreas); + + // We need to ensure, that the dock area title bar is visible. The title bar + // is invisible, if the dock are is a single dock area in a floating widget. + if (1 == CountBefore) { + DockAreas.at(0)->updateDockArea(); + } + + if (1 == NewAreaCount) { + DockAreas.last()->updateDockArea(); + } + + emit _this->dockAreasAdded(); + } + + //============================================================================ + void DockContainerWidgetPrivate::saveChildNodesState(QXmlStreamWriter &s, QWidget *Widget) { + QSplitter *Splitter = dynamic_cast(Widget); + if (Splitter) { + s.writeStartElement("Splitter"); + s.writeAttribute("Orientation", QString::number(Splitter->orientation())); + s.writeAttribute("Count", QString::number(Splitter->count())); + for (int i = 0; i < Splitter->count(); ++i) { + saveChildNodesState(s, Splitter->widget(i)); + } + + s.writeStartElement("Sizes"); + for (auto Size : Splitter->sizes()) { + s.writeCharacters(QString::number(Size) + " "); + } + s.writeEndElement(); + s.writeEndElement(); + } else { + CDockAreaWidget *DockArea = dynamic_cast(Widget); + if (DockArea) { + DockArea->saveState(s); + } + } + } + + //============================================================================ + bool DockContainerWidgetPrivate::restoreSplitter(QXmlStreamReader &s, QWidget *&CreatedWidget, bool Testing) { + bool Ok; + int Orientation = s.attributes().value("Orientation").toInt(&Ok); + if (!Ok) { + return false; + } + + int WidgetCount = s.attributes().value("Count").toInt(&Ok); + if (!Ok) { + return false; + } + QSplitter *Splitter = nullptr; + if (!Testing) { + Splitter = internal::newSplitter((Qt::Orientation) Orientation); + } + bool Visible = false; + QList Sizes; + while (s.readNextStartElement()) { + QWidget *ChildNode = nullptr; + bool Result = true; + if (s.name() == "Splitter") { + Result = restoreSplitter(s, ChildNode, Testing); + } else if (s.name() == "DockAreaWidget") { + Result = restoreDockArea(s, ChildNode, Testing); + } else if (s.name() == "Sizes") { + QString sSizes = s.readElementText().trimmed(); + QTextStream TextStream(&sSizes); + while (!TextStream.atEnd()) { + int value; + TextStream >> value; + Sizes.append(value); + } + } else { + s.skipCurrentElement(); + } + + if (!Result) { + return false; + } + + if (Testing || !ChildNode) { + continue; + } + + Splitter->addWidget(ChildNode); + Visible |= ChildNode->isVisibleTo(Splitter); + } + + if (Sizes.count() != WidgetCount) { + return false; + } + + if (!Testing) { + if (!Splitter->count()) { + delete Splitter; + Splitter = nullptr; + } else { + Splitter->setSizes(Sizes); + Splitter->setVisible(Visible); + } + CreatedWidget = Splitter; + } else { + CreatedWidget = nullptr; + } + + return true; + } + + //============================================================================ + bool DockContainerWidgetPrivate::restoreDockArea(QXmlStreamReader &s, QWidget *&CreatedWidget, bool Testing) { + bool Ok; + s.attributes().value("Tabs").toInt(&Ok); + if (!Ok) { + return false; + } + + int CurrentIndex = s.attributes().value("CurrentIndex").toInt(&Ok); + if (!Ok) { + return false; + } + + CDockAreaWidget *DockArea = nullptr; + if (!Testing) { + DockArea = new CDockAreaWidget(DockManager, _this); + } + + while (s.readNextStartElement()) { + if (s.name() != "DockWidget") { + continue; + } + + auto ObjectName = s.attributes().value("ObjectName"); + if (ObjectName.isEmpty()) { + return false; + } + + bool Closed = s.attributes().value("Closed").toInt(&Ok); + if (!Ok) { + return false; + } + + s.skipCurrentElement(); + CDockWidget *DockWidget = DockManager->findDockWidget(ObjectName.toString()); + if (!DockWidget || Testing) { + continue; + } + + DockArea->addDockWidget(DockWidget); + + DockArea->hide(); + DockWidget->setToggleViewActionChecked(!Closed); + DockWidget->setProperty("closed", Closed); + DockWidget->setProperty("dirty", false); + } + + if (Testing) { + return true; + } + + if (!DockArea->count()) { + delete DockArea; + DockArea = nullptr; + } else { + DockArea->setProperty("currentIndex", CurrentIndex); + DockAreas.append(DockArea); + } + + CreatedWidget = DockArea; + return true; + } + + //============================================================================ + bool DockContainerWidgetPrivate::restoreChildNodes(QXmlStreamReader &s, QWidget *&CreatedWidget, bool Testing) { + bool Result = true; + while (s.readNextStartElement()) { + if (s.name() == "Splitter") { + Result = restoreSplitter(s, CreatedWidget, Testing); + } else if (s.name() == "DockAreaWidget") { + Result = restoreDockArea(s, CreatedWidget, Testing); + } else { + s.skipCurrentElement(); + } + } + + return Result; + } + + //============================================================================ + CDockAreaWidget *DockContainerWidgetPrivate::dockWidgetIntoContainer(DockWidgetArea area, CDockWidget *Dockwidget) { + CDockAreaWidget *NewDockArea = new CDockAreaWidget(DockManager, _this); + NewDockArea->addDockWidget(Dockwidget); + addDockArea(NewDockArea, area); + return NewDockArea; + } + + //============================================================================ + void DockContainerWidgetPrivate::addDockArea(CDockAreaWidget *NewDockArea, DockWidgetArea area) { + auto InsertParam = internal::dockAreaInsertParameters(area); + // As long as we have only one dock area in the splitter we can adjust + // its orientation + if (DockAreas.count() <= 1) { + RootSplitter->setOrientation(InsertParam.orientation()); + } + + QSplitter *Splitter = RootSplitter; + if (Splitter->orientation() == InsertParam.orientation()) { + insertWidgetIntoSplitter(Splitter, NewDockArea, InsertParam.append()); + } else { + QSplitter *NewSplitter = internal::newSplitter(InsertParam.orientation()); + if (InsertParam.append()) { + QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + NewSplitter->addWidget(NewDockArea); + delete li; + } else { + NewSplitter->addWidget(NewDockArea); + QLayoutItem *li = Layout->replaceWidget(Splitter, NewSplitter); + NewSplitter->addWidget(Splitter); + delete li; + } + RootSplitter = NewSplitter; + } + + DockAreas.append(NewDockArea); + NewDockArea->updateDockArea(); + emit _this->dockAreasAdded(); + } + + //============================================================================ + void DockContainerWidgetPrivate::dumpRecursive(int level, QWidget *widget) { +#if defined(QT_DEBUG) + QSplitter *Splitter = dynamic_cast(widget); + QByteArray buf; + buf.fill(' ', level * 4); + if (Splitter) { + for (int i = 0; i < Splitter->count(); ++i) { + dumpRecursive(level + 1, Splitter->widget(i)); + } + } else { + CDockAreaWidget *DockArea = dynamic_cast(widget); + if (!DockArea) { + return; + } + } +#else + Q_UNUSED(level); + Q_UNUSED(widget); +#endif + } + + //============================================================================ + CDockAreaWidget *DockContainerWidgetPrivate::dockWidgetIntoDockArea(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *TargetDockArea) { + if (CenterDockWidgetArea == area) { + TargetDockArea->addDockWidget(Dockwidget); + return TargetDockArea; + } + + CDockAreaWidget *NewDockArea = new CDockAreaWidget(DockManager, _this); + NewDockArea->addDockWidget(Dockwidget); + auto InsertParam = internal::dockAreaInsertParameters(area); + + QSplitter *TargetAreaSplitter = internal::findParent(TargetDockArea); + int index = TargetAreaSplitter->indexOf(TargetDockArea); + if (TargetAreaSplitter->orientation() == InsertParam.orientation()) { + TargetAreaSplitter->insertWidget(index + InsertParam.insertOffset(), NewDockArea); + } else { + QSplitter *NewSplitter = internal::newSplitter(InsertParam.orientation()); + NewSplitter->addWidget(TargetDockArea); + insertWidgetIntoSplitter(NewSplitter, NewDockArea, InsertParam.append()); + TargetAreaSplitter->insertWidget(index, NewSplitter); + } + + DockAreas.append(NewDockArea); + emit _this->dockAreasAdded(); + return NewDockArea; + } + + //============================================================================ + CDockContainerWidget::CDockContainerWidget(CDockManager *DockManager, QWidget *parent) + : QFrame(parent), d(new DockContainerWidgetPrivate(this)) { + d->isFloating = dynamic_cast(parent) != 0; + + // setStyleSheet("background: green;"); + d->DockManager = DockManager; + if (DockManager != this) { + d->DockManager->registerDockContainer(this); + } + + d->Layout = new QGridLayout(); + d->Layout->setContentsMargins(0, 1, 0, 1); + d->Layout->setSpacing(0); + setLayout(d->Layout); + + d->RootSplitter = internal::newSplitter(Qt::Horizontal); + d->Layout->addWidget(d->RootSplitter); + } + + //============================================================================ + CDockContainerWidget::~CDockContainerWidget() { + if (d->DockManager) { + d->DockManager->removeDockContainer(this); + } + delete d; + } + + //============================================================================ + CDockAreaWidget *CDockContainerWidget::addDockWidget(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *DockAreaWidget) { + CDockAreaWidget *OldDockArea = Dockwidget->dockAreaWidget(); + if (OldDockArea) { + OldDockArea->removeDockWidget(Dockwidget); + } + + Dockwidget->setDockManager(d->DockManager); + if (DockAreaWidget) { + return d->dockWidgetIntoDockArea(area, Dockwidget, DockAreaWidget); + } else { + return d->dockWidgetIntoContainer(area, Dockwidget); + } + } + + //============================================================================ + unsigned int CDockContainerWidget::zOrderIndex() const { return d->zOrderIndex; } + + //============================================================================ + bool CDockContainerWidget::isInFrontOf(CDockContainerWidget *Other) const { + return this->zOrderIndex() > Other->zOrderIndex(); + } + + //============================================================================ + bool CDockContainerWidget::event(QEvent *e) { + bool Result = QWidget::event(e); + if (e->type() == QEvent::WindowActivate) { + d->zOrderIndex = ++zOrderCounter; + } else if (e->type() == QEvent::Show && !d->zOrderIndex) { + d->zOrderIndex = ++zOrderCounter; + } + + return Result; + } + + //============================================================================ + void CDockContainerWidget::addDockArea(CDockAreaWidget *DockAreaWidget, DockWidgetArea area) { + CDockContainerWidget *Container = DockAreaWidget->dockContainer(); + if (Container && Container != this) { + Container->removeDockArea(DockAreaWidget); + } + + d->addDockArea(DockAreaWidget, area); + } + + //============================================================================ + void CDockContainerWidget::removeDockArea(CDockAreaWidget *area) { + d->DockAreas.removeAll(area); + CDockSplitter *Splitter = internal::findParent(area); + + // Remove are from parent splitter and hide splitter if it has no visible + // content + area->setParent(0); + Splitter->setVisible(Splitter->hasVisibleContent()); + + // If splitter has more than 1 widgets, we are finished and can leave + if (Splitter->count() > 1) { + goto emitAndExit; + } + + // If this is the RootSplitter we need to remove empty splitters to + // avoid too many empty splitters + if (Splitter == d->RootSplitter) { + // If splitter is empty, we are finished + if (!Splitter->count()) { + Splitter->hide(); + goto emitAndExit; + } + + QWidget *widget = Splitter->widget(0); + QSplitter *ChildSplitter = dynamic_cast(widget); + // If the one and only content widget of the splitter is not a splitter + // then we are finished + if (!ChildSplitter) { + goto emitAndExit; + } + + // We replace the superfluous RootSplitter with the ChildSplitter + ChildSplitter->setParent(0); + QLayoutItem *li = d->Layout->replaceWidget(Splitter, ChildSplitter); + d->RootSplitter = ChildSplitter; + delete li; + } else if (Splitter->count() == 1) { + QWidget *widget = Splitter->widget(0); + widget->setParent(this); + QSplitter *ParentSplitter = internal::findParent(Splitter); + internal::replaceSplitterWidget(ParentSplitter, Splitter, widget); + } + + delete Splitter; + + emitAndExit: + dumpLayout(); + emit dockAreasRemoved(); + } + + //============================================================================ + CDockAreaWidget *CDockContainerWidget::dockAreaAt(const QPoint &GlobalPos) const { + for (const auto &DockArea : d->DockAreas) { + if (DockArea->isVisible() && DockArea->rect().contains(DockArea->mapFromGlobal(GlobalPos))) { + return DockArea; + } + } + + return 0; + } + + //============================================================================ + CDockAreaWidget *CDockContainerWidget::dockArea(int Index) const { + return (Index < dockAreaCount()) ? d->DockAreas[Index] : 0; + } + + //============================================================================ + bool CDockContainerWidget::isFloating() const { return d->isFloating; } + + //============================================================================ + int CDockContainerWidget::dockAreaCount() const { return d->DockAreas.count(); } + + //============================================================================ + int CDockContainerWidget::visibleDockAreaCount() const { + // TODO Cache or precalculate this to speed it up because it is used during + // movement of floating widget + int Result = 0; + for (auto DockArea : d->DockAreas) { + Result += DockArea->isVisible() ? 1 : 0; + } + + return Result; + } + + //============================================================================ + void CDockContainerWidget::dropFloatingWidget(CFloatingDockContainer *FloatingWidget, const QPoint &TargetPos) { + CDockAreaWidget *DockArea = dockAreaAt(TargetPos); + auto dropArea = InvalidDockWidgetArea; + auto ContainerDropArea = d->DockManager->containerOverlay()->dropAreaUnderCursor(); + if (DockArea) { + auto dropOverlay = d->DockManager->dockAreaOverlay(); + dropOverlay->setAllowedAreas(AllDockAreas); + dropArea = dropOverlay->showOverlay(DockArea); + if (ContainerDropArea != InvalidDockWidgetArea && ContainerDropArea != dropArea) { + dropArea = InvalidDockWidgetArea; + } + + if (dropArea != InvalidDockWidgetArea) { + d->dropIntoSection(FloatingWidget, DockArea, dropArea); + } + } + + // mouse is over container + if (InvalidDockWidgetArea == dropArea) { + dropArea = ContainerDropArea; + if (dropArea != InvalidDockWidgetArea) { + d->dropIntoContainer(FloatingWidget, dropArea); + } + } + } + + //============================================================================ + QList CDockContainerWidget::openedDockAreas() const { + QList Result; + for (auto DockArea : d->DockAreas) { + if (DockArea->isVisible()) { + Result.append(DockArea); + } + } + + return Result; + } + + //============================================================================ + void CDockContainerWidget::saveState(QXmlStreamWriter &s) const { + s.writeStartElement("DockContainerWidget"); + s.writeAttribute("Floating", QString::number(isFloating() ? 1 : 0)); + if (isFloating()) { + CFloatingDockContainer *FloatingWidget = internal::findParent(this); + QByteArray Geometry = FloatingWidget->saveGeometry(); + s.writeTextElement("Geometry", Geometry.toHex(' ')); + } + d->saveChildNodesState(s, d->RootSplitter); + s.writeEndElement(); + } + + //============================================================================ + bool CDockContainerWidget::restoreState(QXmlStreamReader &s, bool Testing) { + bool IsFloating = s.attributes().value("Floating").toInt(); + + QWidget *NewRootSplitter{}; + if (!Testing) { + d->DockAreas.clear(); + } + + if (IsFloating) { + if (!s.readNextStartElement() || s.name() != "Geometry") { + return false; + } + + QByteArray GeometryString = s.readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).toLocal8Bit(); + QByteArray Geometry = QByteArray::fromHex(GeometryString); + std::cout << "Geometry: " << Geometry.toHex(' ').toStdString() << std::endl; + if (Geometry.isEmpty()) { + return false; + } + + if (!Testing) { + CFloatingDockContainer *FloatingWidget = internal::findParent(this); + FloatingWidget->restoreGeometry(Geometry); + } + } + + if (!d->restoreChildNodes(s, NewRootSplitter, Testing)) { + return false; + } + + if (Testing) { + return true; + } + + // If the root splitter is empty, rostoreChildNodes returns a 0 pointer + // and we need to create a new empty root splitter + if (!NewRootSplitter) { + NewRootSplitter = internal::newSplitter(Qt::Horizontal); + } + + d->Layout->replaceWidget(d->RootSplitter, NewRootSplitter); + QSplitter *OldRoot = d->RootSplitter; + d->RootSplitter = dynamic_cast(NewRootSplitter); + OldRoot->deleteLater(); + + return true; + } + + //============================================================================ + QSplitter *CDockContainerWidget::rootSplitter() const { return d->RootSplitter; } + + //============================================================================ + void CDockContainerWidget::dumpLayout() {} + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockContainerWidget.cpp diff --git a/editor/widgets/dock/DockContainerWidget.h b/editor/widgets/dock/DockContainerWidget.h new file mode 100644 index 00000000..7cf43049 --- /dev/null +++ b/editor/widgets/dock/DockContainerWidget.h @@ -0,0 +1,179 @@ +#ifndef DockContainerWidgetH +#define DockContainerWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockContainerWidget.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of CDockContainerWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "ads_globals.h" + +class QXmlStreamWriter; +class QXmlStreamReader; + +namespace ads { + struct DockContainerWidgetPrivate; + class CDockAreaWidget; + class CDockWidget; + class CDockManager; + class CFloatingDockContainer; + + /** + * Container that manages a number of dock areas with single dock widgets + * or tabyfied dock widgets in each area + */ + class CDockContainerWidget : public QFrame { + Q_OBJECT + private: + DockContainerWidgetPrivate *d; ///< private data (pimpl) + friend struct DockContainerWidgetPrivate; + + protected: + /** + * Handles activation events to update zOrderIndex + */ + virtual bool event(QEvent *e) override; + + /** + * Access function for the internal root splitter + */ + QSplitter *rootSplitter() const; + + public: + /** + * Default Constructor + */ + CDockContainerWidget(CDockManager *DockManager, QWidget *parent = 0); + + /** + * Virtual Destructor + */ + virtual ~CDockContainerWidget(); + + /** + * Drop floating widget into the container + */ + void dropFloatingWidget(CFloatingDockContainer *FloatingWidget, const QPoint &TargetPos); + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. + * \return Returns the dock area widget that contains the new DockWidget + */ + CDockAreaWidget *addDockWidget(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *DockAreaWidget = nullptr); + + /** + * Adds the given dock area to this container widget + */ + void addDockArea(CDockAreaWidget *DockAreaWidget, DockWidgetArea area = CenterDockWidgetArea); + + /** + * Removes the given dock area from this container + */ + void removeDockArea(CDockAreaWidget *area); + + /** + * Returns the current zOrderIndex + */ + virtual unsigned int zOrderIndex() const; + + /** + * This function returns true if this container widgets z order index is + * higher than the index of the container widget given in Other parameter + */ + bool isInFrontOf(CDockContainerWidget *Other) const; + + /** + * Returns the dock area at teh given global position or 0 if there is no + * dock area at this position + */ + CDockAreaWidget *dockAreaAt(const QPoint &GlobalPos) const; + + /** + * Returns the dock area at the given Index or 0 if the index is out of + * range + */ + CDockAreaWidget *dockArea(int Index) const; + + /** + * Returns the list of dock areas that are not closed + * If all dock widgets in a dock area are closed, the dock area will be closed + */ + QList openedDockAreas() const; + + /** + * Returns the number of dock areas in this container + */ + int dockAreaCount() const; + + /** + * Returns the number of visible dock areas + */ + int visibleDockAreaCount() const; + + /** + * This function returns true, if this container is in a floating widget + */ + bool isFloating() const; + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &Stream) const; + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(QXmlStreamReader &Stream, bool Testing); + + /** + * Dumps the layout for debugging purposes + */ + void dumpLayout(); + + signals: + /** + * This signal is emitted if one or multiple dock areas has been added to + * the internal list of dock areas. + * If multiple dock areas are inserted, this signal is emitted only once + */ + void dockAreasAdded(); + + /** + * This signal is emitted if one or multiple dock areas has been removed + */ + void dockAreasRemoved(); + }; // class DockContainerWidget +} // namespace ads +//----------------------------------------------------------------------------- +#endif // DockContainerWidgetH diff --git a/editor/widgets/dock/DockManager.cpp b/editor/widgets/dock/DockManager.cpp new file mode 100644 index 00000000..a20afcfa --- /dev/null +++ b/editor/widgets/dock/DockManager.cpp @@ -0,0 +1,359 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockManager.cpp +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Implementation of CDockManager class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockManager.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockOverlay.h" +#include "DockStateSerialization.h" +#include "DockWidget.h" +#include "DockWidgetTitleBar.h" +#include "FloatingDockContainer.h" +#include "ads_globals.h" + +namespace ads { + /** + * Private data class of CDockManager class (pimpl) + */ + struct DockManagerPrivate { + CDockManager *_this; + QList FloatingWidgets; + QList Containers; + CDockOverlay *ContainerOverlay; + CDockOverlay *DockAreaOverlay; + QMap DockWidgetsMap; + QMap Perspectives; + + /** + * Private data constructor + */ + DockManagerPrivate(CDockManager *_public); + + /** + * Checks if the given data stream is a valid docking system state + * file. + */ + bool checkFormat(const QByteArray &state, int version); + + /** + * Restores the state + */ + bool restoreState(const QByteArray &state, int version, bool Testing = internal::Restore); + + /** + * Restores the container with the given index + */ + bool restoreContainer(int Index, QXmlStreamReader &stream, bool Testing); + + /** + * Loads the stylesheet + */ + void loadStylesheet(); + }; + // struct DockManagerPrivate + + //============================================================================ + DockManagerPrivate::DockManagerPrivate(CDockManager *_public) : _this(_public) {} + + //============================================================================ + void DockManagerPrivate::loadStylesheet() { + QString Result; + QFile StyleSheetFile(":/styles/Dock.qss"); + StyleSheetFile.open(QIODevice::ReadOnly); + QTextStream StyleSheetStream(&StyleSheetFile); + Result = StyleSheetStream.readAll(); + StyleSheetFile.close(); + _this->setStyleSheet(Result); + } + + //============================================================================ + bool DockManagerPrivate::restoreContainer(int Index, QXmlStreamReader &stream, bool Testing) { + if (Testing) { + Index = 0; + } + + if (Index >= Containers.count()) { + CFloatingDockContainer *FloatingWidget = new CFloatingDockContainer(_this); + return FloatingWidget->restoreState(stream, Testing); + } else { + return Containers[Index]->restoreState(stream, Testing); + } + } + + //============================================================================ + bool DockManagerPrivate::checkFormat(const QByteArray &state, int version) { + return restoreState(state, version, internal::RestoreTesting); + } + + //============================================================================ + bool DockManagerPrivate::restoreState(const QByteArray &state, int version, bool Testing) { + if (state.isEmpty()) { + return false; + } + QXmlStreamReader s(state); + s.readNextStartElement(); + if (s.name() != "QtAdvancedDockingSystem") { + return false; + } + bool ok; + int v = s.attributes().value("Version").toInt(&ok); + if (!ok || v != version) { + return false; + } + + bool Result = true; + int DockContainerCount = 0; + while (s.readNextStartElement()) { + if (s.name() == "DockContainerWidget") { + Result = restoreContainer(DockContainerCount, s, Testing); + if (!Result) { + break; + } + DockContainerCount++; + } + } + + if (!Testing) { + // Delete remaining empty floating widgets + int FloatingWidgetIndex = DockContainerCount - 1; + int DeleteCount = FloatingWidgets.count() - FloatingWidgetIndex; + for (int i = 0; i < DeleteCount; ++i) { + FloatingWidgets[FloatingWidgetIndex + i]->deleteLater(); + } + } + + return Result; + } + + //============================================================================ + CDockManager::CDockManager(QWidget *parent) : CDockContainerWidget(this, parent), d(new DockManagerPrivate(this)) { + QMainWindow *MainWindow = dynamic_cast(parent); + if (MainWindow) { + MainWindow->setCentralWidget(this); + } + + d->DockAreaOverlay = new CDockOverlay(this, CDockOverlay::ModeDockAreaOverlay); + d->ContainerOverlay = new CDockOverlay(this, CDockOverlay::ModeContainerOverlay); + d->Containers.append(this); + d->loadStylesheet(); + } + + //============================================================================ + CDockManager::~CDockManager() { + auto FloatingWidgets = d->FloatingWidgets; + for (auto FloatingWidget : FloatingWidgets) { + delete FloatingWidget; + } + delete d; + } + + //============================================================================ + void CDockManager::registerFloatingWidget(CFloatingDockContainer *FloatingWidget) { + d->FloatingWidgets.append(FloatingWidget); + } + + //============================================================================ + void CDockManager::removeFloatingWidget(CFloatingDockContainer *FloatingWidget) { + d->FloatingWidgets.removeAll(FloatingWidget); + } + + //============================================================================ + void CDockManager::registerDockContainer(CDockContainerWidget *DockContainer) { + d->Containers.append(DockContainer); + } + + //============================================================================ + void CDockManager::removeDockContainer(CDockContainerWidget *DockContainer) { + if (this != DockContainer) { + d->Containers.removeAll(DockContainer); + } + } + + //============================================================================ + CDockOverlay *CDockManager::containerOverlay() const { return d->ContainerOverlay; } + + //============================================================================ + CDockOverlay *CDockManager::dockAreaOverlay() const { return d->DockAreaOverlay; } + + //============================================================================ + const QList CDockManager::dockContainers() const { return d->Containers; } + + //============================================================================ + const QList CDockManager::floatingWidgets() const { return d->FloatingWidgets; } + + //============================================================================ + unsigned int CDockManager::zOrderIndex() const { return 0; } + + //============================================================================ + QByteArray CDockManager::saveState(int version) const { + QByteArray xmldata; + QXmlStreamWriter s(&xmldata); + s.setAutoFormatting(true); + s.writeStartDocument(); + s.writeStartElement("QtAdvancedDockingSystem"); + s.writeAttribute("Version", QString::number(version)); + s.writeAttribute("DockContainers", QString::number(d->Containers.count())); + for (auto Container : d->Containers) { + Container->saveState(s); + } + + s.writeEndElement(); + s.writeEndDocument(); + + std::cout << xmldata.toStdString() << std::endl; + return xmldata; + } + + //============================================================================ + bool CDockManager::restoreState(const QByteArray &state, int version) { + if (!d->checkFormat(state, version)) { + return false; + } + + for (auto DockWidget : d->DockWidgetsMap) { + DockWidget->setProperty("dirty", true); + } + + if (!d->restoreState(state, version)) { + return false; + } + + // All dock widgets, that have not been processed in the restore state + // function are invisible to the user now and have no assigned dock area + // They do not belong to any dock container, until the user toggles the + // toggle view action the next time + for (auto DockWidget : d->DockWidgetsMap) { + if (DockWidget->property("dirty").toBool()) { + DockWidget->flagAsUnassigned(); + } else { + DockWidget->toggleView(!DockWidget->property("closed").toBool()); + } + } + + // Now all dock areas are properly restored and we setup the index of + // The dock areas because the previous toggleView() action has changed + // the dock area index + for (auto DockContainer : d->Containers) { + for (int i = 0; i < DockContainer->dockAreaCount(); ++i) { + CDockAreaWidget *DockArea = DockContainer->dockArea(i); + int CurrentIndex = DockArea->property("currentIndex").toInt(); + if (CurrentIndex < DockArea->count() && DockArea->count() > 1 && CurrentIndex > -1) { + DockArea->setCurrentIndex(CurrentIndex); + } + } + } + + return true; + } + + //============================================================================ + CDockAreaWidget *CDockManager::addDockWidget(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *DockAreaWidget) { + d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); + return CDockContainerWidget::addDockWidget(area, Dockwidget, DockAreaWidget); + } + + //============================================================================ + CDockWidget *CDockManager::findDockWidget(const QString &ObjectName) { + return d->DockWidgetsMap.value(ObjectName, nullptr); + } + + //============================================================================ + void CDockManager::addPerspective(const QString &UniquePrespectiveName) { + d->Perspectives.insert(UniquePrespectiveName, saveState()); + emit perspectiveListChanged(); + } + + //============================================================================ + QStringList CDockManager::perspectiveNames() const { return d->Perspectives.keys(); } + + //============================================================================ + void CDockManager::openPerspective(const QString &PerspectiveName) { + std::cout << "CDockManager::openPerspective " << PerspectiveName.toStdString() << std::endl; + const auto Iterator = d->Perspectives.find(PerspectiveName); + if (d->Perspectives.end() == Iterator) { + return; + } + + std::cout << "CDockManager::openPerspective - restoring state" << std::endl; + restoreState(Iterator.value()); + } + + //============================================================================ + void CDockManager::savePerspectives(QSettings &Settings) const { + Settings.beginWriteArray("Perspectives", d->Perspectives.size()); + int i = 0; + for (auto it = d->Perspectives.constBegin(); it != d->Perspectives.constEnd(); ++it) { + Settings.setArrayIndex(i); + Settings.setValue("Name", it.key()); + Settings.setValue("State", it.value()); + ++i; + } + Settings.endArray(); + } + + //============================================================================ + void CDockManager::loadPerspectives(QSettings &Settings) { + d->Perspectives.clear(); + int Size = Settings.beginReadArray("Perspectives"); + if (!Size) { + Settings.endArray(); + return; + } + + for (int i = 0; i < Size; ++i) { + Settings.setArrayIndex(i); + QString Name = Settings.value("Name").toString(); + QByteArray Data = Settings.value("State").toByteArray(); + if (Name.isEmpty() || Data.isEmpty()) { + continue; + } + + d->Perspectives.insert(Name, Data); + } + + Settings.endArray(); + } +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockManager.cpp diff --git a/editor/widgets/dock/DockManager.h b/editor/widgets/dock/DockManager.h new file mode 100644 index 00000000..2f4b27cb --- /dev/null +++ b/editor/widgets/dock/DockManager.h @@ -0,0 +1,185 @@ +#ifndef DockManagerH +#define DockManagerH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockManager.h +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Declaration of CDockManager class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockContainerWidget.h" + +class QSettings; + +namespace ads { + struct DockManagerPrivate; + class CFloatingDockContainer; + class CDockContainerWidget; + class CDockOverlay; + + /** + * The central dock manager that maintains the complete docking system + **/ + class CDockManager : public CDockContainerWidget { + Q_OBJECT + private: + DockManagerPrivate *d; ///< private data (pimpl) + friend struct DockManagerPrivate; + + protected: + public: + /** + * Default Constructor. + * If the given parent is a QMainWindow, the dock manager sets itself as the + * central widget + */ + CDockManager(QWidget *parent = 0); + + /** + * Virtual Destructor + */ + virtual ~CDockManager(); + + /** + * Registers the given floating widget in the internal list of + * floating widgets + */ + void registerFloatingWidget(CFloatingDockContainer *FloatingWidget); + + /** + * Remove the given floating widget from the list of registered floating + * widgets + */ + void removeFloatingWidget(CFloatingDockContainer *FloatingWidget); + + /** + * Registers the given dock container widget + */ + void registerDockContainer(CDockContainerWidget *DockContainer); + + /** + * Remove dock container from the internal list of registered dock + * containers + */ + void removeDockContainer(CDockContainerWidget *DockContainer); + + /** + * Overlay for containers + */ + CDockOverlay *containerOverlay() const; + + /** + * Overlay for dock areas + */ + CDockOverlay *dockAreaOverlay() const; + + /** + * Adds dockwidget into the given area. + * If DockAreaWidget is not null, then the area parameter indicates the area + * into the DockAreaWidget. If DockAreaWidget is null, the Dockwidget will + * be dropped into the container. + * \return Returns the dock area widget that contains the new DockWidget + */ + CDockAreaWidget *addDockWidget(DockWidgetArea area, CDockWidget *Dockwidget, + CDockAreaWidget *DockAreaWidget = nullptr); + + /** + * Searches for a registered doc widget with the given ObjectName + * \return Return the found dock widget or nullptr if a dock widget with the + * given name is not registered + */ + CDockWidget *findDockWidget(const QString &ObjectName); + + /** + * Returns the list of all active and visible dock containers + * Dock containers are the main dock manager and all floating widgets + */ + const QList dockContainers() const; + + /** + * Returns the list of all floating widgets + */ + const QList floatingWidgets() const; + + /** + * This function always return 0 because the main window is always behind + * any floating widget + */ + virtual unsigned int zOrderIndex() const; + + /** + * Saves the current state of the dockmanger and all its dock widgets + * into the returned QByteArray + */ + QByteArray saveState(int version = 0) const; + + /** + * Restores the state of this dockmanagers dockwidgets. + * The version number is compared with that stored in state. If they do + * not match, the dockmanager's state is left unchanged, and this function + * returns false; otherwise, the state is restored, and this function + * returns true. + */ + bool restoreState(const QByteArray &state, int version = 0); + + /** + * Saves the current perspective to the internal list of perspectives. + * A perspective is the current state of the dock manager assigned + * with a certain name. This makes it possible for the user, + * to switch between different perspectives quickly. + * If a perspective with the given name already exists, then + * it will be overwritten with the new state. + */ + void addPerspective(const QString &UniquePrespectiveName); + + /** + * Returns the names of all available perspectives + */ + QStringList perspectiveNames() const; + + /** + * Saves the perspectives to the given settings file. + */ + void savePerspectives(QSettings &Settings) const; + + /** + * Loads the perspectives from the given settings file + */ + void loadPerspectives(QSettings &Settings); + + public slots: + /** + * Opens the perspective with the given name. + */ + void openPerspective(const QString &PerspectiveName); + + signals: + /** + * This signal is emitted if the list of perspectives changed + */ + void perspectiveListChanged(); + }; // class DockManager +} // namespace ads +//----------------------------------------------------------------------------- +#endif // DockManagerH diff --git a/editor/widgets/dock/DockOverlay.cpp b/editor/widgets/dock/DockOverlay.cpp new file mode 100644 index 00000000..4734c52c --- /dev/null +++ b/editor/widgets/dock/DockOverlay.cpp @@ -0,0 +1,543 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockOverlay.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" + +namespace ads { + //============================================================================ + static QPixmap createDropIndicatorPixmap(const QPalette &pal, const QSizeF &size, DockWidgetArea DockWidgetArea, + CDockOverlay::eMode Mode) { + QColor borderColor = pal.color(QPalette::Active, QPalette::Highlight); + QColor backgroundColor = pal.color(QPalette::Active, QPalette::Base); + + QPixmap pm(size.width(), size.height()); + pm.fill(QColor(0, 0, 0, 0)); + + QPainter p(&pm); + QPen pen = p.pen(); + QRectF ShadowRect(pm.rect()); + QRectF baseRect; + baseRect.setSize(ShadowRect.size() * 0.7); + baseRect.moveCenter(ShadowRect.center()); + + // Fill + p.fillRect(ShadowRect, QColor(0, 0, 0, 64)); + + // Drop area rect. + p.save(); + QRectF areaRect; + QLineF areaLine; + QRectF nonAreaRect; + switch (DockWidgetArea) { + case TopDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * .5f); + nonAreaRect = QRectF(baseRect.x(), ShadowRect.height() * .5f, baseRect.width(), baseRect.height() * .5f); + areaLine = QLineF(areaRect.bottomLeft(), areaRect.bottomRight()); + break; + case RightDockWidgetArea: + areaRect = QRectF(ShadowRect.width() * .5f, baseRect.y(), baseRect.width() * .5f, baseRect.height()); + nonAreaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * .5f, baseRect.height()); + areaLine = QLineF(areaRect.topLeft(), areaRect.bottomLeft()); + break; + case BottomDockWidgetArea: + areaRect = QRectF(baseRect.x(), ShadowRect.height() * .5f, baseRect.width(), baseRect.height() * .5f); + nonAreaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width(), baseRect.height() * .5f); + areaLine = QLineF(areaRect.topLeft(), areaRect.topRight()); + break; + case LeftDockWidgetArea: + areaRect = QRectF(baseRect.x(), baseRect.y(), baseRect.width() * .5f, baseRect.height()); + nonAreaRect = QRectF(ShadowRect.width() * .5f, baseRect.y(), baseRect.width() * .5f, baseRect.height()); + areaLine = QLineF(areaRect.topRight(), areaRect.bottomRight()); + break; + default: + break; + } + + QSizeF baseSize = baseRect.size(); + if (CDockOverlay::ModeContainerOverlay == Mode && DockWidgetArea != CenterDockWidgetArea) { + baseRect = areaRect; + } + + p.fillRect(baseRect, backgroundColor); + if (areaRect.isValid()) { + pen = p.pen(); + pen.setColor(borderColor); + QColor Color = borderColor; + Color.setAlpha(64); + p.setBrush(Color); + p.setPen(Qt::NoPen); + p.drawRect(areaRect); + + pen = p.pen(); + pen.setColor(borderColor); + pen.setStyle(Qt::DashLine); + p.setPen(pen); + p.drawLine(areaLine); + } + p.restore(); + + p.save(); + // Draw outer border + pen = p.pen(); + pen.setColor(borderColor); + pen.setWidth(1); + p.setBrush(Qt::NoBrush); + p.setPen(pen); + p.drawRect(baseRect); + + // draw window title bar + p.setBrush(borderColor); + QRectF FrameRect(baseRect.topLeft(), QSizeF(baseRect.width(), baseSize.height() / 10)); + p.drawRect(FrameRect); + p.restore(); + + // Draw arrow for outer container drop indicators + if (CDockOverlay::ModeContainerOverlay == Mode && DockWidgetArea != CenterDockWidgetArea) { + QRectF ArrowRect; + ArrowRect.setSize(baseSize); + ArrowRect.setWidth(ArrowRect.width() / 4.6); + ArrowRect.setHeight(ArrowRect.height() / 2); + ArrowRect.moveCenter(QPointF(0, 0)); + QPolygonF Arrow; + Arrow << ArrowRect.topLeft() << QPointF(ArrowRect.right(), ArrowRect.center().y()) + << ArrowRect.bottomLeft(); + p.setPen(Qt::NoPen); + p.setBrush(backgroundColor); + p.setRenderHint(QPainter::Antialiasing, true); + p.translate(nonAreaRect.center().x(), nonAreaRect.center().y()); + + switch (DockWidgetArea) { + case TopDockWidgetArea: + p.rotate(-90); + break; + case RightDockWidgetArea: + break; + case BottomDockWidgetArea: + p.rotate(90); + break; + case LeftDockWidgetArea: + p.rotate(180); + break; + default: + break; + } + + p.drawPolygon(Arrow); + } + + return pm; + } + + //============================================================================ + QWidget *createDropIndicatorWidget(DockWidgetArea DockWidgetArea, CDockOverlay::eMode Mode) { + QLabel *l = new QLabel(); + l->setObjectName("DockWidgetAreaLabel"); + + const qreal metric = static_cast(l->fontMetrics().height()) * 3.f; + const QSizeF size(metric, metric); + + l->setPixmap(createDropIndicatorPixmap(l->palette(), size, DockWidgetArea, Mode)); + l->setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + l->setAttribute(Qt::WA_TranslucentBackground); + return l; + } + + /** + * Private data class of CDockOverlay + */ + struct DockOverlayPrivate { + CDockOverlay *_this; + DockWidgetAreas AllowedAreas = InvalidDockWidgetArea; + CDockOverlayCross *Cross; + QPointer TargetWidget; + QRect TargetRect; + DockWidgetArea LastLocation = InvalidDockWidgetArea; + bool DropPreviewEnabled = true; + CDockOverlay::eMode Mode = CDockOverlay::ModeDockAreaOverlay; + QRect DropAreaRect; + + /** + * Private data constructor + */ + DockOverlayPrivate(CDockOverlay *_public) : _this(_public) {} + }; + + /** + * Private data of CDockOverlayCross class + */ + struct DockOverlayCrossPrivate { + CDockOverlayCross *_this; + CDockOverlay::eMode Mode = CDockOverlay::ModeDockAreaOverlay; + CDockOverlay *DockOverlay; + QHash DropIndicatorWidgets; + QGridLayout *GridLayout; + + /** + * Private data constructor + */ + DockOverlayCrossPrivate(CDockOverlayCross *_public) : _this(_public) {} + + /** + * + * @param area + * @return + */ + QPoint areaGridPosition(const DockWidgetArea area); + }; + + //============================================================================ + CDockOverlay::CDockOverlay(QWidget *parent, eMode Mode) : QFrame(parent), d(new DockOverlayPrivate(this)) { + d->Mode = Mode; + d->Cross = new CDockOverlayCross(this); + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setWindowOpacity(1); + setWindowTitle("DockOverlay"); + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); + + d->Cross->setupOverlayCross(Mode); + d->Cross->setVisible(false); + setVisible(false); + } + + //============================================================================ + CDockOverlay::~CDockOverlay() { delete d; } + + //============================================================================ + void CDockOverlay::setAllowedAreas(DockWidgetAreas areas) { + if (areas == d->AllowedAreas) return; + d->AllowedAreas = areas; + d->Cross->reset(); + } + + //============================================================================ + DockWidgetAreas CDockOverlay::allowedAreas() const { return d->AllowedAreas; } + + //============================================================================ + DockWidgetArea CDockOverlay::dropAreaUnderCursor() const { + DockWidgetArea Result = d->Cross->cursorLocation(); + if (Result != InvalidDockWidgetArea) { + return Result; + } + + CDockAreaWidget *DockArea = dynamic_cast(d->TargetWidget.data()); + if (!DockArea) { + return Result; + } + + if (DockArea->titleAreaGeometry().contains(DockArea->mapFromGlobal(QCursor::pos()))) { + return CenterDockWidgetArea; + } + + return Result; + } + + //============================================================================ + DockWidgetArea CDockOverlay::showOverlay(QWidget *target) { + if (d->TargetWidget == target) { + // Hint: We could update geometry of overlay here. + DockWidgetArea da = dropAreaUnderCursor(); + if (da != d->LastLocation) { + repaint(); + d->LastLocation = da; + } + return da; + } + + d->TargetWidget = target; + d->TargetRect = QRect(); + d->LastLocation = InvalidDockWidgetArea; + + // Move it over the target. + resize(target->size()); + QPoint TopLeft = target->mapToGlobal(target->rect().topLeft()); + move(TopLeft); + show(); + d->Cross->updatePosition(); + return dropAreaUnderCursor(); + } + + //============================================================================ + void CDockOverlay::hideOverlay() { + hide(); + d->TargetWidget.clear(); + d->TargetRect = QRect(); + d->LastLocation = InvalidDockWidgetArea; + } + + //============================================================================ + void CDockOverlay::enableDropPreview(bool Enable) { + d->DropPreviewEnabled = Enable; + update(); + } + + //============================================================================ + void CDockOverlay::paintEvent(QPaintEvent *event) { + Q_UNUSED(event); + // Draw rect based on location + if (!d->DropPreviewEnabled) { + d->DropAreaRect = QRect(); + return; + } + + QRect r = rect(); + const DockWidgetArea da = dropAreaUnderCursor(); + double Factor = (CDockOverlay::ModeContainerOverlay == d->Mode) ? 3 : 2; + + switch (da) { + case TopDockWidgetArea: + r.setHeight(r.height() / Factor); + break; + case RightDockWidgetArea: + r.setX(r.width() * (1 - 1 / Factor)); + break; + case BottomDockWidgetArea: + r.setY(r.height() * (1 - 1 / Factor)); + break; + case LeftDockWidgetArea: + r.setWidth(r.width() / Factor); + break; + case CenterDockWidgetArea: + r = rect(); + break; + default: + return; + } + QPainter painter(this); + QColor Color = palette().color(QPalette::Active, QPalette::Highlight); + Color.setAlpha(64); + painter.setPen(Qt::NoPen); + painter.fillRect(r, Color); + d->DropAreaRect = r; + } + + //============================================================================ + QRect CDockOverlay::dropOverlayRect() const { return d->DropAreaRect; } + + //============================================================================ + void CDockOverlay::showEvent(QShowEvent *e) { + d->Cross->show(); + QFrame::showEvent(e); + } + + //============================================================================ + void CDockOverlay::hideEvent(QHideEvent *e) { + d->Cross->hide(); + QFrame::hideEvent(e); + } + + //============================================================================ + static int areaAlignment(const DockWidgetArea area) { + switch (area) { + case TopDockWidgetArea: + return (int) Qt::AlignHCenter | Qt::AlignBottom; + case RightDockWidgetArea: + return (int) Qt::AlignLeft | Qt::AlignVCenter; + case BottomDockWidgetArea: + return (int) Qt::AlignHCenter | Qt::AlignTop; + case LeftDockWidgetArea: + return (int) Qt::AlignRight | Qt::AlignVCenter; + case CenterDockWidgetArea: + return (int) Qt::AlignCenter; + default: + return Qt::AlignCenter; + } + } + + //============================================================================ + // DockOverlayCrossPrivate + //============================================================================ + QPoint DockOverlayCrossPrivate::areaGridPosition(const DockWidgetArea area) { + if (CDockOverlay::ModeDockAreaOverlay == Mode) { + switch (area) { + case TopDockWidgetArea: + return QPoint(1, 2); + case RightDockWidgetArea: + return QPoint(2, 3); + case BottomDockWidgetArea: + return QPoint(3, 2); + case LeftDockWidgetArea: + return QPoint(2, 1); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } else { + switch (area) { + case TopDockWidgetArea: + return QPoint(0, 2); + case RightDockWidgetArea: + return QPoint(2, 4); + case BottomDockWidgetArea: + return QPoint(4, 2); + case LeftDockWidgetArea: + return QPoint(2, 0); + case CenterDockWidgetArea: + return QPoint(2, 2); + default: + return QPoint(); + } + } + } + + //============================================================================ + CDockOverlayCross::CDockOverlayCross(CDockOverlay *overlay) + : QWidget(overlay->parentWidget()), d(new DockOverlayCrossPrivate(this)) { + d->DockOverlay = overlay; + setWindowFlags(Qt::Tool | Qt::FramelessWindowHint); + setWindowTitle("DockOverlayCross"); + setAttribute(Qt::WA_TranslucentBackground); + + d->GridLayout = new QGridLayout(); + d->GridLayout->setSpacing(0); + setLayout(d->GridLayout); + } + + //============================================================================ + CDockOverlayCross::~CDockOverlayCross() { delete d; } + + //============================================================================ + void CDockOverlayCross::setupOverlayCross(CDockOverlay::eMode Mode) { + d->Mode = Mode; + + QHash areaWidgets; + areaWidgets.insert(TopDockWidgetArea, createDropIndicatorWidget(TopDockWidgetArea, Mode)); + areaWidgets.insert(RightDockWidgetArea, createDropIndicatorWidget(RightDockWidgetArea, Mode)); + areaWidgets.insert(BottomDockWidgetArea, createDropIndicatorWidget(BottomDockWidgetArea, Mode)); + areaWidgets.insert(LeftDockWidgetArea, createDropIndicatorWidget(LeftDockWidgetArea, Mode)); + areaWidgets.insert(CenterDockWidgetArea, createDropIndicatorWidget(CenterDockWidgetArea, Mode)); + + setAreaWidgets(areaWidgets); + } + + //============================================================================ + void CDockOverlayCross::setAreaWidgets(const QHash &widgets) { + // Delete old widgets. + QMutableHashIterator i(d->DropIndicatorWidgets); + while (i.hasNext()) { + i.next(); + QWidget *widget = i.value(); + d->GridLayout->removeWidget(widget); + delete widget; + i.remove(); + } + + // Insert new widgets into grid. + d->DropIndicatorWidgets = widgets; + QHashIterator i2(d->DropIndicatorWidgets); + while (i2.hasNext()) { + i2.next(); + const DockWidgetArea area = i2.key(); + QWidget *widget = i2.value(); + QPoint p = d->areaGridPosition(area); + d->GridLayout->addWidget(widget, p.x(), p.y(), (Qt::Alignment) areaAlignment(area)); + } + + if (CDockOverlay::ModeDockAreaOverlay == d->Mode) { + d->GridLayout->setContentsMargins(0, 0, 0, 0); + d->GridLayout->setRowStretch(0, 1); + d->GridLayout->setRowStretch(1, 0); + d->GridLayout->setRowStretch(2, 0); + d->GridLayout->setRowStretch(3, 0); + d->GridLayout->setRowStretch(4, 1); + + d->GridLayout->setColumnStretch(0, 1); + d->GridLayout->setColumnStretch(1, 0); + d->GridLayout->setColumnStretch(2, 0); + d->GridLayout->setColumnStretch(3, 0); + d->GridLayout->setColumnStretch(4, 1); + } else { + d->GridLayout->setContentsMargins(4, 4, 4, 4); + d->GridLayout->setRowStretch(0, 0); + d->GridLayout->setRowStretch(1, 1); + d->GridLayout->setRowStretch(2, 1); + d->GridLayout->setRowStretch(3, 1); + d->GridLayout->setRowStretch(4, 0); + + d->GridLayout->setColumnStretch(0, 0); + d->GridLayout->setColumnStretch(1, 1); + d->GridLayout->setColumnStretch(2, 1); + d->GridLayout->setColumnStretch(3, 1); + d->GridLayout->setColumnStretch(4, 0); + } + reset(); + } + + //============================================================================ + DockWidgetArea CDockOverlayCross::cursorLocation() const { + const QPoint pos = mapFromGlobal(QCursor::pos()); + QHashIterator i(d->DropIndicatorWidgets); + while (i.hasNext()) { + i.next(); + if (d->DockOverlay->allowedAreas().testFlag(i.key()) && i.value() && i.value()->isVisible() && + i.value()->geometry().contains(pos)) { + return i.key(); + } + } + return InvalidDockWidgetArea; + } + + //============================================================================ + void CDockOverlayCross::showEvent(QShowEvent *) { this->updatePosition(); } + + //============================================================================ + void CDockOverlayCross::updatePosition() { + resize(d->DockOverlay->size()); + QPoint TopLeft = d->DockOverlay->pos(); + QPoint Offest((this->width() - d->DockOverlay->width()) / 2, (this->height() - d->DockOverlay->height()) / 2); + QPoint CrossTopLeft = TopLeft - Offest; + move(CrossTopLeft); + } + + //============================================================================ + void CDockOverlayCross::reset() { + QList allAreas; + allAreas << TopDockWidgetArea << RightDockWidgetArea << BottomDockWidgetArea << LeftDockWidgetArea + << CenterDockWidgetArea; + const DockWidgetAreas allowedAreas = d->DockOverlay->allowedAreas(); + + // Update visibility of area widgets based on allowedAreas. + for (int i = 0; i < allAreas.count(); ++i) { + QPoint p = d->areaGridPosition(allAreas.at(i)); + QLayoutItem *item = d->GridLayout->itemAtPosition(p.x(), p.y()); + QWidget *w = nullptr; + if (item && (w = item->widget()) != nullptr) { + w->setVisible(allowedAreas.testFlag(allAreas.at(i))); + } + } + } + +} // namespace ads +//---------------------------------------------------------------------------- diff --git a/editor/widgets/dock/DockOverlay.h b/editor/widgets/dock/DockOverlay.h new file mode 100644 index 00000000..57fc08c0 --- /dev/null +++ b/editor/widgets/dock/DockOverlay.h @@ -0,0 +1,156 @@ +#ifndef DockOverlayH +#define DockOverlayH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +// INCLUDES +//============================================================================ +#include +#include +#include +#include +class QGridLayout; + +#include "ads_globals.h" + +namespace ads { + struct DockOverlayPrivate; + class CDockOverlayCross; + + /*! + * DockOverlay paints a translucent rectangle over another widget. The geometry + * of the rectangle is based on the mouse location. + */ + class CDockOverlay : public QFrame { + Q_OBJECT + private: + DockOverlayPrivate *d; //< private data class + friend struct DockOverlayPrivate; + friend class DockOverlayCross; + + public: + enum eMode { ModeDockAreaOverlay, ModeContainerOverlay }; + + /** + * Creates a dock overlay + */ + CDockOverlay(QWidget *parent, eMode Mode = ModeDockAreaOverlay); + + /** + * Virtual destructor + */ + virtual ~CDockOverlay(); + + /** + * Configures the areas that are allowed for docking + */ + void setAllowedAreas(DockWidgetAreas areas); + + /** + * Returns flags with all allowed drop areas + */ + DockWidgetAreas allowedAreas() const; + + /** + * Returns the drop area under the current cursor location + */ + DockWidgetArea dropAreaUnderCursor() const; + + /** + * Show the drop overly for the given target widget + */ + DockWidgetArea showOverlay(QWidget *target); + + /** + * Hides the overlay + */ + void hideOverlay(); + + /** + * Enables / disables the semi transparent overlay rectangle that represents + * the future area of the dropped widget + */ + void enableDropPreview(bool Enable); + + /** + * The drop overlay rectangle for the target area + */ + QRect dropOverlayRect() const; + + protected: + virtual void paintEvent(QPaintEvent *e) override; + virtual void showEvent(QShowEvent *e) override; + virtual void hideEvent(QHideEvent *e) override; + }; + + struct DockOverlayCrossPrivate; + /*! + * DockOverlayCross shows a cross with 5 different drop area possibilities. + * I could have handled everything inside DockOverlay, but because of some + * styling issues it's better to have a separate class for the cross. + */ + class CDockOverlayCross : public QWidget { + Q_OBJECT + private: + DockOverlayCrossPrivate *d; + friend struct DockOverlayCrossPrivate; + friend class CDockOverlay; + + public: + /** + * Creates an overlay corss for the given overlay + */ + CDockOverlayCross(CDockOverlay *overlay); + + /** + * Virtual destructor + */ + virtual ~CDockOverlayCross(); + + /** + * Returns the dock widget area depending on the current cursor location. + * The function checks, if the mouse cursor is inside of any drop indicator + * widget and returns the corresponding DockWidgetArea. + */ + DockWidgetArea cursorLocation() const; + + /** + * Sets up the overlay cross for the given overlay mode + */ + void setupOverlayCross(CDockOverlay::eMode Mode); + + /** + * Resets and updates the + */ + void reset(); + + /** + * Updates the current position + */ + void updatePosition(); + + protected: + virtual void showEvent(QShowEvent *e) override; + void setAreaWidgets(const QHash &widgets); + + private: + }; // CDockOverlayCross + +} // namespace ads +#endif // DockOverlayH diff --git a/editor/widgets/dock/DockPanel.cpp b/editor/widgets/dock/DockPanel.cpp deleted file mode 100644 index ce52a796..00000000 --- a/editor/widgets/dock/DockPanel.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "DockPanel.h" - -#include - -using namespace AxiomGui; - -DockPanel::DockPanel(const QString &title, QWidget *parent) : QDockWidget(title, parent) { -} diff --git a/editor/widgets/dock/DockPanel.h b/editor/widgets/dock/DockPanel.h deleted file mode 100644 index 9c08fa5b..00000000 --- a/editor/widgets/dock/DockPanel.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include - -namespace AxiomGui { - - class DockPanel : public QDockWidget { - Q_OBJECT - - public: - DockPanel(const QString &title, QWidget *parent = nullptr); - }; - -} diff --git a/editor/widgets/dock/DockSplitter.cpp b/editor/widgets/dock/DockSplitter.cpp new file mode 100644 index 00000000..30026b85 --- /dev/null +++ b/editor/widgets/dock/DockSplitter.cpp @@ -0,0 +1,71 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockSplitter.cpp +/// \author Uwe Kindler +/// \date 24.03.2017 +/// \brief Implementation of CDockSplitter +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockSplitter.h" + +#include + +#include "DockAreaWidget.h" + +namespace ads { + /** + * Private dock splitter data + */ + struct DockSplitterPrivate { + CDockSplitter *_this; + int VisibleContentCount = 0; + + DockSplitterPrivate(CDockSplitter *_public) : _this(_public) {} + }; + + //============================================================================ + CDockSplitter::CDockSplitter(QWidget *parent) : QSplitter(parent), d(new DockSplitterPrivate(this)) {} + + //============================================================================ + CDockSplitter::CDockSplitter(Qt::Orientation orientation, QWidget *parent) + : QSplitter(orientation, parent), d(new DockSplitterPrivate(this)) {} + + //============================================================================ + CDockSplitter::~CDockSplitter() { delete d; } + + //============================================================================ + bool CDockSplitter::hasVisibleContent() const { + // TODO Cache or precalculate this to speed up + for (int i = 0; i < count(); ++i) { + if (widget(i)->isVisibleTo(this)) { + return true; + } + } + + return false; + } + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockSplitter.cpp diff --git a/editor/widgets/dock/DockSplitter.h b/editor/widgets/dock/DockSplitter.h new file mode 100644 index 00000000..1a7dafd8 --- /dev/null +++ b/editor/widgets/dock/DockSplitter.h @@ -0,0 +1,63 @@ +#ifndef DockSplitterH +#define DockSplitterH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockSplitter.h +/// \author Uwe Kindler +/// \date 24.03.2017 +/// \brief Declaration of CDockSplitter +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +namespace ads { + struct DockSplitterPrivate; + + /** + * Splitter used internally instead of QSplitter + */ + class CDockSplitter : public QSplitter { + Q_OBJECT + private: + DockSplitterPrivate *d; + friend struct DockSplitterPrivate; + + public: + CDockSplitter(QWidget *parent = Q_NULLPTR); + CDockSplitter(Qt::Orientation orientation, QWidget *parent = Q_NULLPTR); + + /** + * Prints debug info + */ + virtual ~CDockSplitter(); + + /** + * Returns true, if any of the internal widgets is visible + */ + bool hasVisibleContent() const; + }; // class CDockSplitter + +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // DockSplitterH diff --git a/editor/widgets/dock/DockStateSerialization.cpp b/editor/widgets/dock/DockStateSerialization.cpp new file mode 100644 index 00000000..f983afdb --- /dev/null +++ b/editor/widgets/dock/DockStateSerialization.cpp @@ -0,0 +1,29 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockStateSerialization.cpp +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Serialization related data, constants and stuff +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockStateSerialization.h" diff --git a/editor/widgets/dock/DockStateSerialization.h b/editor/widgets/dock/DockStateSerialization.h new file mode 100644 index 00000000..a74f8022 --- /dev/null +++ b/editor/widgets/dock/DockStateSerialization.h @@ -0,0 +1,49 @@ +#ifndef DockStateSerializationH +#define DockStateSerializationH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockStateSerialization.h +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Declaration of serialization related data, constants and stuff +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ + +namespace ads { + + namespace internal { + // sentinel values used to validate state data + enum VersionMarkers { + VersionMarker = 0xff, + ContainerMarker = 0xfe, + SplitterMarker = 0xfd, + DockAreaMarker = 0xfc, + DockWidgetMarker = 0xfb + }; + + static const bool RestoreTesting = true; + static const bool Restore = false; + } // internal +} // namespace ads +//----------------------------------------------------------------------------- +#endif // DockManagerH diff --git a/editor/widgets/dock/DockWidget.cpp b/editor/widgets/dock/DockWidget.cpp new file mode 100644 index 00000000..19443d78 --- /dev/null +++ b/editor/widgets/dock/DockWidget.cpp @@ -0,0 +1,313 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockWidget.cpp +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Implementation of CDockWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockContainerWidget.h" +#include "DockManager.h" +#include "DockSplitter.h" +#include "DockStateSerialization.h" +#include "DockWidgetTitleBar.h" +#include "FloatingDockContainer.h" +#include "ads_globals.h" + +namespace ads { + /** + * Private data class of CDockWidget class (pimpl) + */ + struct DockWidgetPrivate { + CDockWidget *_this; + QBoxLayout *Layout; + QWidget *Widget = nullptr; + CDockWidgetTitleBar *TitleWidget; + CDockWidget::DockWidgetFeatures Features = CDockWidget::AllDockWidgetFeatures; + CDockManager *DockManager = nullptr; + CDockAreaWidget *DockArea = nullptr; + QAction *ToggleViewAction; + bool Closed = false; + + /** + * Private data constructor + */ + DockWidgetPrivate(CDockWidget *_public); + + /** + * Show dock widget + */ + void showDockWidget(); + + /** + * Hide dock widget. + */ + void hideDockWidget(); + + /** + * Hides a parent splitter if all dock widgets in the splitter are closed + */ + void hideEmptyParentSplitters(); + + /** + * Hides a dock area if all dock widgets in the area are closed + */ + void hideEmptyParentDockArea(); + + /** + * Hides a floating widget if all dock areas are empty - that means, + * if all dock widgets in all dock areas are closed + */ + void hideEmptyFloatingWidget(); + }; + // struct DockWidgetPrivate + + //============================================================================ + DockWidgetPrivate::DockWidgetPrivate(CDockWidget *_public) : _this(_public) {} + + //============================================================================ + void DockWidgetPrivate::showDockWidget() { + if (!DockArea) { + CFloatingDockContainer *FloatingWidget = new CFloatingDockContainer(_this); + FloatingWidget->resize(_this->size()); + FloatingWidget->show(); + } else { + DockArea->show(); + DockArea->setCurrentIndex(DockArea->tabIndex(_this)); + QSplitter *Splitter = internal::findParent(_this); + while (Splitter && !Splitter->isVisible()) { + Splitter->show(); + Splitter = internal::findParent(Splitter); + } + + CDockContainerWidget *Container = DockArea->dockContainer(); + if (Container->isFloating()) { + CFloatingDockContainer *FloatingWidget = internal::findParent(Container); + FloatingWidget->show(); + } + } + } + + //============================================================================ + void DockWidgetPrivate::hideDockWidget() { + TitleWidget->hide(); + hideEmptyParentDockArea(); + hideEmptyParentSplitters(); + hideEmptyFloatingWidget(); + } + + //============================================================================ + void DockWidgetPrivate::hideEmptyParentSplitters() { + auto Splitter = internal::findParent(_this); + while (Splitter && Splitter->isVisible()) { + if (!Splitter->hasVisibleContent()) { + Splitter->hide(); + } + Splitter = internal::findParent(Splitter); + } + } + + //============================================================================ + void DockWidgetPrivate::hideEmptyParentDockArea() { + auto OpenDockWidgets = DockArea->openedDockWidgets(); + if (OpenDockWidgets.count() > 1) { + CDockWidget *NextDockWidget; + if (OpenDockWidgets.last() == _this) { + NextDockWidget = OpenDockWidgets[OpenDockWidgets.count() - 2]; + } else { + int NextIndex = OpenDockWidgets.indexOf(_this) + 1; + NextDockWidget = OpenDockWidgets[NextIndex]; + } + + DockArea->setCurrentDockWidget(NextDockWidget); + } else { + DockArea->hide(); + } + } + + //============================================================================ + void DockWidgetPrivate::hideEmptyFloatingWidget() { + CDockContainerWidget *Container = _this->dockContainer(); + if (Container->isFloating() && Container->openedDockAreas().isEmpty()) { + CFloatingDockContainer *FloatingWidget = internal::findParent(Container); + FloatingWidget->hide(); + } + } + + //============================================================================ + CDockWidget::CDockWidget(const QString &title, QWidget *parent) : QFrame(parent), d(new DockWidgetPrivate(this)) { + d->Layout = new QBoxLayout(QBoxLayout::TopToBottom); + d->Layout->setContentsMargins(0, 0, 0, 0); + d->Layout->setSpacing(0); + setLayout(d->Layout); + setWindowTitle(title); + + d->TitleWidget = new CDockWidgetTitleBar(this); + d->ToggleViewAction = new QAction(title); + d->ToggleViewAction->setCheckable(true); + connect(d->ToggleViewAction, SIGNAL(triggered(bool)), this, SLOT(toggleView(bool))); + } + + //============================================================================ + CDockWidget::~CDockWidget() { delete d; } + + //============================================================================ + void CDockWidget::setToggleViewActionChecked(bool Checked) { + QAction *Action = d->ToggleViewAction; + Action->blockSignals(true); + Action->setChecked(Checked); + Action->blockSignals(false); + } + + //============================================================================ + void CDockWidget::setWidget(QWidget *widget) { + if (d->Widget) { + d->Layout->replaceWidget(d->Widget, widget); + } else { + d->Layout->addWidget(widget); + } + + d->Widget = widget; + } + + //============================================================================ + QWidget *CDockWidget::widget() const { return d->Widget; } + + //============================================================================ + CDockWidgetTitleBar *CDockWidget::titleBar() const { return d->TitleWidget; } + + //============================================================================ + void CDockWidget::setFeatures(DockWidgetFeatures features) { d->Features = features; } + + //============================================================================ + CDockWidget::DockWidgetFeatures CDockWidget::features() const { return d->Features; } + + //============================================================================ + CDockManager *CDockWidget::dockManager() const { return d->DockManager; } + + //============================================================================ + void CDockWidget::setDockManager(CDockManager *DockManager) { d->DockManager = DockManager; } + + //============================================================================ + CDockContainerWidget *CDockWidget::dockContainer() const { + return internal::findParent(this); + } + + //============================================================================ + CDockAreaWidget *CDockWidget::dockAreaWidget() const { return internal::findParent(this); } + + //============================================================================ + bool CDockWidget::isFloating() const { return dockContainer() ? dockContainer()->isFloating() : false; } + + //============================================================================ + bool CDockWidget::isClosed() const { return d->Closed; } + + //============================================================================ + QAction *CDockWidget::toggleViewAction() const { return d->ToggleViewAction; } + + //============================================================================ + void CDockWidget::setToggleViewActionMode(eToggleViewActionMode Mode) { + if (ActionModeToggle == Mode) { + d->ToggleViewAction->setCheckable(true); + d->ToggleViewAction->setIcon(QIcon()); + } else { + d->ToggleViewAction->setCheckable(false); + d->ToggleViewAction->setIcon(d->TitleWidget->icon()); + } + } + + //============================================================================ + void CDockWidget::toggleView(bool Open) { + QAction *Sender = qobject_cast(sender()); + if (Sender == d->ToggleViewAction && !d->ToggleViewAction->isCheckable()) { + Open = true; + } + + if (Open) { + d->showDockWidget(); + } else { + d->hideDockWidget(); + } + d->Closed = !Open; + d->ToggleViewAction->blockSignals(true); + d->ToggleViewAction->setChecked(Open); + d->ToggleViewAction->blockSignals(false); + if (!Open) { + emit closed(); + } + emit viewToggled(Open); + } + + //============================================================================ + void CDockWidget::setDockArea(CDockAreaWidget *DockArea) { + d->DockArea = DockArea; + d->ToggleViewAction->setChecked(DockArea != nullptr); + } + + //============================================================================ + void CDockWidget::saveState(QXmlStreamWriter &s) const { + s.writeStartElement("DockWidget"); + s.writeAttribute("ObjectName", objectName()); + s.writeAttribute("Closed", QString::number(d->Closed ? 1 : 0)); + s.writeEndElement(); + } + + //============================================================================ + void CDockWidget::flagAsUnassigned() { + setParent(d->DockManager); + setDockArea(nullptr); + titleBar()->setParent(this); + } + + //============================================================================ + bool CDockWidget::event(QEvent *e) { + if (e->type() == QEvent::WindowTitleChange) { + emit titleChanged(windowTitle()); + } + return QFrame::event(e); + } + + //============================================================================ + void CDockWidget::setIcon(const QIcon &Icon) { + d->TitleWidget->setIcon(Icon); + if (!d->ToggleViewAction->isCheckable()) { + d->ToggleViewAction->setIcon(Icon); + } + } + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockWidget.cpp diff --git a/editor/widgets/dock/DockWidget.h b/editor/widgets/dock/DockWidget.h new file mode 100644 index 00000000..1e0abb49 --- /dev/null +++ b/editor/widgets/dock/DockWidget.h @@ -0,0 +1,237 @@ +#ifndef DockWidgetH +#define DockWidgetH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockWidget.h +/// \author Uwe Kindler +/// \date 26.02.2017 +/// \brief Declaration of CDockWidget class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +class QXmlStreamWriter; + +namespace ads { + struct DockWidgetPrivate; + class CDockWidgetTitleBar; + class CDockManager; + class CDockContainerWidget; + class CDockAreaWidget; + struct DockContainerWidgetPrivate; + + /** + * The QDockWidget class provides a widget that can be docked inside a + * CDockManager or floated as a top-level window on the desktop. + */ + class CDockWidget : public QFrame { + Q_OBJECT + private: + DockWidgetPrivate *d; ///< private data (pimpl) + friend struct DockWidgetPrivate; + + protected: + friend class CDockContainerWidget; + friend class CDockAreaWidget; + friend class CFloatingDockContainer; + friend class CDockManager; + friend struct DockContainerWidgetPrivate; + + /** + * Assigns the dock manager that manages this dock widget + */ + void setDockManager(CDockManager *DockManager); + + /** + * If this dock widget is inserted into a dock area, the dock area will + * be registered on this widget via this function. If a dock widget is + * removed from a dock area, this function will be called with nullptr + * value. + */ + void setDockArea(CDockAreaWidget *DockArea); + + /** + * This function changes the toggle view action without emitting any + * signal + */ + void setToggleViewActionChecked(bool Checked); + + /** + * Saves the state into the given stream + */ + void saveState(QXmlStreamWriter &Stream) const; + + /** + * This is a helper function for the dock manager to flag this widget + * as unassigned. + * When calling the restore function, it may happen, that the saved state + * contains less dock widgets then currently available. All widgets whose + * data is not contained in the saved state, are flagged as unassigned + * after the restore process. If the user shows an unassigned dock widget, + * a floating widget will be created to take up the dock widget. + */ + void flagAsUnassigned(); + + public: + enum DockWidgetFeature { + DockWidgetClosable = 0x01, + DockWidgetMovable = 0x02, + DockWidgetFloatable = 0x04, + AllDockWidgetFeatures = DockWidgetClosable | DockWidgetMovable | DockWidgetFloatable, + NoDockWidgetFeatures = 0x00 + }; + Q_DECLARE_FLAGS(DockWidgetFeatures, DockWidgetFeature) + + enum eState { StateHidden, StateDocked, StateFloating }; + + /** + * This mode configures the behavior of the toggle view action. + * If the mode if ActionModeToggle, then the toggle view action is + * a checkable action to show / hide the dock widget. If the mode + * is ActionModeShow, then the action is not checkable an it will + * always show the dock widget if clicked. If the mode is ActionModeShow, + * the user can only close the DockWidget with the close button. + */ + enum eToggleViewActionMode { + ActionModeToggle, //!< ActionModeToggle + ActionModeShow //!< ActionModeShow + }; + + /** + * Default Constructor + */ + CDockWidget(const QString &title, QWidget *parent = 0); + + /** + * Virtual Destructor + */ + virtual ~CDockWidget(); + + /** + * Sets the widget for the dock widget to widget. + */ + void setWidget(QWidget *widget); + + /** + * Returns the widget for the dock widget. This function returns zero if + * the widget has not been set. + */ + QWidget *widget() const; + + /** + * Returns the title bar widget of this dock widget + */ + CDockWidgetTitleBar *titleBar() const; + + /** + * Sets, whether the dock widget is movable, closable, and floatable. + */ + void setFeatures(DockWidgetFeatures features); + + /** + * This property holds whether the dock widget is movable, closable, and + * floatable. + * By default, this property is set to a combination of DockWidgetClosable, + * DockWidgetMovable and DockWidgetFloatable. + */ + DockWidgetFeatures features() const; + + /** + * Returns the dock manager that manages the dock widget or 0 if the widget + * has not been assigned to any dock manager yet + */ + CDockManager *dockManager() const; + + /** + * Returns the dock container widget this dock area widget belongs to or 0 + * if this dock widget has nt been docked yet + */ + CDockContainerWidget *dockContainer() const; + + /** + * Returns the dock area widget this dock widget belongs to or 0 + * if this dock widget has not been docked yet + */ + CDockAreaWidget *dockAreaWidget() const; + + /** + * This property holds whether the dock widget is floating. + */ + bool isFloating() const; + + /** + * Returns true, if this dock widget is closed. + */ + bool isClosed() const; + + /** + * Returns a checkable action that can be used to show or close this dock widget. + * The action's text is set to the dock widget's window title. + */ + QAction *toggleViewAction() const; + + /** + * Configures the behavior of the toggle view action. + * \see eToggleViewActionMode for a detailed description + */ + void setToggleViewActionMode(eToggleViewActionMode Mode); + + /** + * Emits titleChanged signal if title change event occurs + */ + virtual bool event(QEvent *e) override; + + /** + * Sets the dock widget icon that is shown in tabs and in toggle view + * actions + */ + void setIcon(const QIcon &Icon); + + public slots: + /** + * This property controls whether the dock widget is open or closed. + * The toogleViewAction triggers this slot + */ + void toggleView(bool Open = true); + + signals: + /** + * This signal is emitted if the dock widget is opened or closed + */ + void viewToggled(bool Open); + + /** + * This signal is emitted if the dock widget is closed + */ + void closed(); + + /** + * This signal is emitted if the window title of this dock widget + * changed + */ + void titleChanged(const QString &Title); + }; // class DockWidget +} +// namespace ads +//----------------------------------------------------------------------------- +#endif // DockWidgetH diff --git a/editor/widgets/dock/DockWidgetTitleBar.cpp b/editor/widgets/dock/DockWidgetTitleBar.cpp new file mode 100644 index 00000000..5ddcfaf4 --- /dev/null +++ b/editor/widgets/dock/DockWidgetTitleBar.cpp @@ -0,0 +1,289 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockWidgetTitleBar.cpp +/// \author Uwe Kindler +/// \date 27.02.2017 +/// \brief Implementation of CDockWidgetTitleBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "DockWidgetTitleBar.h" + +#include +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockManager.h" +#include "DockOverlay.h" +#include "DockWidget.h" +#include "FloatingDockContainer.h" +#include "ads_globals.h" + +namespace ads { + /** + * The different dragging states + */ + enum eDragState { + DraggingInactive, //!< DraggingInactive + DraggingMousePressed, //!< DraggingMousePressed + DraggingTab, //!< DraggingTab + DraggingFloatingWidget //!< DraggingFloatingWidget + }; + + /** + * Private data class of CDockWidgetTitleBar class (pimpl) + */ + struct DockWidgetTitleBarPrivate { + CDockWidgetTitleBar *_this; + CDockWidget *DockWidget; + QLabel *IconLabel; + QLabel *TitleLabel; + QPoint DragStartMousePosition; + bool IsActiveTab = false; + CDockAreaWidget *DockArea = nullptr; + eDragState DragState = DraggingInactive; + CFloatingDockContainer *FloatingWidget = nullptr; + QIcon Icon; + + /** + * Private data constructor + */ + DockWidgetTitleBarPrivate(CDockWidgetTitleBar *_public); + + /** + * Creates the complete layout including all controls + */ + void createLayout(); + + /** + * Moves the tab depending on the position in the given mouse event + */ + void moveTab(QMouseEvent *ev); + + /** + * Test function for current drag state + */ + bool isDraggingState(eDragState dragState) { return this->DragState == dragState; } + + /** + * Returns true if the given global point is inside the title area geometry + * rectangle. + * The position is given as global position. + */ + bool titleAreaGeometryContains(const QPoint &GlobalPos) const { + return DockArea->titleAreaGeometry().contains(DockArea->mapFromGlobal(GlobalPos)); + } + + /** + * Starts floating of the dock widget that belongs to this title bar + * Returns true, if floating has been started and false if floating + * is not possible for any reason + */ + bool startFloating(); + }; + // struct DockWidgetTitleBarPrivate + + //============================================================================ + DockWidgetTitleBarPrivate::DockWidgetTitleBarPrivate(CDockWidgetTitleBar *_public) : _this(_public) {} + + //============================================================================ + void DockWidgetTitleBarPrivate::createLayout() { + QBoxLayout *l = new QBoxLayout(QBoxLayout::LeftToRight); + l->setContentsMargins(0, 0, 0, 0); + _this->setLayout(l); + + IconLabel = new QLabel(); + IconLabel->setAlignment(Qt::AlignVCenter); + l->addWidget(IconLabel, Qt::AlignVCenter); + + TitleLabel = new QLabel(); + l->addWidget(TitleLabel, 1); + + IconLabel->setVisible(false); + TitleLabel->setVisible(true); + TitleLabel->setText(DockWidget->windowTitle()); + } + + //============================================================================ + void DockWidgetTitleBarPrivate::moveTab(QMouseEvent *ev) { + ev->accept(); + int left, top, right, bottom; + _this->getContentsMargins(&left, &top, &right, &bottom); + QPoint moveToPos = _this->mapToParent(ev->pos()) - DragStartMousePosition; + moveToPos.setY(0); + _this->move(moveToPos); + _this->raise(); + } + + //============================================================================ + bool DockWidgetTitleBarPrivate::startFloating() { + // if this is the last dock widget inside of this floating widget, + // then it does not make any sense, to make it floating because + // it is already floating + if (DockWidget->dockContainer()->isFloating() && (DockWidget->dockContainer()->visibleDockAreaCount() == 1) && + (DockWidget->dockAreaWidget()->count() == 1)) { + return false; + } + + DragState = DraggingFloatingWidget; + QSize Size = DockArea->size(); + CFloatingDockContainer *FloatingWidget = nullptr; + if (DockArea->count() > 1) { + // If section widget has multiple tabs, we take only one tab + FloatingWidget = new CFloatingDockContainer(DockWidget); + } else { + // If section widget has only one content widget, we can move the complete + // dock area into floating widget + FloatingWidget = new CFloatingDockContainer(DockArea); + } + + FloatingWidget->startFloating(DragStartMousePosition, Size); + auto Overlay = DockWidget->dockManager()->containerOverlay(); + Overlay->setAllowedAreas(OuterDockAreas); + this->FloatingWidget = FloatingWidget; + return true; + } + + //============================================================================ + CDockWidgetTitleBar::CDockWidgetTitleBar(CDockWidget *DockWidget, QWidget *parent) + : QFrame(parent), d(new DockWidgetTitleBarPrivate(this)) { + setAttribute(Qt::WA_NoMousePropagation, true); + d->DockWidget = DockWidget; + d->createLayout(); + } + + //============================================================================ + CDockWidgetTitleBar::~CDockWidgetTitleBar() { delete d; } + + //============================================================================ + void CDockWidgetTitleBar::mousePressEvent(QMouseEvent *ev) { + if (ev->button() == Qt::LeftButton) { + ev->accept(); + d->DragStartMousePosition = ev->pos(); + d->DragState = DraggingMousePressed; + return; + } + QFrame::mousePressEvent(ev); + } + + //============================================================================ + void CDockWidgetTitleBar::mouseReleaseEvent(QMouseEvent *ev) { + // End of tab moving, change order now + if (d->isDraggingState(DraggingTab) && d->DockArea) { + // Find tab under mouse + QPoint pos = d->DockArea->mapFromGlobal(ev->globalPos()); + int fromIndex = d->DockArea->tabIndex(d->DockWidget); + int toIndex = d->DockArea->indexOfContentByTitlePos(pos, this); + if (-1 == toIndex) { + toIndex = d->DockArea->count() - 1; + } + d->DockArea->reorderDockWidget(fromIndex, toIndex); + } + + if (!d->DragStartMousePosition.isNull()) { + emit clicked(); + } + + d->DragStartMousePosition = QPoint(); + d->DragState = DraggingInactive; + QFrame::mouseReleaseEvent(ev); + } + + //============================================================================ + void CDockWidgetTitleBar::mouseMoveEvent(QMouseEvent *ev) { + if (!(ev->buttons() & Qt::LeftButton) || d->isDraggingState(DraggingInactive)) { + d->DragState = DraggingInactive; + QFrame::mouseMoveEvent(ev); + return; + } + + if (d->isDraggingState(DraggingFloatingWidget)) { + d->FloatingWidget->moveFloating(); + QFrame::mouseMoveEvent(ev); + return; + } + + // move tab + if (d->isDraggingState(DraggingTab)) { + d->moveTab(ev); + } + + bool MouseInsideTitleArea = d->titleAreaGeometryContains(ev->globalPos()); + if (!MouseInsideTitleArea) { + d->startFloating(); + return; + } else if (d->DockArea->count() > 1 && + (ev->pos() - d->DragStartMousePosition).manhattanLength() >= + QApplication::startDragDistance()) // Wait a few pixels before start moving + { + d->DragState = DraggingTab; + return; + } + + QFrame::mouseMoveEvent(ev); + } + + //============================================================================ + bool CDockWidgetTitleBar::isActiveTab() const { return d->IsActiveTab; } + + //============================================================================ + void CDockWidgetTitleBar::setActiveTab(bool active) { + if (d->IsActiveTab == active) { + return; + } + + d->IsActiveTab = active; + style()->unpolish(this); + style()->polish(this); + d->TitleLabel->style()->unpolish(d->TitleLabel); + d->TitleLabel->style()->polish(d->TitleLabel); + update(); + + emit activeTabChanged(); + } + + //============================================================================ + CDockWidget *CDockWidgetTitleBar::dockWidget() const { return d->DockWidget; } + + //============================================================================ + void CDockWidgetTitleBar::setDockAreaWidget(CDockAreaWidget *DockArea) { d->DockArea = DockArea; } + + //============================================================================ + CDockAreaWidget *CDockWidgetTitleBar::dockAreaWidget() const { return d->DockArea; } + + //============================================================================ + void CDockWidgetTitleBar::setIcon(const QIcon &Icon) { + d->Icon = Icon; + d->IconLabel->setPixmap(Icon.pixmap(this->windowHandle(), QSize(16, 16))); + d->IconLabel->setVisible(true); + } + + //============================================================================ + const QIcon &CDockWidgetTitleBar::icon() const { return d->Icon; } +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF DockWidgetTitleBar.cpp diff --git a/editor/widgets/dock/DockWidgetTitleBar.h b/editor/widgets/dock/DockWidgetTitleBar.h new file mode 100644 index 00000000..7f8282d1 --- /dev/null +++ b/editor/widgets/dock/DockWidgetTitleBar.h @@ -0,0 +1,112 @@ +#ifndef DockWidgetTitleBarH +#define DockWidgetTitleBarH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file DockWidgetTitleBar.h +/// \author Uwe Kindler +/// \date 27.02.2017 +/// \brief Declaration of CDockWidgetTitleBar class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +namespace ads { + class CDockWidget; + class CDockAreaWidget; + struct DockWidgetTitleBarPrivate; + + /** + * A dock widget title bar that shows a title and an icon + */ + class CDockWidgetTitleBar : public QFrame { + Q_OBJECT + Q_PROPERTY(bool activeTab READ isActiveTab WRITE setActiveTab NOTIFY activeTabChanged) + + private: + DockWidgetTitleBarPrivate *d; ///< private data (pimpl) + friend struct DockWidgetTitleBarPrivate; + + protected: + virtual void mousePressEvent(QMouseEvent *ev) override; + virtual void mouseReleaseEvent(QMouseEvent *ev) override; + virtual void mouseMoveEvent(QMouseEvent *ev) override; + + public: + /** + * Default Constructor + * param[in] DockWidget The dock widget this title bar belongs to + * param[in] parent The parent widget of this title bar + */ + CDockWidgetTitleBar(CDockWidget *DockWidget, QWidget *parent = 0); + + /** + * Virtual Destructor + */ + virtual ~CDockWidgetTitleBar(); + + /** + * Returns true, if this is the active tab + */ + bool isActiveTab() const; + + /** + * Set this true to make this tab the active tab + */ + void setActiveTab(bool active); + + /** + * Returns the dock widget this title widget belongs to + */ + CDockWidget *dockWidget() const; + + /** + * Sets the dock area widget the dockWidget returned by dockWidget() + * function belongs to. + */ + void setDockAreaWidget(CDockAreaWidget *DockArea); + + /** + * Returns the dock area widget this title bar belongs to. + * \return This function returns 0 if the dock widget that owns this title + * bar widget has not been added to any dock area yet. + */ + CDockAreaWidget *dockAreaWidget() const; + + /** + * Sets the icon to show in title bar + */ + void setIcon(const QIcon &Icon); + + /** + * Returns the icon + */ + const QIcon &icon() const; + + signals: + void activeTabChanged(); + void clicked(); + }; // class DockWidgetTitleBar +} +// namespace ads +//----------------------------------------------------------------------------- +#endif // DockWidgetTitleBarH diff --git a/editor/widgets/dock/FloatingDockContainer.cpp b/editor/widgets/dock/FloatingDockContainer.cpp new file mode 100644 index 00000000..40387838 --- /dev/null +++ b/editor/widgets/dock/FloatingDockContainer.cpp @@ -0,0 +1,351 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file FloatingDockContainer.cpp +/// \author Uwe Kindler +/// \date 01.03.2017 +/// \brief Implementation of CFloatingDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include "FloatingDockContainer.h" + +#include +#include +#include +#include +#include + +#include "DockAreaWidget.h" +#include "DockContainerWidget.h" +#include "DockManager.h" +#include "DockOverlay.h" +#include "DockWidget.h" + +#include + +namespace ads { + static unsigned int zOrderCounter = 0; + + /** + * Private data class of CFloatingDockContainer class (pimpl) + */ + struct FloatingDockContainerPrivate { + CFloatingDockContainer *_this; + CDockContainerWidget *DockContainer; + unsigned int zOrderIndex = ++zOrderCounter; + QPointer DockManager; + bool DraggingActive = false; + bool NonClientAreaMouseButtonPress = false; + QPoint DragStartMousePosition; + CDockContainerWidget *DropContainer = nullptr; + CDockAreaWidget *SingleDockArea = nullptr; + + /** + * Private data constructor + */ + FloatingDockContainerPrivate(CFloatingDockContainer *_public); + + void titleMouseReleaseEvent(); + void updateDropOverlays(const QPoint &GlobalPos); + void setDraggingActive(bool Active); + }; + // struct FloatingDockContainerPrivate + + //============================================================================ + FloatingDockContainerPrivate::FloatingDockContainerPrivate(CFloatingDockContainer *_public) : _this(_public) {} + + //============================================================================ + void FloatingDockContainerPrivate::titleMouseReleaseEvent() { + setDraggingActive(false); + if (!DropContainer) { + return; + } + + if (DockManager->dockAreaOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea || + DockManager->containerOverlay()->dropAreaUnderCursor() != InvalidDockWidgetArea) { + // Resize the floating widget to the size of the highlighted drop area + // rectangle + QRect Rect = DockManager->containerOverlay()->dropOverlayRect(); + if (!Rect.isValid()) { + Rect = DockManager->dockAreaOverlay()->rect(); + } + + if (Rect.isValid()) { + _this->resize(Rect.size()); + } + DropContainer->dropFloatingWidget(_this, QCursor::pos()); + } + + DockManager->containerOverlay()->hideOverlay(); + DockManager->dockAreaOverlay()->hideOverlay(); + } + + //============================================================================ + void FloatingDockContainerPrivate::updateDropOverlays(const QPoint &GlobalPos) { + if (!_this->isVisible() || !DockManager) { + return; + } + + auto Containers = DockManager->dockContainers(); + CDockContainerWidget *TopContainer = nullptr; + for (auto ContainerWidget : Containers) { + if (!ContainerWidget->isVisible()) { + continue; + } + + if (DockContainer == ContainerWidget) { + continue; + } + + QPoint MappedPos = ContainerWidget->mapFromGlobal(GlobalPos); + if (ContainerWidget->rect().contains(MappedPos)) { + if (!TopContainer || ContainerWidget->isInFrontOf(TopContainer)) { + TopContainer = ContainerWidget; + } + } + } + + DropContainer = TopContainer; + auto ContainerOverlay = DockManager->containerOverlay(); + auto DockAreaOverlay = DockManager->dockAreaOverlay(); + + if (!TopContainer) { + ContainerOverlay->hideOverlay(); + DockAreaOverlay->hideOverlay(); + return; + } + + int VisibleDockAreas = TopContainer->visibleDockAreaCount(); + ContainerOverlay->setAllowedAreas(VisibleDockAreas > 1 ? OuterDockAreas : AllDockAreas); + DockWidgetArea ContainerArea = ContainerOverlay->showOverlay(TopContainer); + + auto DockArea = TopContainer->dockAreaAt(GlobalPos); + if (DockArea && DockArea->isVisible() && VisibleDockAreas > 0) { + DockAreaOverlay->enableDropPreview(true); + DockAreaOverlay->setAllowedAreas((VisibleDockAreas == 1) ? NoDockWidgetArea : AllDockAreas); + DockWidgetArea Area = DockAreaOverlay->showOverlay(DockArea); + + // A CenterDockWidgetArea for the dockAreaOverlay() indicates that + // the mouse is in the title bar. If the ContainerArea is valid + // then we ignore the dock area of the dockAreaOverlay() and disable + // the drop preview + if ((Area == CenterDockWidgetArea) && (ContainerArea != InvalidDockWidgetArea)) { + DockAreaOverlay->enableDropPreview(false); + ContainerOverlay->enableDropPreview(true); + } else { + ContainerOverlay->enableDropPreview(InvalidDockWidgetArea == Area); + } + } else { + DockAreaOverlay->hideOverlay(); + } + } + + //============================================================================ + void FloatingDockContainerPrivate::setDraggingActive(bool Active) { + DraggingActive = Active; + if (!DraggingActive) { + NonClientAreaMouseButtonPress = false; + } + } + + //============================================================================ + CFloatingDockContainer::CFloatingDockContainer(CDockManager *DockManager) + : QWidget(DockManager, Qt::Window), d(new FloatingDockContainerPrivate(this)) { + // setAttribute(Qt::WA_DeleteOnClose); + d->DockManager = DockManager; + QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom); + l->setContentsMargins(0, 0, 0, 0); + l->setSpacing(0); + setLayout(l); + + d->DockContainer = new CDockContainerWidget(DockManager, this); + connect(d->DockContainer, SIGNAL(dockAreasAdded()), this, SLOT(onDockAreasAddedOrRemoved())); + connect(d->DockContainer, SIGNAL(dockAreasRemoved()), this, SLOT(onDockAreasAddedOrRemoved())); + l->addWidget(d->DockContainer); + DockManager->registerFloatingWidget(this); + + // We install an event filter to detect mouse release events because we + // do not receive mouse release event if the floating widget is behind + // the drop overlay cross + qApp->installEventFilter(this); + } + + //============================================================================ + CFloatingDockContainer::CFloatingDockContainer(CDockAreaWidget *DockArea) + : CFloatingDockContainer(DockArea->dockManager()) { + d->DockContainer->addDockArea(DockArea); + } + + //============================================================================ + CFloatingDockContainer::CFloatingDockContainer(CDockWidget *DockWidget) + : CFloatingDockContainer(DockWidget->dockManager()) { + d->DockContainer->addDockWidget(CenterDockWidgetArea, DockWidget); + } + + //============================================================================ + CFloatingDockContainer::~CFloatingDockContainer() { + if (d->DockManager) { + d->DockManager->removeFloatingWidget(this); + } + delete d; + } + + //============================================================================ + CDockContainerWidget *CFloatingDockContainer::dockContainer() const { return d->DockContainer; } + + //============================================================================ + void CFloatingDockContainer::changeEvent(QEvent *event) { + QWidget::changeEvent(event); + if ((event->type() == QEvent::ActivationChange) && isActiveWindow()) { + d->zOrderIndex = ++zOrderCounter; + return; + } + } + + //============================================================================ + void CFloatingDockContainer::moveEvent(QMoveEvent *event) { + QWidget::moveEvent(event); + if (!qApp->mouseButtons().testFlag(Qt::LeftButton)) { + if (d->DraggingActive) { + d->setDraggingActive(false); + } + return; + } + + if (d->DraggingActive) { + d->updateDropOverlays(QCursor::pos()); + } + } + + //============================================================================ + void CFloatingDockContainer::closeEvent(QCloseEvent *event) { + std::cout << "closeEvent" << std::endl; + d->setDraggingActive(false); + QWidget::closeEvent(event); + } + + //============================================================================ + void CFloatingDockContainer::hideEvent(QHideEvent *event) { + std::cout << "hideEvent" << std::endl; + QWidget::hideEvent(event); + auto OpenDockAreas = d->DockContainer->openedDockAreas(); + for (auto DockArea : OpenDockAreas) { + auto OpenDockWidgets = DockArea->openedDockWidgets(); + for (auto DockWidget : OpenDockWidgets) { + DockWidget->toggleView(false); + } + } + } + + //============================================================================ + void CFloatingDockContainer::showEvent(QShowEvent *event) { + QWidget::showEvent(event); + CDockContainerWidget *DockContainer = dockContainer(); + for (int i = 0; i < DockContainer->dockAreaCount(); ++i) { + auto DockArea = DockContainer->dockArea(i); + for (auto DockWidget : DockArea->openedDockWidgets()) { + DockWidget->setToggleViewActionChecked(true); + } + } + } + + //============================================================================ + bool CFloatingDockContainer::event(QEvent *e) { + if (e->type() == QEvent::NonClientAreaMouseButtonPress) { + if (QGuiApplication::mouseButtons() == Qt::LeftButton) { + d->setDraggingActive(true); + d->NonClientAreaMouseButtonPress = true; + } + } else if (e->type() == QEvent::NonClientAreaMouseButtonDblClick) { + d->setDraggingActive(false); + } else if ((e->type() == QEvent::NonClientAreaMouseButtonRelease) && d->DraggingActive) { + d->titleMouseReleaseEvent(); + } else if (d->NonClientAreaMouseButtonPress && (e->type() == QEvent::Resize)) { + // If user resizes the floating widget, we do not want to show any + // drop overlays or drop overlay icons + d->setDraggingActive(false); + } + + return QWidget::event(e); + } + + //============================================================================ + bool CFloatingDockContainer::eventFilter(QObject *watched, QEvent *event) { + Q_UNUSED(watched); + if (event->type() == QEvent::MouseButtonRelease && d->DraggingActive) { + d->titleMouseReleaseEvent(); + } + + return false; + } + + //============================================================================ + void CFloatingDockContainer::startFloating(const QPoint &Pos, const QSize &Size) { + resize(Size); + d->setDraggingActive(true); + QPoint TargetPos = QCursor::pos() - Pos; + move(TargetPos); + show(); + d->DragStartMousePosition = Pos; + } + + //============================================================================ + void CFloatingDockContainer::moveFloating() { + int BorderSize = (frameSize().width() - size().width()) / 2; + const QPoint moveToPos = QCursor::pos() - d->DragStartMousePosition - QPoint(BorderSize, 0); + move(moveToPos); + } + + //============================================================================ + void CFloatingDockContainer::onDockAreasAddedOrRemoved() { + if (d->DockContainer->dockAreaCount() == 1) { + d->SingleDockArea = d->DockContainer->dockArea(0); + this->setWindowTitle(d->SingleDockArea->currentDockWidget()->windowTitle()); + connect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, SLOT(onDockAreaCurrentChanged(int))); + } else { + if (d->SingleDockArea) { + disconnect(d->SingleDockArea, SIGNAL(currentChanged(int)), this, SLOT(onDockAreaCurrentChanged(int))); + d->SingleDockArea = nullptr; + } + this->setWindowTitle(qApp->applicationDisplayName()); + } + } + + //============================================================================ + void CFloatingDockContainer::onDockAreaCurrentChanged(int Index) { + Q_UNUSED(Index); + this->setWindowTitle(d->SingleDockArea->currentDockWidget()->windowTitle()); + } + + //============================================================================ + bool CFloatingDockContainer::restoreState(QXmlStreamReader &Stream, bool Testing) { + if (!d->DockContainer->restoreState(Stream, Testing)) { + return false; + } + onDockAreasAddedOrRemoved(); + return true; + } + +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF FloatingDockContainer.cpp diff --git a/editor/widgets/dock/FloatingDockContainer.h b/editor/widgets/dock/FloatingDockContainer.h new file mode 100644 index 00000000..ebd47dd4 --- /dev/null +++ b/editor/widgets/dock/FloatingDockContainer.h @@ -0,0 +1,116 @@ +#ifndef FloatingDockContainerH +#define FloatingDockContainerH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file FloatingDockContainer.h +/// \author Uwe Kindler +/// \date 01.03.2017 +/// \brief Declaration of CFloatingDockContainer class +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +class QXmlStreamReader; + +namespace ads { + struct FloatingDockContainerPrivate; + class CDockAreaWidget; + class CDockContainerWidget; + class CDockWidget; + class CDockManager; + + /** + * This implements a floating widget that is a dock container that accepts + * docking of dock widgets like the main window and that can be docked into + * another dock container + */ + class CFloatingDockContainer : public QWidget { + Q_OBJECT + private: + FloatingDockContainerPrivate *d; ///< private data (pimpl) + friend struct FloatingDockContainerPrivate; + + private slots: + void onDockAreasAddedOrRemoved(); + void onDockAreaCurrentChanged(int Index); + + protected: // reimplements QWidget + virtual void changeEvent(QEvent *event) override; + virtual void moveEvent(QMoveEvent *event) override; + virtual bool event(QEvent *e) override; + virtual void closeEvent(QCloseEvent *event) override; + virtual void hideEvent(QHideEvent *event) override; + virtual void showEvent(QShowEvent *event) override; + virtual bool eventFilter(QObject *watched, QEvent *event) override; + + public: + /** + * Create empty flatingb widget - required for restore state + */ + CFloatingDockContainer(CDockManager *DockManager); + + /** + * Create floating widget with the given dock area + */ + CFloatingDockContainer(CDockAreaWidget *DockArea); + + /** + * Create floating widget with the given dock widget + */ + CFloatingDockContainer(CDockWidget *DockWidget); + + /** + * Virtual Destructor + */ + virtual ~CFloatingDockContainer(); + + /** + * Access function for the internal dock container + */ + CDockContainerWidget *dockContainer() const; + + /** + * Starts floating at the given global position. + * Use moveToGlobalPos() to move the widget to a new position + * depending on the start position given in Pos parameter + */ + void startFloating(const QPoint &Pos, const QSize &Size = QSize()); + + /** + * Moves the widget to a new position relative to the position given when + * startFloating() was called + */ + void moveFloating(); + + /** + * Restores the state from given stream. + * If Testing is true, the function only parses the data from the given + * stream but does not restore anything. You can use this check for + * faulty files before you start restoring the state + */ + bool restoreState(QXmlStreamReader &Stream, bool Testing); + }; // class FloatingDockContainer +} +// namespace ads +//----------------------------------------------------------------------------- +#endif // FloatingDockContainerH diff --git a/editor/widgets/dock/LICENSE b/editor/widgets/dock/LICENSE new file mode 100644 index 00000000..e7d2602f --- /dev/null +++ b/editor/widgets/dock/LICENSE @@ -0,0 +1,15 @@ +Qt Advanced Docking System +Copyright (C) 2017 Uwe Kindler + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; If not, see . diff --git a/editor/widgets/dock/ads_globals.cpp b/editor/widgets/dock/ads_globals.cpp new file mode 100644 index 00000000..e3bbf9ff --- /dev/null +++ b/editor/widgets/dock/ads_globals.cpp @@ -0,0 +1,76 @@ +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file ads_globals.cpp +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Implementation of +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +#include "DockSplitter.h" +#include "ads_globals.h" + +namespace ads { + + namespace internal { + //============================================================================ + QSplitter *newSplitter(Qt::Orientation orientation, QWidget *parent) { + QSplitter *s = new CDockSplitter(orientation, parent); + s->setProperty("ads-splitter", QVariant(true)); + s->setChildrenCollapsible(false); + s->setOpaqueResize(false); + return s; + } + + //============================================================================ + void replaceSplitterWidget(QSplitter *Splitter, QWidget *From, QWidget *To) { + int index = Splitter->indexOf(From); + From->setParent(0); + Splitter->insertWidget(index, To); + } + + //============================================================================ + CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area) { + switch (Area) { + case TopDockWidgetArea: + return CDockInsertParam(Qt::Vertical, false); + case RightDockWidgetArea: + return CDockInsertParam(Qt::Horizontal, true); + case CenterDockWidgetArea: + case BottomDockWidgetArea: + return CDockInsertParam(Qt::Vertical, true); + case LeftDockWidgetArea: + return CDockInsertParam(Qt::Horizontal, false); + default: + CDockInsertParam(Qt::Vertical, false); + } // switch (Area) + + return CDockInsertParam(Qt::Vertical, false); + } + + } // namespace internal +} // namespace ads + +//--------------------------------------------------------------------------- +// EOF ads_globals.cpp diff --git a/editor/widgets/dock/ads_globals.h b/editor/widgets/dock/ads_globals.h new file mode 100644 index 00000000..a5a854ae --- /dev/null +++ b/editor/widgets/dock/ads_globals.h @@ -0,0 +1,101 @@ +#ifndef ads_globalsH +#define ads_globalsH +/******************************************************************************* +** Qt Advanced Docking System +** Copyright (C) 2017 Uwe Kindler +** +** This library is free software; you can redistribute it and/or +** modify it under the terms of the GNU Lesser General Public +** License as published by the Free Software Foundation; either +** version 2.1 of the License, or (at your option) any later version. +** +** This library is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public +** License along with this library; If not, see . +******************************************************************************/ + +//============================================================================ +/// \file ads_globals.h +/// \author Uwe Kindler +/// \date 24.02.2017 +/// \brief Declaration of +//============================================================================ + +//============================================================================ +// INCLUDES +//============================================================================ +#include + +class QSplitter; + +namespace ads { + enum DockWidgetArea { + NoDockWidgetArea = 0x00, + LeftDockWidgetArea = 0x01, + RightDockWidgetArea = 0x02, + TopDockWidgetArea = 0x04, + BottomDockWidgetArea = 0x08, + CenterDockWidgetArea = 0x10, + + InvalidDockWidgetArea = NoDockWidgetArea, + OuterDockAreas = TopDockWidgetArea | LeftDockWidgetArea | RightDockWidgetArea | BottomDockWidgetArea, + AllDockAreas = OuterDockAreas | CenterDockWidgetArea + }; + Q_DECLARE_FLAGS(DockWidgetAreas, DockWidgetArea) + + namespace internal { + + /** + * Helper function to create new splitter widgets + */ + QSplitter *newSplitter(Qt::Orientation orientation, QWidget *parent = 0); + + /** + * Replace the from widget in the given splitter with the To widget + */ + void replaceSplitterWidget(QSplitter *Splitter, QWidget *From, QWidget *To); + + /** + * Convenience class for QPair to provide better naming than first and + * second + */ + class CDockInsertParam : public QPair { + public: + using QPair::QPair; + Qt::Orientation orientation() const { return this->first; } + bool append() const { return this->second; } + int insertOffset() const { return append() ? 1 : 0; } + }; + + /** + * Returns the insertion parameters for the given dock area + */ + CDockInsertParam dockAreaInsertParameters(DockWidgetArea Area); + + /** + * Searches for the parent widget of the given type. + * Returns the parent widget of the given widget or 0 if the widget is not + * child of any widget of type T + */ + template + T findParent(const QWidget *w) { + QWidget *parentWidget = w->parentWidget(); + while (parentWidget) { + T ParentImpl = dynamic_cast(parentWidget); + if (ParentImpl) { + return ParentImpl; + } + parentWidget = parentWidget->parentWidget(); + } + return 0; + } + + } // namespace internal +} // namespace ads + +//--------------------------------------------------------------------------- +#endif // ads_globalsH diff --git a/editor/widgets/history/HistoryPanel.cpp b/editor/widgets/history/HistoryPanel.cpp index 4135022a..6d0b2a6c 100644 --- a/editor/widgets/history/HistoryPanel.cpp +++ b/editor/widgets/history/HistoryPanel.cpp @@ -5,7 +5,7 @@ using namespace AxiomGui; -HistoryPanel::HistoryPanel(AxiomModel::HistoryList *list, QWidget *parent) : DockPanel("History", parent), list(list) { +HistoryPanel::HistoryPanel(AxiomModel::HistoryList *list, QWidget *parent) : ads::CDockWidget("History", parent), list(list) { listWidget = new QListWidget(this); setWidget(listWidget); diff --git a/editor/widgets/history/HistoryPanel.h b/editor/widgets/history/HistoryPanel.h index 790565ff..9479d19f 100644 --- a/editor/widgets/history/HistoryPanel.h +++ b/editor/widgets/history/HistoryPanel.h @@ -2,7 +2,7 @@ #include -#include "../dock/DockPanel.h" +#include "../dock/DockWidget.h" #include "common/TrackedObject.h" namespace AxiomModel { @@ -13,7 +13,7 @@ namespace AxiomModel { namespace AxiomGui { - class HistoryPanel : public DockPanel, public AxiomCommon::TrackedObject { + class HistoryPanel : public ads::CDockWidget, public AxiomCommon::TrackedObject { Q_OBJECT public: diff --git a/editor/widgets/modulebrowser/ModuleBrowserPanel.cpp b/editor/widgets/modulebrowser/ModuleBrowserPanel.cpp index 6519f237..58a458f7 100644 --- a/editor/widgets/modulebrowser/ModuleBrowserPanel.cpp +++ b/editor/widgets/modulebrowser/ModuleBrowserPanel.cpp @@ -11,7 +11,7 @@ using namespace AxiomGui; ModuleBrowserPanel::ModuleBrowserPanel(MainWindow *window, AxiomModel::Library *library, QWidget *parent) - : DockPanel("Modules", parent), library(library) { + : ads::CDockWidget("Modules", parent), library(library) { setStyleSheet(AxiomUtil::loadStylesheet(":/styles/ModuleBrowserPanel.qss")); auto mainLayout = new QGridLayout(this); diff --git a/editor/widgets/modulebrowser/ModuleBrowserPanel.h b/editor/widgets/modulebrowser/ModuleBrowserPanel.h index 67f38a7a..19aec61e 100644 --- a/editor/widgets/modulebrowser/ModuleBrowserPanel.h +++ b/editor/widgets/modulebrowser/ModuleBrowserPanel.h @@ -2,7 +2,7 @@ #include -#include "../dock/DockPanel.h" +#include "../dock/DockWidget.h" #include "common/TrackedObject.h" class QTabBar; @@ -16,7 +16,7 @@ namespace AxiomGui { class MainWindow; - class ModuleBrowserPanel : public DockPanel, public AxiomCommon::TrackedObject { + class ModuleBrowserPanel : public ads::CDockWidget, public AxiomCommon::TrackedObject { Q_OBJECT public: diff --git a/editor/widgets/modulebrowser/ModulePreviewButton.cpp b/editor/widgets/modulebrowser/ModulePreviewButton.cpp index 41c07182..2c6e7300 100644 --- a/editor/widgets/modulebrowser/ModulePreviewButton.cpp +++ b/editor/widgets/modulebrowser/ModulePreviewButton.cpp @@ -15,7 +15,7 @@ #include "ModulePreviewCanvas.h" #include "editor/model/Library.h" #include "editor/model/LibraryEntry.h" -#include "editor/model/objects/RootSurface.h" +#include "editor/model/objects/ModuleSurface.h" #include "editor/model/serialize/LibrarySerializer.h" #include "editor/model/serialize/ModelObjectSerializer.h" #include "editor/model/serialize/ProjectSerializer.h" @@ -85,7 +85,7 @@ void ModulePreviewButton::mousePressEvent(QMouseEvent *event) { } void ModulePreviewButton::mouseDoubleClickEvent(QMouseEvent *event) { - window->showSurface(nullptr, _entry->rootSurface(), true, false); + window->showSurface(nullptr, _entry->rootSurface(), false, false); } void ModulePreviewButton::contextMenuEvent(QContextMenuEvent *event) { @@ -101,7 +101,7 @@ void ModulePreviewButton::contextMenuEvent(QContextMenuEvent *event) { auto selectedAction = menu.exec(event->globalPos()); if (selectedAction == editAction) { - window->showSurface(nullptr, _entry->rootSurface(), true, false); + window->showSurface(nullptr, _entry->rootSurface(), false, false); } else if (selectedAction == propertiesAction) { ModulePropertiesWindow propWindow(library); propWindow.setEnteredName(_entry->name()); diff --git a/editor/widgets/node/NodeItem.cpp b/editor/widgets/node/NodeItem.cpp index cefff55c..4a529dec 100644 --- a/editor/widgets/node/NodeItem.cpp +++ b/editor/widgets/node/NodeItem.cpp @@ -26,6 +26,7 @@ #include "editor/model/objects/GraphControl.h" #include "editor/model/objects/GroupNode.h" #include "editor/model/objects/MidiControl.h" +#include "editor/model/objects/ModuleSurface.h" #include "editor/model/objects/Node.h" #include "editor/model/objects/NumControl.h" #include "editor/model/objects/PortalControl.h" @@ -240,7 +241,7 @@ void NodeItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void NodeItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { if (auto groupNode = dynamic_cast(node); groupNode && groupNode->nodes().value()) { event->accept(); - canvas->panel->window->showSurface(canvas->panel, *groupNode->nodes().value(), false, false); + canvas->panel->window->showSurface(canvas->panel, *groupNode->nodes().value(), true, false); } else if (auto customNode = dynamic_cast(node)) { event->accept(); customNode->setPanelOpen(!customNode->isPanelOpen()); @@ -311,7 +312,7 @@ void NodeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) { ref.setUuid(newEntry->rootSurface()->uuid(), newEntry->rootSurface()->uuid()); ref.setPos(newEntry->rootSurface()->uuid(), -centerPos); ModelObjectSerializer::deserializeChunk(deserializeStream, ProjectSerializer::schemaVersion, - newEntry->root(), newEntry->rootSurface()->uuid(), &ref); + newEntry->root(), newEntry->rootSurface()->uuid(), &ref, false); mainWindow->library()->addEntry(std::move(newEntry)); } diff --git a/editor/widgets/surface/NodeSurfacePanel.cpp b/editor/widgets/surface/NodeSurfacePanel.cpp index 7a7b9406..ff3f40ef 100644 --- a/editor/widgets/surface/NodeSurfacePanel.cpp +++ b/editor/widgets/surface/NodeSurfacePanel.cpp @@ -8,17 +8,16 @@ using namespace AxiomGui; NodeSurfacePanel::NodeSurfacePanel(MainWindow *window, AxiomModel::NodeSurface *surface) - : DockPanel(surface->name()), window(window) { + : ads::CDockWidget(surface->name()), window(window) { setStyleSheet(AxiomUtil::loadStylesheet(":/styles/SchematicPanel.qss")); surface->nameChanged.connect(this, &NodeSurfacePanel::setWindowTitle); - surface->removed.connect(this, &NodeSurfacePanel::close); + surface->removed.connect(this, &NodeSurfacePanel::cleanup); setWidget(new NodeSurfaceView(this, surface)); widget()->setParent(this); } -void NodeSurfacePanel::closeEvent(QCloseEvent *event) { - DockPanel::closeEvent(event); - emit closed(); +void NodeSurfacePanel::cleanup() { + toggleView(false); } diff --git a/editor/widgets/surface/NodeSurfacePanel.h b/editor/widgets/surface/NodeSurfacePanel.h index 0b937e3e..dc048f30 100644 --- a/editor/widgets/surface/NodeSurfacePanel.h +++ b/editor/widgets/surface/NodeSurfacePanel.h @@ -2,7 +2,7 @@ #include -#include "../dock/DockPanel.h" +#include "../dock/DockWidget.h" #include "common/TrackedObject.h" namespace AxiomModel { @@ -13,22 +13,16 @@ namespace AxiomGui { class MainWindow; - class NodeSurfacePanel : public DockPanel, public AxiomCommon::TrackedObject { + class NodeSurfacePanel : public ads::CDockWidget, public AxiomCommon::TrackedObject { Q_OBJECT public: MainWindow *window; - explicit NodeSurfacePanel(MainWindow *window, AxiomModel::NodeSurface *surface); + NodeSurfacePanel(MainWindow *window, AxiomModel::NodeSurface *surface); - signals: + private slots: - void closed(); - - protected: - void closeEvent(QCloseEvent *event) override; - - private: - QGraphicsScene *scene; + void cleanup(); }; } diff --git a/editor/widgets/windows/MainWindow.cpp b/editor/widgets/windows/MainWindow.cpp index 3c2b4da2..347f1a6b 100644 --- a/editor/widgets/windows/MainWindow.cpp +++ b/editor/widgets/windows/MainWindow.cpp @@ -17,6 +17,7 @@ #include "../GlobalActions.h" #include "../InteractiveImport.h" +#include "../dock/DockManager.h" #include "../history/HistoryPanel.h" #include "../modulebrowser/ModuleBrowserPanel.h" #include "../surface/NodeSurfacePanel.h" @@ -42,8 +43,8 @@ MainWindow::MainWindow(AxiomBackend::AudioBackend *backend) resize(1440, 810); setUnifiedTitleAndToolBarOnMac(true); - setDockNestingEnabled(true); - setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); + + dockManager = new ads::CDockManager(this); auto startTime = std::chrono::high_resolution_clock::now(); lockGlobalLibrary(); @@ -80,7 +81,7 @@ MainWindow::MainWindow(AxiomBackend::AudioBackend *backend) connect(&loadDebounceTimer, &QTimer::timeout, this, &MainWindow::triggerLibraryReloadDebounce); _modulePanel = std::make_unique(this, _library.get(), this); - addDockWidget(Qt::BottomDockWidgetArea, _modulePanel.get()); + dockManager->addDockWidget(ads::BottomDockWidgetArea, _modulePanel.get()); // build menus auto fileMenu = menuBar()->addMenu(tr("&File")); @@ -150,20 +151,19 @@ NodeSurfacePanel *MainWindow::showSurface(NodeSurfacePanel *fromPanel, AxiomMode auto newDock = std::make_unique(this, surface); auto newDockPtr = newDock.get(); - newDock->setAllowedAreas(Qt::AllDockWidgetAreas); + if (!fromPanel) { - addDockWidget(Qt::LeftDockWidgetArea, newDockPtr); - } else if (split) { - splitDockWidget(fromPanel, newDockPtr, Qt::Horizontal); - } else { - tabifyDockWidget(fromPanel, newDockPtr); + auto rootSurface = _project->rootSurface(); + if (surface == rootSurface) { + dockManager->addDockWidget(ads::TopDockWidgetArea, newDockPtr); + } else { + fromPanel = _openPanels[_project->rootSurface()].get(); + } + } - // raise() doesn't seem to work when called synchronously after tabifyDockWidget, so we wait for the next - // event loop iteration - QTimer::singleShot(0, newDockPtr, [newDockPtr]() { - newDockPtr->raise(); - newDockPtr->setFocus(Qt::OtherFocusReason); - }); + if (fromPanel) { + auto area = split ? ads::RightDockWidgetArea : ads::CenterDockWidgetArea; + dockManager->addDockWidget(area, newDockPtr, fromPanel->dockAreaWidget()); } if (!permanent) { @@ -256,8 +256,8 @@ void MainWindow::setProject(std::unique_ptr project) { _modulePanel->show(); _historyPanel = std::make_unique(&_project->mainRoot().history(), this); - addDockWidget(Qt::RightDockWidgetArea, _historyPanel.get()); - _historyPanel->hide(); + dockManager->addDockWidget(ads::RightDockWidgetArea, _historyPanel.get()); + _historyPanel->toggleView(false); _viewMenu->addAction(surfacePanel->toggleViewAction()); _viewMenu->addAction(_historyPanel->toggleViewAction()); diff --git a/editor/widgets/windows/MainWindow.h b/editor/widgets/windows/MainWindow.h index 393f1f57..ce57a837 100644 --- a/editor/widgets/windows/MainWindow.h +++ b/editor/widgets/windows/MainWindow.h @@ -19,6 +19,10 @@ namespace AxiomModel { class NodeSurface; } +namespace ads{ + class CDockManager; +} + namespace AxiomGui { class NodeSurfacePanel; @@ -88,6 +92,7 @@ namespace AxiomGui { void importLibraryFrom(const QString &path); private: + ads::CDockManager *dockManager; AxiomBackend::AudioBackend *_backend; MaximCompiler::Runtime _runtime; std::unique_ptr _project;