1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2020 Uwe Kindler 4 ** Contact: https://www.qt.io/licensing/ 5 ** 6 ** This file is part of Qt Creator. 7 ** 8 ** Commercial License Usage 9 ** Licensees holding valid commercial Qt licenses may use this file in 10 ** accordance with the commercial license agreement provided with the 11 ** Software or, alternatively, in accordance with the terms contained in 12 ** a written agreement between you and The Qt Company. For licensing terms 13 ** and conditions see https://www.qt.io/terms-conditions. For further 14 ** information use the contact form at https://www.qt.io/contact-us. 15 ** 16 ** GNU Lesser General Public License Usage 17 ** Alternatively, this file may be used under the terms of the GNU Lesser 18 ** General Public License version 2.1 or (at your option) any later version. 19 ** The licenses are as published by the Free Software Foundation 20 ** and appearing in the file LICENSE.LGPLv21 included in the packaging 21 ** of this file. Please review the following information to ensure 22 ** the GNU Lesser General Public License version 2.1 requirements 23 ** will be met: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 24 ** 25 ** GNU General Public License Usage 26 ** Alternatively, this file may be used under the terms of the GNU 27 ** General Public License version 3 or (at your option) any later version 28 ** approved by the KDE Free Qt Foundation. The licenses are as published by 29 ** the Free Software Foundation and appearing in the file LICENSE.GPL3 30 ** included in the packaging of this file. Please review the following 31 ** information to ensure the GNU General Public License requirements will 32 ** be met: https://www.gnu.org/licenses/gpl-3.0.html. 33 ** 34 ****************************************************************************/ 35 36 #include "dockmanager.h" 37 38 #include "ads_globals.h" 39 #include "dockareatitlebar.h" 40 #include "dockareawidget.h" 41 #include "dockfocuscontroller.h" 42 #include "dockingstatereader.h" 43 #include "dockoverlay.h" 44 #include "dockwidget.h" 45 #include "dockwidgettab.h" 46 #include "floatingdockcontainer.h" 47 #include "iconprovider.h" 48 49 #include "workspacedialog.h" 50 51 #include <utils/algorithm.h> 52 #include <utils/qtcassert.h> 53 54 #include <algorithm> 55 #include <iostream> 56 57 #include <QAction> 58 #include <QApplication> 59 #include <QDateTime> 60 #include <QDir> 61 #include <QFile> 62 #include <QFileInfo> 63 #include <QList> 64 #include <QLoggingCategory> 65 #include <QMainWindow> 66 #include <QMap> 67 #include <QMenu> 68 #include <QMessageBox> 69 #include <QSettings> 70 #include <QVariant> 71 #include <QXmlStreamWriter> 72 73 static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg) 74 75 namespace ADS 76 { 77 /** 78 * Internal file version in case the structure changes internally 79 */ 80 enum eStateFileVersion { 81 InitialVersion = 0, //!< InitialVersion 82 Version1 = 1, //!< Version1 83 CurrentVersion = Version1 //!< CurrentVersion 84 }; 85 86 static DockManager::ConfigFlags g_staticConfigFlags = DockManager::DefaultNonOpaqueConfig; 87 88 /** 89 * Private data class of DockManager class (pimpl) 90 */ 91 class DockManagerPrivate 92 { 93 public: 94 DockManager *q; 95 QList<QPointer<FloatingDockContainer>> m_floatingWidgets; 96 QList<DockContainerWidget *> m_containers; 97 DockOverlay *m_containerOverlay = nullptr; 98 DockOverlay *m_dockAreaOverlay = nullptr; 99 QMap<QString, DockWidget *> m_dockWidgetsMap; 100 bool m_restoringState = false; 101 QVector<FloatingDockContainer *> m_uninitializedFloatingWidgets; 102 DockFocusController *m_focusController = nullptr; 103 104 QString m_workspaceName; 105 bool m_workspaceListDirty = true; 106 QStringList m_workspaces; 107 QSet<QString> m_workspacePresets; 108 QHash<QString, QDateTime> m_workspaceDateTimes; 109 QString m_workspaceToRestoreAtStartup; 110 bool m_autorestoreLastWorkspace; // This option is set in the Workspace Manager! 111 QSettings *m_settings = nullptr; 112 QString m_workspacePresetsPath; 113 bool m_modeChangeState = false; 114 115 /** 116 * Private data constructor 117 */ 118 DockManagerPrivate(DockManager *parent); 119 120 /** 121 * Restores the state. If testing is set to true it will check if 122 * the given data stream is a valid docking system state file. 123 */ 124 bool restoreStateFromXml(const QByteArray &state, 125 int version, 126 bool testing = false); 127 128 /** 129 * Restore state 130 */ 131 bool restoreState(const QByteArray &state, int version); 132 133 void restoreDockWidgetsOpenState(); 134 void restoreDockAreasIndices(); 135 void emitTopLevelEvents(); 136 hideFloatingWidgets()137 void hideFloatingWidgets() 138 { 139 // Hide updates of floating widgets from user 140 for (auto floatingWidget : qAsConst(m_floatingWidgets)) 141 floatingWidget->hide(); 142 } 143 markDockWidgetsDirty()144 void markDockWidgetsDirty() 145 { 146 for (auto dockWidget : qAsConst(m_dockWidgetsMap)) 147 dockWidget->setProperty("dirty", true); 148 } 149 150 /** 151 * Restores the container with the given index 152 */ 153 bool restoreContainer(int index, DockingStateReader &stream, bool testing); 154 155 void workspaceLoadingProgress(); 156 }; // class DockManagerPrivate 157 DockManagerPrivate(DockManager * parent)158 DockManagerPrivate::DockManagerPrivate(DockManager *parent) 159 : q(parent) 160 {} 161 restoreContainer(int index,DockingStateReader & stream,bool testing)162 bool DockManagerPrivate::restoreContainer(int index, DockingStateReader &stream, bool testing) 163 { 164 if (testing) 165 index = 0; 166 167 bool result = false; 168 if (index >= m_containers.count()) { 169 FloatingDockContainer *floatingWidget = new FloatingDockContainer(q); 170 result = floatingWidget->restoreState(stream, testing); 171 } else { 172 qCInfo(adsLog) << "d->m_containers[i]->restoreState "; 173 auto container = m_containers[index]; 174 if (container->isFloating()) 175 result = container->floatingWidget()->restoreState(stream, testing); 176 else 177 result = container->restoreState(stream, testing); 178 } 179 180 return result; 181 } 182 restoreStateFromXml(const QByteArray & state,int version,bool testing)183 bool DockManagerPrivate::restoreStateFromXml(const QByteArray &state, int version, bool testing) 184 { 185 Q_UNUSED(version) // TODO version is not needed, why is it in here in the first place? 186 187 if (state.isEmpty()) 188 return false; 189 190 DockingStateReader stateReader(state); 191 if (!stateReader.readNextStartElement()) 192 return false; 193 194 if (stateReader.name() != QLatin1String("QtAdvancedDockingSystem")) 195 return false; 196 197 qCInfo(adsLog) << stateReader.attributes().value("version"); 198 bool ok; 199 int v = stateReader.attributes().value("version").toInt(&ok); 200 if (!ok || v > CurrentVersion) 201 return false; 202 203 stateReader.setFileVersion(v); 204 205 qCInfo(adsLog) << stateReader.attributes().value("userVersion"); 206 // Older files do not support UserVersion but we still want to load them so 207 // we first test if the attribute exists 208 if (!stateReader.attributes().value("userVersion").isEmpty()) 209 { 210 v = stateReader.attributes().value("userVersion").toInt(&ok); 211 if (!ok || v != version) 212 return false; 213 } 214 215 bool result = true; 216 #ifdef ADS_DEBUG_PRINT 217 int dockContainers = stateReader.attributes().value("containers").toInt(); 218 qCInfo(adsLog) << dockContainers; 219 #endif 220 int dockContainerCount = 0; 221 while (stateReader.readNextStartElement()) { 222 if (stateReader.name() == QLatin1String("container")) { 223 result = restoreContainer(dockContainerCount, stateReader, testing); 224 if (!result) 225 break; 226 227 dockContainerCount++; 228 } 229 } 230 231 if (!testing) { 232 // Delete remaining empty floating widgets 233 int floatingWidgetIndex = dockContainerCount - 1; 234 int deleteCount = m_floatingWidgets.count() - floatingWidgetIndex; 235 for (int i = 0; i < deleteCount; ++i) { 236 m_floatingWidgets[floatingWidgetIndex + i]->deleteLater(); 237 q->removeDockContainer(m_floatingWidgets[floatingWidgetIndex + i]->dockContainer()); 238 } 239 } 240 241 return result; 242 } 243 restoreDockWidgetsOpenState()244 void DockManagerPrivate::restoreDockWidgetsOpenState() 245 { 246 // All dock widgets, that have not been processed in the restore state 247 // function are invisible to the user now and have no assigned dock area 248 // They do not belong to any dock container, until the user toggles the 249 // toggle view action the next time 250 for (auto dockWidget : qAsConst(m_dockWidgetsMap)) { 251 if (dockWidget->property(internal::dirtyProperty).toBool()) { 252 dockWidget->flagAsUnassigned(); 253 emit dockWidget->viewToggled(false); 254 } else { 255 dockWidget->toggleViewInternal( 256 !dockWidget->property(internal::closedProperty).toBool()); 257 } 258 } 259 } 260 restoreDockAreasIndices()261 void DockManagerPrivate::restoreDockAreasIndices() 262 { 263 // Now all dock areas are properly restored and we setup the index of 264 // The dock areas because the previous toggleView() action has changed 265 // the dock area index 266 int count = 0; 267 for (auto dockContainer : qAsConst(m_containers)) { 268 count++; 269 for (int i = 0; i < dockContainer->dockAreaCount(); ++i) { 270 DockAreaWidget *dockArea = dockContainer->dockArea(i); 271 QString dockWidgetName = dockArea->property("currentDockWidget").toString(); 272 DockWidget *dockWidget = nullptr; 273 if (!dockWidgetName.isEmpty()) 274 dockWidget = q->findDockWidget(dockWidgetName); 275 276 if (!dockWidget || dockWidget->isClosed()) { 277 int index = dockArea->indexOfFirstOpenDockWidget(); 278 if (index < 0) 279 continue; 280 281 dockArea->setCurrentIndex(index); 282 } else { 283 dockArea->internalSetCurrentDockWidget(dockWidget); 284 } 285 } 286 } 287 } 288 emitTopLevelEvents()289 void DockManagerPrivate::emitTopLevelEvents() 290 { 291 // Finally we need to send the topLevelChanged() signals for all dock 292 // widgets if top level changed 293 for (auto dockContainer : qAsConst(m_containers)) { 294 DockWidget *topLevelDockWidget = dockContainer->topLevelDockWidget(); 295 if (topLevelDockWidget) { 296 topLevelDockWidget->emitTopLevelChanged(true); 297 } else { 298 for (int i = 0; i < dockContainer->dockAreaCount(); ++i) { 299 auto dockArea = dockContainer->dockArea(i); 300 for (auto dockWidget : dockArea->dockWidgets()) 301 dockWidget->emitTopLevelChanged(false); 302 } 303 } 304 } 305 } 306 restoreState(const QByteArray & state,int version)307 bool DockManagerPrivate::restoreState(const QByteArray &state, int version) 308 { 309 QByteArray currentState = state.startsWith("<?xml") ? state : qUncompress(state); 310 // Check the format of the given data stream 311 if (!restoreStateFromXml(currentState, version, true)) { 312 qCInfo(adsLog) << "checkFormat: Error checking format!!!"; 313 return false; 314 } 315 316 // Hide updates of floating widgets from use 317 hideFloatingWidgets(); 318 markDockWidgetsDirty(); 319 320 if (!restoreStateFromXml(currentState, version)) { 321 qCInfo(adsLog) << "restoreState: Error restoring state!!!"; 322 return false; 323 } 324 325 restoreDockWidgetsOpenState(); 326 restoreDockAreasIndices(); 327 emitTopLevelEvents(); 328 329 return true; 330 } 331 DockManager(QWidget * parent)332 DockManager::DockManager(QWidget *parent) 333 : DockContainerWidget(this, parent) 334 , d(new DockManagerPrivate(this)) 335 { 336 connect(this, &DockManager::workspaceListChanged, this, [=] { 337 d->m_workspaceListDirty = true; 338 }); 339 340 createRootSplitter(); 341 QMainWindow *mainWindow = qobject_cast<QMainWindow *>(parent); 342 if (mainWindow) { 343 mainWindow->setCentralWidget(this); 344 } 345 346 d->m_dockAreaOverlay = new DockOverlay(this, DockOverlay::ModeDockAreaOverlay); 347 d->m_containerOverlay = new DockOverlay(this, DockOverlay::ModeContainerOverlay); 348 d->m_containers.append(this); 349 350 if (DockManager::configFlags().testFlag(DockManager::FocusHighlighting)) 351 d->m_focusController = new DockFocusController(this); 352 } 353 ~DockManager()354 DockManager::~DockManager() 355 { 356 emit aboutToUnloadWorkspace(d->m_workspaceName); 357 save(); 358 saveStartupWorkspace(); 359 360 // Using a temporal vector since the destructor of 361 // FloatingDockWidgetContainer alters d->m_floatingWidgets. 362 std::vector<FloatingDockContainer *> aboutToDeletes; 363 for (auto floatingWidget : qAsConst(d->m_floatingWidgets)) { 364 if (floatingWidget) 365 aboutToDeletes.push_back(floatingWidget); 366 } 367 368 for (auto del : aboutToDeletes) { 369 delete del; 370 } 371 372 d->m_floatingWidgets.clear(); 373 374 delete d; 375 } 376 configFlags()377 DockManager::ConfigFlags DockManager::configFlags() { return g_staticConfigFlags; } 378 setConfigFlags(const ConfigFlags flags)379 void DockManager::setConfigFlags(const ConfigFlags flags) { g_staticConfigFlags = flags; } 380 setConfigFlag(eConfigFlag flag,bool on)381 void DockManager::setConfigFlag(eConfigFlag flag, bool on) 382 { 383 internal::setFlag(g_staticConfigFlags, flag, on); 384 } 385 testConfigFlag(eConfigFlag flag)386 bool DockManager::testConfigFlag(eConfigFlag flag) 387 { 388 return configFlags().testFlag(flag); 389 } 390 iconProvider()391 IconProvider &DockManager::iconProvider() 392 { 393 static IconProvider instance; 394 return instance; 395 } 396 startDragDistance()397 int DockManager::startDragDistance() 398 { 399 return static_cast<int>(QApplication::startDragDistance() * 1.5); 400 } 401 setSettings(QSettings * settings)402 void DockManager::setSettings(QSettings *settings) { d->m_settings = settings; } 403 setWorkspacePresetsPath(const QString & path)404 void DockManager::setWorkspacePresetsPath(const QString &path) { d->m_workspacePresetsPath = path; } 405 addDockWidget(DockWidgetArea area,DockWidget * dockWidget,DockAreaWidget * dockAreaWidget)406 DockAreaWidget *DockManager::addDockWidget(DockWidgetArea area, 407 DockWidget *dockWidget, 408 DockAreaWidget *dockAreaWidget) 409 { 410 d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); 411 return DockContainerWidget::addDockWidget(area, dockWidget, dockAreaWidget); 412 } 413 initialize()414 void DockManager::initialize() 415 { 416 syncWorkspacePresets(); 417 418 QString workspace = ADS::Constants::DEFAULT_WORKSPACE; 419 420 // Determine workspace to restore at startup 421 if (autoRestorLastWorkspace()) { 422 QString lastWS = lastWorkspace(); 423 if (!lastWS.isEmpty() && workspaces().contains(lastWS)) 424 workspace = lastWS; 425 else 426 qDebug() << "Couldn't restore last workspace!"; 427 } 428 429 openWorkspace(workspace); 430 } 431 addDockWidgetTab(DockWidgetArea area,DockWidget * dockWidget)432 DockAreaWidget *DockManager::addDockWidgetTab(DockWidgetArea area, DockWidget *dockWidget) 433 { 434 DockAreaWidget *areaWidget = lastAddedDockAreaWidget(area); 435 if (areaWidget) 436 return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, areaWidget); 437 else if (!openedDockAreas().isEmpty()) 438 return addDockWidget(area, dockWidget, openedDockAreas().constLast()); 439 else 440 return addDockWidget(area, dockWidget, nullptr); 441 } 442 addDockWidgetTabToArea(DockWidget * dockWidget,DockAreaWidget * dockAreaWidget)443 DockAreaWidget *DockManager::addDockWidgetTabToArea(DockWidget *dockWidget, 444 DockAreaWidget *dockAreaWidget) 445 { 446 return addDockWidget(ADS::CenterDockWidgetArea, dockWidget, dockAreaWidget); 447 } 448 addDockWidgetFloating(DockWidget * dockWidget)449 FloatingDockContainer *DockManager::addDockWidgetFloating(DockWidget *dockWidget) 450 { 451 d->m_dockWidgetsMap.insert(dockWidget->objectName(), dockWidget); 452 DockAreaWidget *oldDockArea = dockWidget->dockAreaWidget(); 453 if (oldDockArea) 454 oldDockArea->removeDockWidget(dockWidget); 455 456 dockWidget->setDockManager(this); 457 FloatingDockContainer *floatingWidget = new FloatingDockContainer(dockWidget); 458 floatingWidget->resize(dockWidget->size()); 459 if (isVisible()) 460 floatingWidget->show(); 461 else 462 d->m_uninitializedFloatingWidgets.append(floatingWidget); 463 464 return floatingWidget; 465 } 466 registerFloatingWidget(FloatingDockContainer * floatingWidget)467 void DockManager::registerFloatingWidget(FloatingDockContainer *floatingWidget) 468 { 469 d->m_floatingWidgets.append(floatingWidget); 470 emit floatingWidgetCreated(floatingWidget); 471 qCInfo(adsLog) << "d->FloatingWidgets.count() " << d->m_floatingWidgets.count(); 472 } 473 removeFloatingWidget(FloatingDockContainer * floatingWidget)474 void DockManager::removeFloatingWidget(FloatingDockContainer *floatingWidget) 475 { 476 d->m_floatingWidgets.removeAll(floatingWidget); 477 } 478 registerDockContainer(DockContainerWidget * dockContainer)479 void DockManager::registerDockContainer(DockContainerWidget *dockContainer) 480 { 481 d->m_containers.append(dockContainer); 482 } 483 removeDockContainer(DockContainerWidget * dockContainer)484 void DockManager::removeDockContainer(DockContainerWidget *dockContainer) 485 { 486 if (this != dockContainer) 487 d->m_containers.removeAll(dockContainer); 488 } 489 containerOverlay() const490 DockOverlay *DockManager::containerOverlay() const { return d->m_containerOverlay; } 491 dockAreaOverlay() const492 DockOverlay *DockManager::dockAreaOverlay() const { return d->m_dockAreaOverlay; } 493 dockContainers() const494 const QList<DockContainerWidget *> DockManager::dockContainers() const 495 { 496 return d->m_containers; 497 } 498 floatingWidgets() const499 const QList<QPointer<FloatingDockContainer>> DockManager::floatingWidgets() const 500 { 501 return d->m_floatingWidgets; 502 } 503 zOrderIndex() const504 unsigned int DockManager::zOrderIndex() const { return 0; } 505 saveState(int version) const506 QByteArray DockManager::saveState(int version) const 507 { 508 QByteArray xmlData; 509 QXmlStreamWriter stream(&xmlData); 510 auto configFlags = DockManager::configFlags(); 511 stream.setAutoFormatting(configFlags.testFlag(XmlAutoFormattingEnabled)); 512 stream.writeStartDocument(); 513 stream.writeStartElement("QtAdvancedDockingSystem"); 514 stream.writeAttribute("version", QString::number(CurrentVersion)); 515 stream.writeAttribute("userVersion", QString::number(version)); 516 stream.writeAttribute("containers", QString::number(d->m_containers.count())); 517 for (auto container : qAsConst(d->m_containers)) 518 container->saveState(stream); 519 520 stream.writeEndElement(); 521 stream.writeEndDocument(); 522 return xmlData; 523 } 524 restoreState(const QByteArray & state,int version)525 bool DockManager::restoreState(const QByteArray &state, int version) 526 { 527 // Prevent multiple calls as long as state is not restore. This may 528 // happen, if QApplication::processEvents() is called somewhere 529 if (d->m_restoringState) 530 return false; 531 532 // We hide the complete dock manager here. Restoring the state means 533 // that DockWidgets are removed from the DockArea internal stack layout 534 // which in turn means, that each time a widget is removed the stack 535 // will show and raise the next available widget which in turn 536 // triggers show events for the dock widgets. To avoid this we hide the 537 // dock manager. Because there will be no processing of application 538 // events until this function is finished, the user will not see this 539 // hiding 540 bool isHidden = this->isHidden(); 541 if (!isHidden) 542 hide(); 543 544 d->m_restoringState = true; 545 emit restoringState(); 546 bool result = d->restoreState(state, version); 547 d->m_restoringState = false; 548 if (!isHidden) 549 show(); 550 551 emit stateRestored(); 552 return result; 553 } 554 showEvent(QShowEvent * event)555 void DockManager::showEvent(QShowEvent *event) 556 { 557 Super::showEvent(event); 558 if (d->m_uninitializedFloatingWidgets.empty()) 559 return; 560 561 for (auto floatingWidget : qAsConst(d->m_uninitializedFloatingWidgets)) 562 floatingWidget->show(); 563 564 d->m_uninitializedFloatingWidgets.clear(); 565 } 566 findDockWidget(const QString & objectName) const567 DockWidget *DockManager::findDockWidget(const QString &objectName) const 568 { 569 return d->m_dockWidgetsMap.value(objectName, nullptr); 570 } 571 removeDockWidget(DockWidget * dockWidget)572 void DockManager::removeDockWidget(DockWidget *dockWidget) 573 { 574 emit dockWidgetAboutToBeRemoved(dockWidget); 575 d->m_dockWidgetsMap.remove(dockWidget->objectName()); 576 DockContainerWidget::removeDockWidget(dockWidget); 577 emit dockWidgetRemoved(dockWidget); 578 } 579 dockWidgetsMap() const580 QMap<QString, DockWidget *> DockManager::dockWidgetsMap() const { return d->m_dockWidgetsMap; } 581 isRestoringState() const582 bool DockManager::isRestoringState() const { return d->m_restoringState; } 583 showWorkspaceMananger()584 void DockManager::showWorkspaceMananger() 585 { 586 save(); // Save current workspace 587 588 WorkspaceDialog workspaceDialog(this, parentWidget()); 589 workspaceDialog.setAutoLoadWorkspace(autoRestorLastWorkspace()); 590 workspaceDialog.exec(); 591 592 QTC_ASSERT(d->m_settings, return ); 593 d->m_settings->setValue(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY, 594 workspaceDialog.autoLoadWorkspace()); 595 } 596 isWorkspacePreset(const QString & workspace) const597 bool DockManager::isWorkspacePreset(const QString &workspace) const 598 { 599 return d->m_workspacePresets.contains(workspace); 600 } 601 save()602 bool DockManager::save() 603 { 604 if (isModeChangeState()) 605 return false; 606 607 emit aboutToSaveWorkspace(); 608 609 bool result = write(activeWorkspace(), saveState(), parentWidget()); 610 if (result) 611 d->m_workspaceDateTimes.insert(activeWorkspace(), QDateTime::currentDateTime()); 612 else 613 QMessageBox::warning(parentWidget(), 614 tr("Cannot Save Workspace"), 615 tr("Could not save workspace to file %1") 616 .arg(workspaceNameToFilePath(d->m_workspaceName) 617 .toUserOutput())); 618 619 return result; 620 } 621 activeWorkspace() const622 QString DockManager::activeWorkspace() const { return d->m_workspaceName; } 623 lastWorkspace() const624 QString DockManager::lastWorkspace() const 625 { 626 QTC_ASSERT(d->m_settings, return {}); 627 return d->m_settings->value(Constants::STARTUP_WORKSPACE_SETTINGS_KEY).toString(); 628 } 629 autoRestorLastWorkspace() const630 bool DockManager::autoRestorLastWorkspace() const 631 { 632 QTC_ASSERT(d->m_settings, return false); 633 return d->m_settings->value(Constants::AUTO_RESTORE_WORKSPACE_SETTINGS_KEY).toBool(); 634 } 635 636 const QString m_dirName = QLatin1String("workspaces"); 637 const QString m_fileExt = QLatin1String(".wrk"); // TODO 638 workspaceFileExtension() const639 QString DockManager::workspaceFileExtension() const { return m_fileExt; } 640 workspaces()641 QStringList DockManager::workspaces() 642 { 643 if (d->m_workspaces.isEmpty() || d->m_workspaceListDirty) { 644 auto tmp = Utils::toSet(d->m_workspaces); 645 646 QTC_ASSERT(d->m_settings, return {}); 647 QDir workspaceDir(QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') 648 + m_dirName); 649 QFileInfoList workspaceFiles 650 = workspaceDir.entryInfoList(QStringList() << QLatin1Char('*') + m_fileExt, 651 QDir::NoFilter, 652 QDir::Time); 653 for (const QFileInfo &fileInfo : workspaceFiles) { 654 QString workspaceName = fileNameToWorkspaceName(fileInfo.completeBaseName()); 655 d->m_workspaceDateTimes.insert(workspaceName, fileInfo.lastModified()); 656 tmp.insert(workspaceName); 657 } 658 659 d->m_workspaceListDirty = false; 660 d->m_workspaces = Utils::toList(tmp); 661 } 662 return d->m_workspaces; 663 } 664 workspacePresets() const665 QSet<QString> DockManager::workspacePresets() const 666 { 667 if (d->m_workspacePresets.isEmpty()) { 668 QDir workspacePresetsDir(d->m_workspacePresetsPath); 669 QFileInfoList workspacePresetsFiles 670 = workspacePresetsDir.entryInfoList(QStringList() << QLatin1Char('*') + m_fileExt, 671 QDir::NoFilter, 672 QDir::Time); 673 for (const QFileInfo &fileInfo : workspacePresetsFiles) 674 d->m_workspacePresets.insert(fileNameToWorkspaceName(fileInfo.completeBaseName())); 675 } 676 return d->m_workspacePresets; 677 } 678 workspaceDateTime(const QString & workspace) const679 QDateTime DockManager::workspaceDateTime(const QString &workspace) const 680 { 681 return d->m_workspaceDateTimes.value(workspace); 682 } 683 workspaceNameToFilePath(const QString & workspaceName) const684 Utils::FilePath DockManager::workspaceNameToFilePath(const QString &workspaceName) const 685 { 686 QTC_ASSERT(d->m_settings, return {}); 687 return Utils::FilePath::fromString( 688 QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') + m_dirName 689 + QLatin1Char('/') + workspaceNameToFileName(workspaceName)); 690 } 691 fileNameToWorkspaceName(const QString & fileName) const692 QString DockManager::fileNameToWorkspaceName(const QString &fileName) const 693 { 694 QString copy = QFileInfo(fileName).baseName(); 695 copy.replace("_", " "); 696 return copy; 697 } 698 workspaceNameToFileName(const QString & workspaceName) const699 QString DockManager::workspaceNameToFileName(const QString &workspaceName) const 700 { 701 QString copy = workspaceName; 702 copy.replace(" ", "_"); 703 copy.append(m_fileExt); 704 return copy; 705 } 706 707 /** 708 * Creates \a workspace, but does not actually create the file. 709 */ createWorkspace(const QString & workspace)710 bool DockManager::createWorkspace(const QString &workspace) 711 { 712 if (workspaces().contains(workspace)) 713 return false; 714 715 bool result = write(workspace, saveState(), parentWidget()); 716 if (result) { 717 d->m_workspaces.insert(1, workspace); 718 d->m_workspaceDateTimes.insert(workspace, QDateTime::currentDateTime()); 719 emit workspaceListChanged(); 720 } else { 721 QMessageBox::warning(parentWidget(), 722 tr("Cannot Save Workspace"), 723 tr("Could not save workspace to file %1") 724 .arg(workspaceNameToFilePath(d->m_workspaceName) 725 .toUserOutput())); 726 } 727 728 return result; 729 } 730 openWorkspace(const QString & workspace)731 bool DockManager::openWorkspace(const QString &workspace) 732 { 733 // Do nothing if we have that workspace already loaded, exception if it is 734 // a preset workspace. In this case we still want to be able to load the 735 // default workspace to undo potential user changes. 736 if (workspace == d->m_workspaceName && !isWorkspacePreset(workspace)) 737 return true; 738 739 if (!workspaces().contains(workspace)) 740 return false; 741 742 // Check if the currently active workspace isn't empty and try to save it 743 if (!d->m_workspaceName.isEmpty()) { 744 // Allow everyone to set something in the workspace and before saving 745 emit aboutToUnloadWorkspace(d->m_workspaceName); 746 if (!save()) 747 return false; 748 } 749 750 // Try loading the file 751 QByteArray data = loadWorkspace(workspace); 752 if (data.isEmpty()) 753 return false; 754 755 emit openingWorkspace(workspace); 756 // If data was loaded from file try to restore its state 757 if (!data.isNull() && !restoreState(data)) 758 return false; 759 760 d->m_workspaceName = workspace; 761 emit workspaceLoaded(workspace); 762 763 return true; 764 } 765 reloadActiveWorkspace()766 bool DockManager::reloadActiveWorkspace() 767 { 768 if (!workspaces().contains(activeWorkspace())) 769 return false; 770 771 // Try loading the file 772 QByteArray data = loadWorkspace(activeWorkspace()); 773 if (data.isEmpty()) 774 return false; 775 776 // If data was loaded from file try to restore its state 777 if (!data.isNull() && !restoreState(data)) 778 return false; 779 780 emit workspaceReloaded(activeWorkspace()); 781 782 return true; 783 } 784 785 /** 786 * \brief Shows a dialog asking the user to confirm deleting the workspace \p workspace 787 */ confirmWorkspaceDelete(const QStringList & workspace)788 bool DockManager::confirmWorkspaceDelete(const QStringList &workspace) 789 { 790 const QString title = workspace.size() == 1 ? tr("Delete Workspace") 791 : tr("Delete Workspaces"); 792 const QString question = workspace.size() == 1 793 ? tr("Delete workspace %1?").arg(workspace.first()) 794 : tr("Delete these workspaces?\n %1") 795 .arg(workspace.join("\n ")); 796 return QMessageBox::question(parentWidget(), 797 title, 798 question, 799 QMessageBox::Yes | QMessageBox::No) 800 == QMessageBox::Yes; 801 } 802 803 /** 804 * Deletes \a workspace name from workspace list and the file from disk. 805 */ deleteWorkspace(const QString & workspace)806 bool DockManager::deleteWorkspace(const QString &workspace) 807 { 808 // Remove workspace from internal list 809 if (!d->m_workspaces.contains(workspace)) 810 return false; 811 812 // Remove corresponding workspace file 813 QFile fi(workspaceNameToFilePath(workspace).toString()); 814 if (fi.exists()) { 815 if (fi.remove()) { 816 d->m_workspaces.removeOne(workspace); 817 emit workspacesRemoved(); 818 emit workspaceListChanged(); 819 return true; 820 } 821 } 822 823 return false; 824 } 825 deleteWorkspaces(const QStringList & workspaces)826 void DockManager::deleteWorkspaces(const QStringList &workspaces) 827 { 828 for (const QString &workspace : workspaces) 829 deleteWorkspace(workspace); 830 } 831 cloneWorkspace(const QString & original,const QString & clone)832 bool DockManager::cloneWorkspace(const QString &original, const QString &clone) 833 { 834 if (!d->m_workspaces.contains(original)) 835 return false; 836 837 QFile fi(workspaceNameToFilePath(original).toString()); 838 // If the file does not exist, we can still clone 839 if (!fi.exists() || fi.copy(workspaceNameToFilePath(clone).toString())) { 840 d->m_workspaces.insert(1, clone); 841 d->m_workspaceDateTimes 842 .insert(clone, workspaceNameToFilePath(clone).lastModified()); 843 emit workspaceListChanged(); 844 return true; 845 } 846 return false; 847 } 848 renameWorkspace(const QString & original,const QString & newName)849 bool DockManager::renameWorkspace(const QString &original, const QString &newName) 850 { 851 if (!cloneWorkspace(original, newName)) 852 return false; 853 854 if (original == activeWorkspace()) 855 openWorkspace(newName); 856 857 return deleteWorkspace(original); 858 } 859 resetWorkspacePreset(const QString & workspace)860 bool DockManager::resetWorkspacePreset(const QString &workspace) 861 { 862 if (!isWorkspacePreset(workspace)) 863 return false; 864 865 Utils::FilePath fileName = workspaceNameToFilePath(workspace); 866 867 if (!QFile::remove(fileName.toString())) 868 return false; 869 870 QDir presetsDir(d->m_workspacePresetsPath); 871 bool result = QFile::copy(presetsDir.filePath(workspaceNameToFileName(workspace)), 872 fileName.toString()); 873 if (result) 874 d->m_workspaceDateTimes.insert(workspace, QDateTime::currentDateTime()); 875 876 return result; 877 } 878 setModeChangeState(bool value)879 void DockManager::setModeChangeState(bool value) 880 { 881 d->m_modeChangeState = value; 882 } 883 isModeChangeState() const884 bool DockManager::isModeChangeState() const 885 { 886 return d->m_modeChangeState; 887 } 888 importWorkspace(const QString & workspace)889 void DockManager::importWorkspace(const QString &workspace) 890 { 891 // Extract workspace name 892 QString workspaceName = fileNameToWorkspaceName(workspace); 893 894 // Check if the workspace is already contained in the list of workspaces. If that is the case 895 // add a counter to the workspace name. 896 if (workspaces().contains(workspaceName)) { 897 int i = 2; 898 QString copy; 899 do { 900 copy = workspaceName + QLatin1String(" (") + QString::number(i) + QLatin1Char(')'); 901 ++i; 902 } while (workspaces().contains(copy)); 903 workspaceName = copy; 904 } 905 906 QString fileName = workspaceNameToFileName(workspaceName); 907 QFile file(workspace); 908 if (!file.exists()) { 909 qCInfo(adsLog) << QString("File doesn't exist '%1'").arg(workspace); 910 return; 911 } 912 913 QDir workspaceDir(QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') + m_dirName); 914 915 if (!file.copy(workspaceDir.filePath(fileName))) { 916 qCInfo(adsLog) << QString("Could not copy '%1' to '%2' error: %3").arg( 917 workspace, workspaceDir.filePath(fileName), file.errorString()); 918 } else { 919 d->m_workspaces.insert(1, workspaceName); 920 d->m_workspaceDateTimes.insert(workspaceName, 921 workspaceNameToFilePath(workspaceName).lastModified()); 922 d->m_workspaceListDirty = true; 923 // After importing the workspace, update the workspace list 924 workspaces(); 925 emit workspaceListChanged(); 926 } 927 } 928 exportWorkspace(const QString & target,const QString & workspace)929 void DockManager::exportWorkspace(const QString &target, const QString &workspace) 930 { 931 // If we came this far the user decided that in case the target already exists to overwrite it. 932 // We first need to remove the existing file, otherwise QFile::copy() will fail. 933 QFileInfo targetFileInfo(target); 934 935 // Remove the file which supposed to be overwritten 936 if (targetFileInfo.exists()) { 937 QFile fi(targetFileInfo.absoluteFilePath()); 938 if (!fi.remove()) { 939 qCInfo(adsLog) << QString("Couldn't remove '%1'").arg(targetFileInfo.absoluteFilePath()); 940 return; 941 } 942 } 943 944 // Check if the target directory exists 945 if (!targetFileInfo.absoluteDir().exists()) { 946 qCInfo(adsLog) << QString("Directory doesn't exist '%1'").arg(targetFileInfo.dir().dirName()); 947 return; 948 } 949 950 // Check if the workspace exists 951 Utils::FilePath workspaceFilePath = workspaceNameToFilePath(workspace); 952 if (!workspaceFilePath.exists()) { 953 qCInfo(adsLog) << QString("Workspace doesn't exist '%1'").arg(workspaceFilePath.toString()); 954 return; 955 } 956 957 // Finally copy the workspace to the target 958 QFile workspaceFile(workspaceFilePath.toString()); 959 if (!workspaceFile.copy(targetFileInfo.absoluteFilePath())) { 960 qCInfo(adsLog) << QString("Could not copy '%1' to '%2' error: %3").arg( 961 workspace, workspaceFilePath.toString(), workspaceFile.errorString()); 962 } 963 } 964 write(const QString & workspace,const QByteArray & data,QString * errorString) const965 bool DockManager::write(const QString &workspace, const QByteArray &data, QString *errorString) const 966 { 967 Utils::FilePath fileName = workspaceNameToFilePath(workspace); 968 969 QDir tmp; 970 tmp.mkpath(fileName.toFileInfo().path()); 971 Utils::FileSaver fileSaver(fileName, QIODevice::Text); 972 if (!fileSaver.hasError()) 973 fileSaver.write(data); 974 975 bool ok = fileSaver.finalize(); 976 977 if (!ok && errorString) 978 *errorString = fileSaver.errorString(); 979 980 return ok; 981 } 982 write(const QString & workspace,const QByteArray & data,QWidget * parent) const983 bool DockManager::write(const QString &workspace, const QByteArray &data, QWidget *parent) const 984 { 985 QString errorString; 986 const bool success = write(workspace, data, &errorString); 987 if (!success) 988 QMessageBox::critical(parent, 989 QCoreApplication::translate("Utils::FileSaverBase", "File Error"), 990 errorString); 991 return success; 992 } 993 loadWorkspace(const QString & workspace) const994 QByteArray DockManager::loadWorkspace(const QString &workspace) const 995 { 996 QByteArray data; 997 Utils::FilePath fileName = workspaceNameToFilePath(workspace); 998 if (fileName.exists()) { 999 QFile file(fileName.toString()); 1000 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 1001 QMessageBox::warning(parentWidget(), 1002 tr("Cannot Restore Workspace"), 1003 tr("Could not restore workspace %1") 1004 .arg(fileName.toUserOutput())); 1005 return data; 1006 } 1007 data = file.readAll(); 1008 file.close(); 1009 } 1010 return data; 1011 } 1012 syncWorkspacePresets()1013 void DockManager::syncWorkspacePresets() 1014 { 1015 // Get a list of all workspace presets 1016 QSet<QString> presets = workspacePresets(); 1017 1018 // Get a list of all available workspaces 1019 QSet<QString> availableWorkspaces = Utils::toSet(workspaces()); 1020 presets.subtract(availableWorkspaces); 1021 1022 // Copy all missing workspace presets over to the local workspace folder 1023 QDir presetsDir(d->m_workspacePresetsPath); 1024 QDir workspaceDir(QFileInfo(d->m_settings->fileName()).path() + QLatin1Char('/') + m_dirName); 1025 // Try do create the 'workspaces' directory if it doesn't exist already 1026 workspaceDir.mkpath(workspaceDir.absolutePath()); 1027 if (!workspaceDir.exists()) { 1028 qCInfo(adsLog) << QString("Could not make directory '%1')").arg(workspaceDir.absolutePath()); 1029 return; 1030 } 1031 1032 for (const auto &preset : presets) { 1033 QString fileName = workspaceNameToFileName(preset); 1034 QString filePath = presetsDir.filePath(fileName); 1035 QFile file(filePath); 1036 1037 if (file.exists()) { 1038 if (!file.copy(workspaceDir.filePath(fileName))) 1039 qCInfo(adsLog) << QString("Could not copy '%1' to '%2' error: %3").arg( 1040 filePath, workspaceDir.filePath(fileName), file.errorString()); 1041 1042 d->m_workspaceListDirty = true; 1043 } 1044 } 1045 1046 // After copying over missing workspace presets, update the workspace list 1047 workspaces(); 1048 } 1049 saveStartupWorkspace()1050 void DockManager::saveStartupWorkspace() 1051 { 1052 QTC_ASSERT(d->m_settings, return ); 1053 d->m_settings->setValue(Constants::STARTUP_WORKSPACE_SETTINGS_KEY, activeWorkspace()); 1054 } 1055 notifyWidgetOrAreaRelocation(QWidget * droppedWidget)1056 void DockManager::notifyWidgetOrAreaRelocation(QWidget *droppedWidget) 1057 { 1058 if (d->m_focusController) 1059 d->m_focusController->notifyWidgetOrAreaRelocation(droppedWidget); 1060 } 1061 notifyFloatingWidgetDrop(FloatingDockContainer * floatingWidget)1062 void DockManager::notifyFloatingWidgetDrop(FloatingDockContainer *floatingWidget) 1063 { 1064 if (d->m_focusController) 1065 d->m_focusController->notifyFloatingWidgetDrop(floatingWidget); 1066 } 1067 setDockWidgetFocused(DockWidget * dockWidget)1068 void DockManager::setDockWidgetFocused(DockWidget *dockWidget) 1069 { 1070 if (d->m_focusController) 1071 d->m_focusController->setDockWidgetFocused(dockWidget); 1072 } 1073 1074 } // namespace ADS 1075