/******************************************************************************* ** 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 #include "DockManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FloatingDockContainer.h" #include "DockOverlay.h" #include "DockWidget.h" #include "ads_globals.h" #include "DockAreaWidget.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; QMap ViewMenuGroups; QMenu* ViewMenu; CDockManager::eViewMenuInsertionOrder MenuInsertionOrder = CDockManager::MenuAlphabeticallySorted; bool RestoringState = false; CDockManager::ConfigFlags ConfigFlags = CDockManager::DefaultConfig; /** * 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 restoreStateFromXml(const QByteArray &state, int version, bool Testing = internal::Restore); /** * Restore state */ bool restoreState(const QByteArray &state, int version); void restoreDockWidgetsOpenState(); void restoreDockAreasIndices(); void emitTopLevelEvents(); void hideFloatingWidgets() { // Hide updates of floating widgets from use for (auto FloatingWidget : FloatingWidgets) { FloatingWidget->hide(); } } void markDockWidgetsDirty() { for (auto DockWidget : DockWidgetsMap) { DockWidget->setProperty("dirty", true); } } /** * Restores the container with the given index */ bool restoreContainer(int Index, QXmlStreamReader& stream, bool Testing); /** * Loads the stylesheet */ void loadStylesheet(); /** * Adds action to menu - optionally in sorted order */ void addActionToMenu(QAction* Action, QMenu* Menu, bool InsertSorted); }; // struct DockManagerPrivate //============================================================================ DockManagerPrivate::DockManagerPrivate(CDockManager* _public) : _this(_public) { } //============================================================================ void DockManagerPrivate::loadStylesheet() { QString Result; QFile StyleSheetFile(":ads/stylesheets/default.css"); 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; } bool Result = false; if (Index >= Containers.count()) { CFloatingDockContainer* FloatingWidget = new CFloatingDockContainer(_this); Result = FloatingWidget->restoreState(stream, Testing); } else { qDebug() << "d->Containers[i]->restoreState "; auto Container = Containers[Index]; if (Container->isFloating()) { Result = Container->floatingWidget()->restoreState(stream, Testing); } else { Result = Container->restoreState(stream, Testing); } } return Result; } //============================================================================ bool DockManagerPrivate::checkFormat(const QByteArray &state, int version) { return restoreStateFromXml(state, version, internal::RestoreTesting); } //============================================================================ bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, bool Testing) { if (state.isEmpty()) { return false; } QXmlStreamReader s(state); s.readNextStartElement(); if (s.name() != "QtAdvancedDockingSystem") { return false; } qDebug() << s.attributes().value("Version"); bool ok; int v = s.attributes().value("Version").toInt(&ok); if (!ok || v != version) { return false; } bool Result = true; int DockContainers = s.attributes().value("Containers").toInt(); qDebug() << DockContainers; int DockContainerCount = 0; while (s.readNextStartElement()) { if (s.name() == "Container") { 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(); _this->removeDockContainer(FloatingWidgets[FloatingWidgetIndex + i]->dockContainer()); } } return Result; } //============================================================================ void DockManagerPrivate::restoreDockWidgetsOpenState() { // 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 : DockWidgetsMap) { if (DockWidget->property("dirty").toBool()) { DockWidget->flagAsUnassigned(); } else { DockWidget->toggleViewInternal(!DockWidget->property("closed").toBool()); } } } //============================================================================ void DockManagerPrivate::restoreDockAreasIndices() { // 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 int Count = 0; for (auto DockContainer : Containers) { Count++; for (int i = 0; i < DockContainer->dockAreaCount(); ++i) { CDockAreaWidget* DockArea = DockContainer->dockArea(i); QString DockWidgetName = DockArea->property("currentDockWidget").toString(); CDockWidget* DockWidget = nullptr; if (!DockWidgetName.isEmpty()) { DockWidget = _this->findDockWidget(DockWidgetName); } if (!DockWidget || DockWidget->isClosed()) { int Index = DockArea->indexOfFirstOpenDockWidget(); if (Index < 0) { continue; } DockArea->setCurrentIndex(Index); } else { DockArea->internalSetCurrentDockWidget(DockWidget); } } } } //============================================================================ void DockManagerPrivate::emitTopLevelEvents() { // Finally we need to send the topLevelChanged() signals for all dock // widgets if top level changed for (auto DockContainer : Containers) { CDockWidget* TopLevelDockWidget = DockContainer->topLevelDockWidget(); if (TopLevelDockWidget) { TopLevelDockWidget->emitTopLevelChanged(true); } else { for (int i = 0; i < DockContainer->dockAreaCount(); ++i) { auto DockArea = DockContainer->dockArea(i); for (auto DockWidget : DockArea->dockWidgets()) { DockWidget->emitTopLevelChanged(false); } } } } } //============================================================================ bool DockManagerPrivate::restoreState(const QByteArray &state, int version) { if (!checkFormat(state, version)) { qDebug() << "checkFormat: Error checking format!!!!!!!"; return false; } // Hide updates of floating widgets from use hideFloatingWidgets(); markDockWidgetsDirty(); if (!restoreStateFromXml(state, version)) { qDebug() << "restoreState: Error restoring state!!!!!!!"; return false; } restoreDockWidgetsOpenState(); restoreDockAreasIndices(); emitTopLevelEvents(); return true; } //============================================================================ void DockManagerPrivate::addActionToMenu(QAction* Action, QMenu* Menu, bool InsertSorted) { if (InsertSorted) { auto Actions = Menu->actions(); auto it = std::find_if(Actions.begin(), Actions.end(), [&Action](const QAction* a) { return a->text().compare(Action->text(), Qt::CaseInsensitive) > 0; }); if (it == Actions.end()) { Menu->addAction(Action); } else { Menu->insertAction(*it, Action); } } else { Menu->addAction(Action); } } //============================================================================ CDockManager::CDockManager(QWidget *parent) : CDockContainerWidget(this, parent), d(new DockManagerPrivate(this)) { createRootSplitter(); QMainWindow* MainWindow = dynamic_cast(parent); if (MainWindow) { MainWindow->setCentralWidget(this); } d->ViewMenu = new QMenu(tr("Show View"), 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); qDebug() << "d->FloatingWidgets.count() " << d->FloatingWidgets.count(); } //============================================================================ 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(eXmlMode XmlMode, int version) const { QByteArray xmldata; QXmlStreamWriter s(&xmldata); s.setAutoFormatting(XmlAutoFormattingEnabled == XmlMode); s.writeStartDocument(); s.writeStartElement("QtAdvancedDockingSystem"); s.writeAttribute("Version", QString::number(version)); s.writeAttribute("Containers", QString::number(d->Containers.count())); for (auto Container : d->Containers) { Container->saveState(s); } s.writeEndElement(); s.writeEndDocument(); return xmldata; } //============================================================================ bool CDockManager::restoreState(const QByteArray &state, int version) { // Prevent multiple calls as long as state is not restore. This may // happen, if QApplication::processEvents() is called somewhere if (d->RestoringState) { return false; } // We hide the complete dock manager here. Restoring the state means // that DockWidgets are removed from the DockArea internal stack layout // which in turn means, that each time a widget is removed the stack // will show and raise the next available widget which in turn // triggers show events for the dock widgets. To avoid this we hide the // dock manager. Because there will be no processing of application // events until this function is finished, the user will not see this // hiding bool IsHidden = this->isHidden(); if (!IsHidden) { hide(); } d->RestoringState = true; emit restoringState(); bool Result = d->restoreState(state, version); d->RestoringState = false; emit stateRestored(); if (!IsHidden) { show(); } return Result; } //============================================================================ CDockAreaWidget* CDockManager::addDockWidget(DockWidgetArea area, CDockWidget* Dockwidget, CDockAreaWidget* DockAreaWidget) { d->DockWidgetsMap.insert(Dockwidget->objectName(), Dockwidget); return CDockContainerWidget::addDockWidget(area, Dockwidget, DockAreaWidget); } //============================================================================ CDockAreaWidget* CDockManager::addDockWidgetTab(DockWidgetArea area, CDockWidget* Dockwidget) { CDockAreaWidget* AreaWidget = lastAddedDockAreaWidget(area); if (AreaWidget) { return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, AreaWidget); } else if (!openedDockAreas().isEmpty()) { return addDockWidget(area, Dockwidget, openedDockAreas().last()); } else { return addDockWidget(area, Dockwidget, nullptr); } } //============================================================================ CDockAreaWidget* CDockManager::addDockWidgetTabToArea(CDockWidget* Dockwidget, CDockAreaWidget* DockAreaWidget) { return addDockWidget(ads::CenterDockWidgetArea, Dockwidget, DockAreaWidget); } //============================================================================ CDockWidget* CDockManager::findDockWidget(const QString& ObjectName) const { return d->DockWidgetsMap.value(ObjectName, nullptr); } //============================================================================ void CDockManager::removeDockWidget(CDockWidget* Dockwidget) { d->DockWidgetsMap.remove(Dockwidget->objectName()); CDockContainerWidget::removeDockWidget(Dockwidget); } //============================================================================ QMap CDockManager::dockWidgetsMap() const { return d->DockWidgetsMap; } //============================================================================ void CDockManager::addPerspective(const QString& UniquePrespectiveName) { d->Perspectives.insert(UniquePrespectiveName, saveState()); emit perspectiveListChanged(); } //============================================================================ void CDockManager::removePerspective(const QString& Name) { removePerspectives({Name}); } //============================================================================ void CDockManager::removePerspectives(const QStringList& Names) { int Count = 0; for (auto Name : Names) { Count += d->Perspectives.remove(Name); } if (Count) { emit perspectivesRemoved(); emit perspectiveListChanged(); } } //============================================================================ QStringList CDockManager::perspectiveNames() const { return d->Perspectives.keys(); } //============================================================================ void CDockManager::openPerspective(const QString& PerspectiveName) { const auto Iterator = d->Perspectives.find(PerspectiveName); if (d->Perspectives.end() == Iterator) { return; } emit openingPerspective(PerspectiveName); restoreState(Iterator.value()); emit perspectiveOpened(PerspectiveName); } //============================================================================ 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(); } //============================================================================ QAction* CDockManager::addToggleViewActionToMenu(QAction* ToggleViewAction, const QString& Group, const QIcon& GroupIcon) { bool AlphabeticallySorted = (MenuAlphabeticallySorted == d->MenuInsertionOrder); if (!Group.isEmpty()) { QMenu* GroupMenu = d->ViewMenuGroups.value(Group, 0); if (!GroupMenu) { GroupMenu = new QMenu(Group, this); GroupMenu->setIcon(GroupIcon); d->addActionToMenu(GroupMenu->menuAction(), d->ViewMenu, AlphabeticallySorted); d->ViewMenuGroups.insert(Group, GroupMenu); } d->addActionToMenu(ToggleViewAction, GroupMenu, AlphabeticallySorted); return GroupMenu->menuAction(); } else { d->addActionToMenu(ToggleViewAction, d->ViewMenu, AlphabeticallySorted); return ToggleViewAction; } } //============================================================================ QMenu* CDockManager::viewMenu() const { return d->ViewMenu; } //============================================================================ void CDockManager::setViewMenuInsertionOrder(eViewMenuInsertionOrder Order) { d->MenuInsertionOrder = Order; } //=========================================================================== bool CDockManager::isRestoringState() const { return d->RestoringState; } //=========================================================================== int CDockManager::startDragDistance() { return QApplication::startDragDistance() * 1.5; } //=========================================================================== CDockManager::ConfigFlags CDockManager::configFlags() const { return d->ConfigFlags; } //=========================================================================== void CDockManager::setConfigFlags(const ConfigFlags Flags) { d->ConfigFlags = Flags; } } // namespace ads //--------------------------------------------------------------------------- // EOF DockManager.cpp