// Copyright (C) 2020 Uwe Kindler // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later #include "dockmanager.h" #include "ads_globals.h" #include "ads_globals_p.h" #include "advanceddockingsystemtr.h" #include "autohidedockcontainer.h" #include "dockareawidget.h" #include "dockfocuscontroller.h" #include "dockingstatereader.h" #include "dockoverlay.h" #include "docksplitter.h" #include "dockwidget.h" #include "floatingdockcontainer.h" #include "iconprovider.h" #include "workspacedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !(defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)) #include #endif Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg); using namespace Utils; namespace ADS { /** * Internal file version in case the structure changes internally */ enum eStateFileVersion { InitialVersion = 0, //!< InitialVersion Version1 = 1, //!< Version1 CurrentVersion = Version1 //!< CurrentVersion }; static DockManager::ConfigFlags g_staticConfigFlags = DockManager::DefaultNonOpaqueConfig; static DockManager::AutoHideFlags g_staticAutoHideConfigFlags; // auto hide is disabled by default static QString g_floatingContainersTitle; /** * Private data class of DockManager class (pimpl) */ class DockManagerPrivate { public: DockManager *q; QList> m_floatingWidgets; QList> m_hiddenFloatingWidgets; QList m_containers; DockOverlay *m_containerOverlay = nullptr; DockOverlay *m_dockAreaOverlay = nullptr; QMap m_dockWidgetsMap; bool m_restoringState = false; QVector m_uninitializedFloatingWidgets; DockFocusController *m_focusController = nullptr; DockWidget *m_centralWidget = nullptr; bool m_isLeavingMinimized = false; Qt::ToolButtonStyle m_toolBarStyleDocked = Qt::ToolButtonIconOnly; Qt::ToolButtonStyle m_toolBarStyleFloating = Qt::ToolButtonTextUnderIcon; QSize m_toolBarIconSizeDocked = QSize(16, 16); QSize m_toolBarIconSizeFloating = QSize(24, 24); QString m_workspacePresetsPath; QList m_workspaces; Workspace m_workspace; bool m_workspaceLocked = false; QtcSettings *m_settings = nullptr; bool m_modeChangeState = false; bool m_wasShown = false; bool m_workspaceOrderDirty = false; bool m_mcusProject = false; bool m_liteModeEnabled = false; /** * Private data constructor */ DockManagerPrivate(DockManager *parent); /** * Restores the state. If testing is set to true it will check if * the given data stream is a valid docking system state file. */ bool restoreStateFromXml(const QByteArray &state, int version, bool testing = false); /** * Restore state */ bool restoreState(const QByteArray &state, int version); void restoreDockWidgetsOpenState(); void restoreDockAreasIndices(); void emitTopLevelEvents(); void hideFloatingWidgets() { // Hide updates of floating widgets from user for (const auto &floatingWidget : std::as_const(m_floatingWidgets)) { if (floatingWidget) floatingWidget->hide(); } } void markDockWidgetsDirty() { for (const auto &dockWidget : std::as_const(m_dockWidgetsMap)) dockWidget->setProperty(internal::g_dirtyProperty, true); } /** * Restores the container with the given index */ bool restoreContainer(int index, DockingStateReader &stream, bool testing); void workspaceLoadingProgress(); }; // class DockManagerPrivate DockManagerPrivate::DockManagerPrivate(DockManager *parent) : q(parent) {} bool DockManagerPrivate::restoreContainer(int index, DockingStateReader &stream, bool testing) { if (testing) index = 0; bool result = false; if (index >= m_containers.count()) { FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); result = floatingWidget->restoreState(stream, testing); } else { auto container = m_containers[index]; if (container->isFloating()) result = container->floatingWidget()->restoreState(stream, testing); else result = container->restoreState(stream, testing); } return result; } bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, bool testing) { Q_UNUSED(version) // TODO version is not needed, why is it in here in the first place? if (state.isEmpty()) return false; DockingStateReader stateReader(state); if (!stateReader.readNextStartElement()) return false; if (stateReader.name() != QLatin1String("QtAdvancedDockingSystem")) return false; qCInfo(adsLog) << "Version" << stateReader.attributes().value("version"); bool ok; int v = stateReader.attributes().value("version").toInt(&ok); if (!ok || v > CurrentVersion) return false; stateReader.setFileVersion(v); qCInfo(adsLog) << "User version" << stateReader.attributes().value("userVersion"); // Older files do not support userVersion, but we still want to load them so we first test // if the attribute exists. if (!stateReader.attributes().value("userVersion").isEmpty()) { v = stateReader.attributes().value("userVersion").toInt(&ok); if (!ok || v != version) return false; } qCInfo(adsLog) << stateReader.attributes().value("containers").toInt(); if (m_centralWidget) { const auto centralWidgetAttribute = stateReader.attributes().value("centralWidget"); // If we have a central widget, but a state without central widget, then something is wrong. if (centralWidgetAttribute.isEmpty()) { qWarning() << "DockManager has central widget, but saved state does not have central widget."; return false; } // If the object name of the central widget does not match the name of the saved central // widget, something is wrong. if (m_centralWidget->objectName() != centralWidgetAttribute.toString()) { qWarning() << "Object name of central widget does not match name of central widget in " "saved state."; return false; } } bool result = true; int dockContainerCount = 0; while (stateReader.readNextStartElement()) { if (stateReader.name() == QLatin1String("container")) { result = restoreContainer(dockContainerCount, stateReader, testing); if (!result) break; dockContainerCount++; } } if (!testing) { // Delete remaining empty floating widgets int floatingWidgetIndex = dockContainerCount - 1; for (int i = floatingWidgetIndex; i < m_floatingWidgets.count(); ++i) { QPointer floatingWidget = m_floatingWidgets[i]; q->removeDockContainer(floatingWidget->dockContainer()); floatingWidget->deleteLater(); } } 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 : std::as_const(m_dockWidgetsMap)) { if (dockWidget->property(internal::g_dirtyProperty).toBool()) { // If the DockWidget is an auto hide widget that is not assigned yet, // then we need to delete the auto hide container now if (dockWidget->isAutoHide()) dockWidget->autoHideDockContainer()->cleanupAndDelete(); dockWidget->flagAsUnassigned(); emit dockWidget->viewToggled(false); } else { dockWidget->toggleViewInternal( !dockWidget->property(internal::g_closedProperty).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. for (auto dockContainer : std::as_const(m_containers)) { for (int i = 0; i < dockContainer->dockAreaCount(); ++i) { DockAreaWidget *dockArea = dockContainer->dockArea(i); const QString dockWidgetName = dockArea->property("currentDockWidget").toString(); DockWidget *dockWidget = nullptr; if (!dockWidgetName.isEmpty()) dockWidget = q->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() signal for all dock widgets if top // level changed. for (auto dockContainer : std::as_const(m_containers)) { DockWidget *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) { QByteArray currentState = state.startsWith("dumpLayout(); return true; } DockManager::DockManager(QWidget *parent) : DockContainerWidget(this, parent) , d(new DockManagerPrivate(this)) { connect(this, &DockManager::workspaceListChanged, this, [this] { d->m_workspaceOrderDirty = true; }); createRootSplitter(); createSideTabBarWidgets(); QMainWindow *mainWindow = qobject_cast(parent); if (mainWindow) mainWindow->setCentralWidget(this); d->m_dockAreaOverlay = new DockOverlay(this, DockOverlay::ModeDockAreaOverlay); d->m_containerOverlay = new DockOverlay(this, DockOverlay::ModeContainerOverlay); d->m_containers.append(this); if (DockManager::configFlags().testFlag(DockManager::FocusHighlighting)) d->m_focusController = new DockFocusController(this); window()->installEventFilter(this); #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) connect(qApp, &QApplication::focusWindowChanged, this, [](QWindow *focusWindow) { // Bring modal dialogs to foreground to ensure that they are in front of any // floating dock widget. if (focusWindow && focusWindow->isModal()) focusWindow->raise(); }); #endif } DockManager::~DockManager() { // Only save startup workspace and lock state if not in lite design mode if (!d->m_liteModeEnabled) { if (d->m_wasShown) { emit aboutToUnloadWorkspace(d->m_workspace.fileName()); save(); } saveStartupWorkspace(); saveLockWorkspace(); } // Fix memory leaks, see https://github.com/githubuser0xFFFF/Qt-Advanced-Docking-System/issues/307 std::vector areas; for (int i = 0; i != dockAreaCount(); ++i) areas.push_back(dockArea(i)); for (auto area : areas) { for (auto widget : area->dockWidgets()) delete widget; delete area; } // Using a temporary vector since the destructor of FloatingDockWidgetContainer // alters d->m_floatingWidgets. const auto copy = d->m_floatingWidgets; for (const auto &floatingWidget : copy) { if (floatingWidget) delete floatingWidget.get(); } delete d; } DockManager::ConfigFlags DockManager::configFlags() { return g_staticConfigFlags; } DockManager::AutoHideFlags DockManager::autoHideConfigFlags() { return g_staticAutoHideConfigFlags; } void DockManager::setConfigFlags(const ConfigFlags flags) { g_staticConfigFlags = flags; } void DockManager::setAutoHideConfigFlags(const AutoHideFlags flags) { g_staticAutoHideConfigFlags = flags; } void DockManager::setConfigFlag(eConfigFlag flag, bool on) { internal::setFlag(g_staticConfigFlags, flag, on); } void DockManager::setAutoHideConfigFlag(eAutoHideFlag flag, bool on) { internal::setFlag(g_staticAutoHideConfigFlags, flag, on); } bool DockManager::testConfigFlag(eConfigFlag flag) { return configFlags().testFlag(flag); } bool DockManager::testAutoHideConfigFlag(eAutoHideFlag flag) { return autoHideConfigFlags().testFlag(flag); } IconProvider &DockManager::iconProvider() { static IconProvider instance; return instance; } int DockManager::startDragDistance() { return static_cast(QApplication::startDragDistance() * 1.5); } void DockManager::setSettings(QtcSettings *settings) { d->m_settings = settings; } void DockManager::setWorkspacePresetsPath(const QString &path) { d->m_workspacePresetsPath = path; } void DockManager::initialize() { qCInfo(adsLog) << Q_FUNC_INFO; QTC_ASSERT(d->m_settings, return); // Can't continue if settings are not set yet syncWorkspacePresets(); prepareWorkspaces(); QString workspace = ADS::Constants::DEFAULT_WORKSPACE; if (d->m_liteModeEnabled && workspaceExists(ADS::Constants::LITE_WORKSPACE)) { workspace = ADS::Constants::LITE_WORKSPACE; } else { // Determine workspace to restore at startup if (autoRestoreWorkspace()) { const QString lastWorkspace = startupWorkspace(); if (!lastWorkspace.isEmpty()) { if (!workspaceExists(lastWorkspace)) { // This is a fallback mechanism for pre 4.1 settings which stored the workspace // name instead of the file name. const std::vector separators = {"-", "_"}; for (const QString &separator : separators) { QString workspaceVariant = lastWorkspace; workspaceVariant.replace(" ", separator); workspaceVariant.append("." + workspaceFileExtension); if (workspaceExists(workspaceVariant)) workspace = workspaceVariant; } } else { workspace = lastWorkspace; } } else qWarning() << "Could not restore workspace:" << lastWorkspace; } } openWorkspace(workspace); if (d->m_liteModeEnabled) lockWorkspace(true); else lockWorkspace(d->m_settings->value(Constants::LOCK_WORKSPACE_SETTINGS_KEY, false).toBool()); } DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, DockWidget *dockWidget, DockAreaWidget *dockAreaWidget, int index) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); auto container = dockAreaWidget ? dockAreaWidget->dockContainer() : this; auto dockArea = container->addDockWidget(area, dockWidget, dockAreaWidget, index); emit dockWidgetAdded(dockWidget); return dockArea; } DockAreaWidget *DockManager::addDockWidgetToContainer(DockWidgetArea area, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); auto dockArea = dockContainerWidget->addDockWidget(area, dockWidget); emit dockWidgetAdded(dockWidget); return dockArea; } AutoHideDockContainer *DockManager::addAutoHideDockWidget(SideBarLocation location, DockWidget *dockWidget) { return addAutoHideDockWidgetToContainer(location, dockWidget, this); } AutoHideDockContainer *DockManager::addAutoHideDockWidgetToContainer( SideBarLocation location, DockWidget *dockWidget, DockContainerWidget *dockContainerWidget) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); auto container = dockContainerWidget->createAndSetupAutoHideContainer(location, dockWidget); container->collapseView(true); emit dockWidgetAdded(dockWidget); return container; } DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget) { DockAreaWidget *areaWidget = lastAddedDockAreaWidget(area); if (areaWidget) return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, areaWidget); else if (!openedDockAreas().isEmpty()) return addDockWidget(area, dockWidget, openedDockAreas().constLast()); // TODO else return addDockWidget(area, dockWidget, nullptr); } DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockWidget, DockAreaWidget *dockAreaWidget, int index) { return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget, index); } FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget) { d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); if (oldDockArea) oldDockArea->removeDockWidget(dockWidget); dockWidget->setDockManager(this); FloatingDockContainer *floatingWidget = new FloatingDockContainer(dockWidget); floatingWidget->resize(dockWidget->size()); if (isVisible()) floatingWidget->show(); else d->m_uninitializedFloatingWidgets.append(floatingWidget); emit dockWidgetAdded(dockWidget); return floatingWidget; } DockWidget *DockManager::findDockWidget(const QString &objectName) const { return d->m_dockWidgetsMap.value(objectName, nullptr); } void DockManager::removeDockWidget(DockWidget *dockWidget) { emit dockWidgetAboutToBeRemoved(dockWidget); d->m_dockWidgetsMap.remove(dockWidget->objectName()); DockContainerWidget::removeDockWidget(dockWidget); dockWidget->setDockManager(nullptr); emit dockWidgetRemoved(dockWidget); } QMap DockManager::dockWidgetsMap() const { return d->m_dockWidgetsMap; } const QList DockManager::dockContainers() const { return d->m_containers; } const QList> DockManager::floatingWidgets() const { return d->m_floatingWidgets; } unsigned int DockManager::zOrderIndex() const { return 0; } QByteArray DockManager::saveState(const QString &displayName, int version) const { qCInfo(adsLog) << "Save state" << displayName; QByteArray xmlData; QXmlStreamWriter stream(&xmlData); auto configFlags = DockManager::configFlags(); stream.setAutoFormatting(configFlags.testFlag(XmlAutoFormattingEnabled)); stream.setAutoFormattingIndent(workspaceXmlFormattingIndent); stream.writeStartDocument(); stream.writeStartElement("QtAdvancedDockingSystem"); stream.writeAttribute("version", QString::number(CurrentVersion)); stream.writeAttribute("userVersion", QString::number(version)); stream.writeAttribute("containers", QString::number(d->m_containers.count())); stream.writeAttribute(workspaceDisplayNameAttribute.toString(), displayName); if (d->m_centralWidget) stream.writeAttribute("centralWidget", d->m_centralWidget->objectName()); for (auto container : std::as_const(d->m_containers)) container->saveState(stream); stream.writeEndElement(); stream.writeEndDocument(); return xmlData; } bool DockManager::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->m_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->m_restoringState = true; emit restoringState(); bool result = d->restoreState(state, version); d->m_restoringState = false; if (!isHidden) show(); emit stateRestored(); return result; } bool DockManager::isRestoringState() const { return d->m_restoringState; } bool DockManager::isLeavingMinimizedState() const { return d->m_isLeavingMinimized; } bool DockManager::eventFilter(QObject *obj, QEvent *event) { #if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) // Emulate Qt:Tool behavior. /* // Window always on top of the MainWindow. if (event->type() == QEvent::WindowActivate) { for (auto &floatingWidget : floatingWidgets()) { if (!floatingWidget->isVisible() || window()->isMinimized()) continue; // setWindowFlags(Qt::WindowStaysOnTopHint) will hide the window and thus requires // a show call. This then leads to flickering and a nasty endless loop (also buggy // behavior on Ubuntu). So we just do it ourself. floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, true); } } else if (event->type() == QEvent::WindowDeactivate) { for (auto &floatingWidget : floatingWidgets()) { if (!floatingWidget->isVisible() || window()->isMinimized()) continue; floatingWidget->setWindowFlag(Qt::WindowStaysOnTopHint, false); floatingWidget->raise(); } } */ // Sync minimize with MainWindow if (event->type() == QEvent::WindowStateChange) { for (auto &floatingWidget : floatingWidgets()) { if (!floatingWidget->isVisible()) continue; if (window()->isMinimized()) floatingWidget->showMinimized(); else floatingWidget->setWindowState(floatingWidget->windowState() & (~Qt::WindowMinimized)); } if (!window()->isMinimized()) QApplication::setActiveWindow(window()); } return Super::eventFilter(obj, event); #else if (event->type() == QEvent::WindowStateChange) { QWindowStateChangeEvent *ev = static_cast(event); if (ev->oldState().testFlag(Qt::WindowMinimized)) { d->m_isLeavingMinimized = true; QMetaObject::invokeMethod( this, [this] { endLeavingMinimizedState(); }, Qt::QueuedConnection); } } return Super::eventFilter(obj, event); #endif } DockWidget *DockManager::focusedDockWidget() const { if (!d->m_focusController) return nullptr; else return d->m_focusController->focusedDockWidget(); } QList DockManager::splitterSizes(DockAreaWidget *containedArea) const { if (containedArea) { auto splitter = internal::findParent(containedArea); if (splitter) return splitter->sizes(); } return {}; } void DockManager::setSplitterSizes(DockAreaWidget *containedArea, const QList &sizes) { if (!containedArea) return; auto splitter = internal::findParent(containedArea); if (splitter && splitter->count() == sizes.count()) splitter->setSizes(sizes); } void DockManager::setFloatingContainersTitle(const QString &title) { g_floatingContainersTitle = title; } QString DockManager::floatingContainersTitle() { if (g_floatingContainersTitle.isEmpty()) return qApp->applicationDisplayName(); return g_floatingContainersTitle; } void DockManager::setDockWidgetToolBarStyle(Qt::ToolButtonStyle style, DockWidget::eState state) { if (DockWidget::StateFloating == state) d->m_toolBarStyleFloating = style; else d->m_toolBarStyleDocked = style; } Qt::ToolButtonStyle DockManager::dockWidgetToolBarStyle(DockWidget::eState state) const { if (DockWidget::StateFloating == state) return d->m_toolBarStyleFloating; else return d->m_toolBarStyleDocked; } void DockManager::setDockWidgetToolBarIconSize(const QSize &iconSize, DockWidget::eState state) { if (DockWidget::StateFloating == state) d->m_toolBarIconSizeFloating = iconSize; else d->m_toolBarIconSizeDocked = iconSize; } QSize DockManager::dockWidgetToolBarIconSize(DockWidget::eState state) const { if (DockWidget::StateFloating == state) return d->m_toolBarIconSizeFloating; else return d->m_toolBarIconSizeDocked; } DockWidget *DockManager::centralWidget() const { return d->m_centralWidget; } DockAreaWidget *DockManager::setCentralWidget(DockWidget *widget) { if (!widget) { d->m_centralWidget = nullptr; return nullptr; } // Setting a new central widget is now allowed if there is already a central widget or // if there are already other dock widgets. if (d->m_centralWidget) { qWarning( "Setting a central widget not possible because there is already a central widget."); return nullptr; } // Setting a central widget is now allowed if there are already other dock widgets. if (!d->m_dockWidgetsMap.isEmpty()) { qWarning("Setting a central widget not possible - the central widget need to be the first " "dock widget that is added to the dock manager."); return nullptr; } widget->setFeature(DockWidget::DockWidgetClosable, false); widget->setFeature(DockWidget::DockWidgetMovable, false); widget->setFeature(DockWidget::DockWidgetFloatable, false); widget->setFeature(DockWidget::DockWidgetPinnable, false); d->m_centralWidget = widget; DockAreaWidget *centralArea = addDockWidget(CenterDockWidgetArea, widget); centralArea->setDockAreaFlag(DockAreaWidget::eDockAreaFlag::HideSingleWidgetTitleBar, true); return centralArea; } void DockManager::setDockWidgetFocused(DockWidget *dockWidget) { if (d->m_focusController) d->m_focusController->setDockWidgetFocused(dockWidget); } void DockManager::hideManagerAndFloatingWidgets() { hide(); d->m_hiddenFloatingWidgets.clear(); // Hide updates of floating widgets from user. for (auto &floatingWidget : d->m_floatingWidgets) { if (floatingWidget->isVisible()) { QList visibleWidgets; for (auto dockWidget : floatingWidget->dockWidgets()) { if (dockWidget->toggleViewAction()->isChecked()) visibleWidgets.push_back(dockWidget); } // Save as floating widget to be shown when DockManager will be shown back. d->m_hiddenFloatingWidgets.push_back(floatingWidget); floatingWidget->hide(); // Hidding floating widget automatically marked contained DockWidgets as hidden // but they must remain marked as visible as we want them to be restored visible // when DockManager will be shown back. for (auto dockWidget : visibleWidgets) dockWidget->toggleViewAction()->setChecked(true); } } } void DockManager::endLeavingMinimizedState() { d->m_isLeavingMinimized = false; this->activateWindow(); } void DockManager::registerFloatingWidget(FloatingDockContainer *floatingWidget) { d->m_floatingWidgets.append(floatingWidget); emit floatingWidgetCreated(floatingWidget); qCInfo(adsLog) << "d->FloatingWidgets.count() " << d->m_floatingWidgets.count(); } void DockManager::removeFloatingWidget(FloatingDockContainer *floatingWidget) { d->m_floatingWidgets.removeAll(floatingWidget); } void DockManager::registerDockContainer(DockContainerWidget *dockContainer) { d->m_containers.append(dockContainer); } void DockManager::removeDockContainer(DockContainerWidget *dockContainer) { if (this != dockContainer) d->m_containers.removeAll(dockContainer); } DockOverlay *DockManager::containerOverlay() const { return d->m_containerOverlay; } DockOverlay *DockManager::dockAreaOverlay() const { return d->m_dockAreaOverlay; } void DockManager::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) { if (d->m_focusController) d->m_focusController->notifyWidgetOrAreaRelocation(droppedWidget); } void DockManager::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) { if (d->m_focusController) d->m_focusController->notifyFloatingWidgetDrop(floatingWidget); } void DockManager::notifyDockWidgetRelocation(DockWidget *, DockContainerWidget *) {} void DockManager::notifyDockAreaRelocation(DockAreaWidget *, DockContainerWidget *) {} void DockManager::showEvent(QShowEvent *event) { Super::showEvent(event); // Fix issue #380 restoreHiddenFloatingWidgets(); if (d->m_uninitializedFloatingWidgets.empty()) return; for (auto floatingWidget : std::as_const(d->m_uninitializedFloatingWidgets)) { // Check, if someone closed a floating dock widget before the dock manager is shown. if (floatingWidget->dockContainer()->hasOpenDockAreas()) floatingWidget->show(); } d->m_uninitializedFloatingWidgets.clear(); } DockFocusController *DockManager::dockFocusController() const { return d->m_focusController; } void DockManager::restoreHiddenFloatingWidgets() { if (d->m_hiddenFloatingWidgets.isEmpty()) return; // Restore floating widgets that were hidden upon hideManagerAndFloatingWidgets for (auto &floatingWidget : d->m_hiddenFloatingWidgets) { bool hasDockWidgetVisible = false; // Needed to prevent FloatingDockContainer being shown empty // Could make sense to move this to FloatingDockContainer::showEvent(QShowEvent *event) // if experiencing FloatingDockContainer being shown empty in other situations, but let's keep // it here for now to make sure changes to fix Issue #380 does not impact existing behaviours for (auto dockWidget : floatingWidget->dockWidgets()) { if (dockWidget->toggleViewAction()->isChecked()) { dockWidget->toggleView(true); hasDockWidgetVisible = true; } } if (hasDockWidgetVisible) floatingWidget->show(); } d->m_hiddenFloatingWidgets.clear(); } Workspace *DockManager::activeWorkspace() const { return &d->m_workspace; } QString DockManager::startupWorkspace() const { QTC_ASSERT(d->m_settings, return {}); return d->m_settings->value(Constants::STARTUP_WORKSPACE_SETTINGS_KEY).toString(); } bool DockManager::autoRestoreWorkspace() const { QTC_ASSERT(d->m_settings, return false); return d->m_settings->value(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY).toBool(); } const QList &DockManager::workspaces() const { return d->m_workspaces; } int DockManager::workspaceIndex(const QString &fileName) const { return Utils::indexOf(d->m_workspaces, [&fileName](const Workspace &workspace) { return workspace == fileName; }); } bool DockManager::workspaceExists(const QString &fileName) const { return workspaceIndex(fileName) >= 0; } Workspace *DockManager::workspace(const QString &fileName) const { auto result = std::find_if(std::begin(d->m_workspaces), std::end(d->m_workspaces), [&fileName](const Workspace &workspace) { return workspace == fileName; }); if (result != std::end(d->m_workspaces)) return &*result; return nullptr; } Workspace *DockManager::workspace(int index) const { if (index < 0 || index >= d->m_workspaces.count()) return nullptr; return &d->m_workspaces[index]; } QDateTime DockManager::workspaceDateTime(const QString &fileName) const { Workspace *w = workspace(fileName); if (w) return w->lastModified(); return QDateTime(); } bool DockManager::moveWorkspace(int from, int to) { qCInfo(adsLog) << "Move workspaces" << from << ">" << to; int c = d->m_workspaces.count(); from = std::clamp(from, 0, c); to = std::clamp(to, 0, c); if (from == to) return false; d->m_workspaces.move(from, to); emit workspaceListChanged(); return true; } bool DockManager::moveWorkspaceUp(const QString &fileName) { qCInfo(adsLog) << "Move workspace up" << fileName; int i = workspaceIndex(fileName); // If workspace does not exist or is first item if (i <= 0) return false; d->m_workspaces.swapItemsAt(i, i - 1); emit workspaceListChanged(); return true; } bool DockManager::moveWorkspaceDown(const QString &fileName) { qCInfo(adsLog) << "Move workspace down" << fileName; int i = workspaceIndex(fileName); int c = d->m_workspaces.count(); // If workspace does not exist or is last item if (i < 0 || i >= (c - 1)) return false; d->m_workspaces.swapItemsAt(i, i + 1); emit workspaceListChanged(); return true; } FilePath DockManager::workspaceNameToFilePath(const QString &workspaceName) const { QTC_ASSERT(d->m_settings, return {}); return userDirectory().pathAppended(workspaceNameToFileName(workspaceName)); } QString DockManager::workspaceNameToFileName(const QString &workspaceName) const { QString copy = workspaceName; copy.replace(" ", "-"); copy.append("." + workspaceFileExtension); return copy; } void DockManager::uniqueWorkspaceFileName(QString &fileName) const { auto filePath = FilePath::fromString(fileName); if (workspaceExists(fileName)) { int i = 1; QString copy; do { copy = filePath.baseName() + QString::number(i) + "." + filePath.completeSuffix(); ++i; } while (workspaceExists(copy)); fileName = copy; } } void DockManager::showWorkspaceMananger() { save(); // Save current workspace WorkspaceDialog workspaceDialog(this, parentWidget()); workspaceDialog.setAutoLoadWorkspace(autoRestoreWorkspace()); workspaceDialog.exec(); if (d->m_workspaceOrderDirty) { writeOrder(); d->m_workspaceOrderDirty = false; } QTC_ASSERT(d->m_settings, return); d->m_settings->setValue(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY, workspaceDialog.autoLoadWorkspace()); } void DockManager::lockWorkspace(bool value) { if (value == d->m_workspaceLocked) return; d->m_workspaceLocked = value; DockWidget::DockWidgetFeatures features = DockWidget::DefaultDockWidgetFeatures; if (value) { internal::setFlag(features, DockWidget::DockWidgetMovable, false); internal::setFlag(features, DockWidget::DockWidgetFloatable, false); } const auto &dockWidgets = dockWidgetsMap(); for (auto dockWidget : dockWidgets) dockWidget->setFeatures(features); emit lockWorkspaceChanged(); } bool DockManager::isWorkspaceLocked() const { return d->m_workspaceLocked; } Result DockManager::createWorkspace(const QString &workspaceName) { qCInfo(adsLog) << "Create workspace" << workspaceName; QString fileName = workspaceNameToFileName(workspaceName); uniqueWorkspaceFileName(fileName); const FilePath filePath = userDirectory().pathAppended(fileName); Result<> result = write(filePath, saveState(workspaceName)); // TODO utils if (!result) return ResultError(result.error()); Workspace workspace(filePath, false); d->m_workspaces.append(workspace); emit workspaceListChanged(); return workspace.fileName(); } Result<> DockManager::openWorkspace(const QString &fileName) { qCInfo(adsLog) << "Open workspace" << fileName; Workspace *wrk = workspace(fileName); if (!wrk) return ResultError(Tr::tr("Workspace \"%1\" does not exist.").arg(fileName)); // Do nothing if workspace is already loaded, exception if it is a preset workspace. In this // case we still want to be able to load the default workspace to undo potential user changes. if (*wrk == *activeWorkspace() && !wrk->isPreset()) return {}; // true if (activeWorkspace()->isValid()) { // Allow everyone to set something in the workspace and before saving emit aboutToUnloadWorkspace(activeWorkspace()->fileName()); Result<> saveResult = save(); if (!saveResult) return saveResult; } // Try loading the file const Result data = loadWorkspace(*wrk); if (!data) return ResultError(data.error()); emit openingWorkspace(wrk->fileName()); // If data was loaded from file try to restore its state if (!data->isNull() && !restoreState(*data)) return ResultError(Tr::tr("Cannot restore \"%1\".").arg(wrk->filePath().toUserOutput())); d->m_workspace = *wrk; emit workspaceLoaded(wrk->fileName()); return {}; } Result<> DockManager::reloadActiveWorkspace() { qCInfo(adsLog) << "Reload active workspace"; Workspace *wrk = activeWorkspace(); if (!workspaces().contains(*wrk)) return ResultError( Tr::tr("Cannot reload \"%1\". It is not in the list of workspaces.") .arg(wrk->filePath().toUserOutput())); const Result data = loadWorkspace(*wrk); if (!data) return ResultError(data.error()); if (!data->isNull() && !restoreState(*data)) return ResultError(Tr::tr("Cannot restore \"%1\".").arg(wrk->filePath().toUserOutput())); emit workspaceReloaded(wrk->fileName()); return {}; } bool DockManager::deleteWorkspace(const QString &fileName) { qCInfo(adsLog) << "Delete workspace" << fileName; Workspace *w = workspace(fileName); if (!w) { qWarning() << "Workspace" << fileName << "does not exist"; return false; } // Remove corresponding workspace file if (w->exists()) { const FilePath filePath = w->filePath(); if (filePath.removeFile()) { d->m_workspaces.removeOne(*w); emit workspaceRemoved(); emit workspaceListChanged(); return true; } } return false; } void DockManager::deleteWorkspaces(const QStringList &fileNames) { for (const QString &fileName : fileNames) deleteWorkspace(fileName); } Result DockManager::cloneWorkspace(const QString &originalFileName, const QString &cloneName) { qCInfo(adsLog) << "Clone workspace" << originalFileName << cloneName; Workspace *w = workspace(originalFileName); if (!w) return ResultError(Tr::tr("Workspace \"%1\" does not exist.").arg(originalFileName)); const FilePath originalPath = w->filePath(); if (!originalPath.exists()) return ResultError( Tr::tr("Workspace \"%1\" does not exist.").arg(originalPath.toUserOutput())); const FilePath clonePath = workspaceNameToFilePath(cloneName); const Result<> copyResult = originalPath.copyFile(clonePath); if (!copyResult) return ResultError(Tr::tr("Could not clone \"%1\" due to: %2") .arg(originalPath.toUserOutput(), copyResult.error())); writeDisplayName(clonePath, cloneName); d->m_workspaces.append(Workspace(clonePath)); emit workspaceListChanged(); return clonePath.fileName(); } Result DockManager::renameWorkspace(const QString &originalFileName, const QString &newName) { qCInfo(adsLog) << "Rename workspace" << originalFileName << newName; Workspace *w = workspace(originalFileName); if (!w) return ResultError(Tr::tr("Workspace \"%1\" does not exist.").arg(originalFileName)); w->setName(newName); emit workspaceListChanged(); return originalFileName; } Result<> DockManager::resetWorkspacePreset(const QString &fileName) { qCInfo(adsLog) << "Reset workspace" << fileName; Workspace *w = workspace(fileName); if (!w) return ResultError(Tr::tr("Workspace \"%1\" does not exist.").arg(fileName)); if (!w->isPreset()) return ResultError(Tr::tr("Workspace \"%1\" is not a preset.").arg(fileName)); const FilePath filePath = w->filePath(); if (!filePath.removeFile()) return ResultError(Tr::tr("Cannot remove \"%1\".").arg(filePath.toUserOutput())); return presetDirectory().pathAppended(fileName).copyFile(filePath); } Result<> DockManager::save() { if (isModeChangeState()) return ResultError(Tr::tr("Cannot save workspace while in mode change state.")); emit aboutToSaveWorkspace(); Workspace *w = activeWorkspace(); return write(w->filePath(), saveState(w->name())); } void DockManager::setModeChangeState(bool value) { d->m_modeChangeState = value; } bool DockManager::isModeChangeState() const { return d->m_modeChangeState; } void DockManager::aboutToShow() { d->m_wasShown = true; } Result DockManager::importWorkspace(const QString &filePath) { qCInfo(adsLog) << "Import workspace" << filePath; const FilePath sourceFilePath = FilePath::fromUserInput(filePath); if (!sourceFilePath.exists()) return ResultError( Tr::tr("File \"%1\" does not exist.").arg(sourceFilePath.toUserOutput())); // Extract workspace file name. Check if the workspace is already contained in the list of // workspaces. If that is the case add a counter to the workspace file name. QString fileName = sourceFilePath.fileName(); uniqueWorkspaceFileName(fileName); const FilePath targetFilePath = userDirectory().pathAppended(fileName); const Result<> copyResult = sourceFilePath.copyFile(targetFilePath); if (!copyResult) return ResultError( Tr::tr("Could not copy \"%1\" to \"%2\" due to: %3") .arg(filePath, targetFilePath.toUserOutput(), copyResult.error())); d->m_workspaces.append(Workspace(targetFilePath)); emit workspaceListChanged(); return targetFilePath.fileName(); } Result DockManager::exportWorkspace(const QString &targetFilePath, const QString &sourceFileName) { qCInfo(adsLog) << "Export workspace" << targetFilePath << sourceFileName; // If we came this far the user decided that in case the target already exists to overwrite it. // We first need to remove the existing file, otherwise QFile::copy() will fail. const FilePath targetFile = FilePath::fromUserInput(targetFilePath); // Remove the file which supposed to be overwritten if (targetFile.exists()) { if (!targetFile.removeFile()) { return ResultError( Tr::tr("Could not remove \"%1\".").arg(targetFile.toUserOutput())); } } // Check if the target directory exists if (!targetFile.parentDir().exists()) return ResultError( Tr::tr("The directory \"%1\" does not exist.").arg(targetFile.parentDir().toUserOutput())); // Check if the workspace exists const FilePath workspaceFile = userDirectory().pathAppended(sourceFileName); if (!workspaceFile.exists()) return ResultError( Tr::tr("The workspace \"%1\" does not exist ").arg(workspaceFile.toUserOutput())); // Finally copy the workspace to the target const Result<> copyResult = workspaceFile.copyFile(targetFile); if (!copyResult) return ResultError( Tr::tr("Could not copy \"%1\" to \"%2\" due to: %3") .arg(sourceFileName, workspaceFile.toUserOutput(), copyResult.error())); return workspaceFile.fileName(); } QStringList DockManager::loadOrder(const FilePath &dir) const { qCInfo(adsLog) << "Load workspace order" << dir.toUserOutput(); if (dir.isEmpty()) return {}; auto filePath = dir.pathAppended(workspaceOrderFileName.toString()); QByteArray data = loadFile(filePath); if (data.isEmpty()) { qWarning() << "Order data is empty" << filePath.toUserOutput(); return {}; } QJsonParseError parseError; QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &parseError); if (parseError.error != QJsonParseError::NoError) { qWarning() << "Failed to parse JSON file" << filePath << ":" << parseError.errorString(); return {}; } return jsonDoc.toVariant().toStringList(); } bool DockManager::writeOrder() const { qCInfo(adsLog) << "Write workspace order"; QJsonArray order; for (const Workspace &workspace : d->m_workspaces) order.append(QJsonValue(workspace.fileName())); const FilePath orderFilePath = userDirectory().pathAppended(workspaceOrderFileName.toString()); if (!orderFilePath.writeFileContents(QJsonDocument{order}.toJson())) { qWarning() << "Faild to write order" << orderFilePath.toUserOutput(); return false; } return true; } QList DockManager::loadWorkspaces(const FilePath &dir) const { if (dir.isEmpty()) return {}; QList workspaces; dir.iterateDirectory( [&workspaces](const FilePath &filePath, const FilePathInfo &) { Workspace workspace(filePath); //workspace.setName(displayName(filePath)); TODO workspaces.append(workspace); return IterationPolicy::Continue; }, FileFilter(QStringList{"*." + workspaceFileExtension}, QDir::Files)); return workspaces; } FilePath DockManager::presetDirectory() const { return FilePath::fromString(d->m_workspacePresetsPath); } FilePath DockManager::userDirectory() const { QTC_ASSERT(d->m_settings, return {}); return FilePath::fromString(QFileInfo(d->m_settings->fileName()).path()) .pathAppended(workspaceFolderName.toString()); } QByteArray DockManager::loadFile(const FilePath &filePath) { qCInfo(adsLog) << "Load" << filePath; if (!filePath.exists()) { qWarning() << "File does not exist" << filePath.toUserOutput(); return {}; } const Result data = filePath.fileContents(); if (!data) { qWarning() << "Could not open" << filePath.toUserOutput() << data.error(); return {}; } return data.value(); } QString DockManager::readDisplayName(const FilePath &filePath) { return readAttribute(filePath, workspaceDisplayNameAttribute); } bool DockManager::writeDisplayName(const FilePath &filePath, const QString &displayName) { return writeAttribute(filePath, workspaceDisplayNameAttribute, displayName); } QString DockManager::readMcusEnabled(const FilePath &filePath) { return readAttribute(filePath, workspaceMcusEnabledAttribute); } bool DockManager::writeMcusEnabled(const FilePath &filePath, const QString &mcusEnabled) { return writeAttribute(filePath, workspaceMcusEnabledAttribute, mcusEnabled); } QString DockManager::readAttribute(const FilePath &filePath, QStringView key) { auto data = loadFile(filePath); if (data.isEmpty()) return {}; auto tmp = data.startsWith(" content = filePath.fileContents(); QTC_ASSERT_RESULT(content, return false); QDomDocument doc; QString error_msg; int error_line, error_col; if (!doc.setContent(*content, &error_msg, &error_line, &error_col)) { qWarning() << QString("XML error on line %1, col %2: %3") .arg(error_line).arg(error_col).arg(error_msg); return false; } QDomElement docElem = doc.documentElement(); docElem.setAttribute(key.toString(), value); const Result<> result = write(filePath, doc.toByteArray(workspaceXmlFormattingIndent)); if (result) return true; qWarning() << "Could not write" << key << value << "to" << filePath << ":" << result.error(); return false; } Result<> DockManager::write(const FilePath &filePath, const QByteArray &data) { qCInfo(adsLog) << "Write" << filePath; if (!filePath.parentDir().ensureWritableDir()) return ResultError(Tr::tr("Cannot write to \"%1\".").arg(filePath.toUserOutput())); FileSaver fileSaver(filePath, QIODevice::Text); if (!fileSaver.hasError()) fileSaver.write(data); if (const Result<> res = fileSaver.finalize(); !res) return ResultError(Tr::tr("Cannot write to \"%1\" due to: %2") .arg(filePath.toUserOutput(), res.error())); return ResultOk; } Result DockManager::loadWorkspace(const Workspace &workspace) const { qCInfo(adsLog) << "Load workspace" << workspace.fileName(); if (!workspace.exists()) return ResultError( Tr::tr("Workspace \"%1\" does not exist.").arg(workspace.filePath().toUserOutput())); return workspace.filePath().fileContents(); } void DockManager::syncWorkspacePresets() { qCInfo(adsLog) << "Sync workspaces"; auto fileFilter = FileFilter(QStringList{"*." + workspaceFileExtension, "*.json"}, QDir::Files); // All files in the workspace preset directory with the file extension wrk or json auto presetFiles = presetDirectory().dirEntries(fileFilter); auto userFiles = userDirectory().dirEntries(fileFilter); // Try do create the 'workspaces' directory if it doesn't exist already if (!userDirectory().ensureWritableDir()) { qWarning() << QString("Could not make directory '%1')").arg(userDirectory().toUserOutput()); return; } for (const auto &filePath : presetFiles) { const int index = Utils::indexOf(userFiles, [&filePath](const FilePath &other) { return other.fileName() == filePath.fileName(); }); // File already contained in user directory if (index >= 0) { auto userFile = userFiles[index]; // If *.wrk file and displayName attribute is empty set the displayName. This // should fix old workspace files which don't have the displayName attribute. if (userFile.suffix() == workspaceFileExtension) { const QString name = readDisplayName(userFile); if (name.isEmpty()) writeDisplayName(userFile, name); const QString presetMcusEnabled = readMcusEnabled(filePath); const QString mcusEnabled = readMcusEnabled(userFile); if (mcusEnabled.isEmpty() || mcusEnabled != presetMcusEnabled) writeMcusEnabled(userFile, presetMcusEnabled); } continue; } const Result<> copyResult = filePath.copyFile( userDirectory().pathAppended(filePath.fileName())); if (!copyResult) qWarning() << QString("Could not copy '%1' to '%2' due to %3") .arg(filePath.toUserOutput(), userDirectory().toUserOutput(), copyResult.error()); } } void DockManager::prepareWorkspaces() { qCInfo(adsLog) << "Prepare workspace"; auto presetWorkspaces = loadWorkspaces(presetDirectory()); auto userWorkspaces = loadWorkspaces(userDirectory()); auto userOrder = loadOrder(userDirectory()); std::vector> tmp; for (Workspace &userWorkspace : userWorkspaces) { userWorkspace.setPreset(presetWorkspaces.contains(userWorkspace)); qsizetype index = userOrder.indexOf(userWorkspace.fileName()); if (index == -1) index = userWorkspaces.count(); tmp.push_back(std::make_pair(index, userWorkspace)); } Utils::sort(tmp, [](auto const &a, auto const &b) { return a.first < b.first; }); d->m_workspaces = Utils::transform>(tmp, [](const std::pair &p) { return p.second; }); emit workspaceListChanged(); } void DockManager::saveStartupWorkspace() { QTC_ASSERT(d->m_settings, return); d->m_settings->setValue(Constants::STARTUP_WORKSPACE_SETTINGS_KEY, activeWorkspace()->fileName()); } void DockManager::saveLockWorkspace() { QTC_ASSERT(d->m_settings, return); d->m_settings->setValue(Constants::LOCK_WORKSPACE_SETTINGS_KEY, d->m_workspaceLocked); } void DockManager::setMcusProject(bool value) { d->m_mcusProject = value; } bool DockManager::mcusProject() const { return d->m_mcusProject; } void DockManager::setLiteMode(bool value) { d->m_liteModeEnabled = value; } bool DockManager::isLiteModeEnabled() const { return d->m_liteModeEnabled; } } // namespace ADS