File indexing completed on 2024-04-28 04:37:28

0001 /*
0002     SPDX-FileCopyrightText: David Nolden <david.nolden.kdevelop@art-master.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "workingsetcontroller.h"
0008 
0009 #include <QTimer>
0010 #include <QVBoxLayout>
0011 #include <QRandomGenerator>
0012 
0013 #include "mainwindow.h"
0014 #include "partdocument.h"
0015 #include "uicontroller.h"
0016 
0017 #include <interfaces/iuicontroller.h>
0018 #include <interfaces/isession.h>
0019 
0020 #include <sublime/view.h>
0021 #include <sublime/area.h>
0022 
0023 #include <util/activetooltip.h>
0024 
0025 #include "workingsets/workingset.h"
0026 #include "workingsets/workingsettooltipwidget.h"
0027 #include "workingsets/workingsetwidget.h"
0028 #include "workingsets/closedworkingsetswidget.h"
0029 #include "core.h"
0030 #include "debug_workingset.h"
0031 
0032 using namespace KDevelop;
0033 
0034 const int toolTipTimeout = 2000;
0035 
0036 WorkingSetController::WorkingSetController()
0037 {
0038     m_hideToolTipTimer = new QTimer(this);
0039     m_hideToolTipTimer->setInterval(toolTipTimeout);
0040     m_hideToolTipTimer->setSingleShot(true);
0041 }
0042 
0043 void WorkingSetController::initialize()
0044 {
0045     //Load all working-sets
0046     KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets");
0047     QMap<QString, QStringList> areaConfigs;
0048     const auto sets = setConfig.groupList();
0049     for (const QString& set : sets) {
0050         // do not load working set if the id contains an '|', because it then belongs to an area.
0051         // this is functionally equivalent to the if ( ! config->icon ) stuff which was there before.
0052         if (set.contains(QLatin1Char('|'))) {
0053             areaConfigs[set.left(set.indexOf(QLatin1Char('|')))] << set;
0054         } else if (!setConfig.group(set).hasKey("Orientation") && setConfig.group(set).readEntry("View Count", 0) == 0) {
0055             areaConfigs[set] << set;
0056         } else {
0057             workingSet(set);
0058         }
0059     }
0060 
0061     // Clean up config
0062     for (auto it = areaConfigs.constBegin(); it != areaConfigs.constEnd(); ++it) {
0063         if (m_workingSets.contains(it.key())) {
0064             continue;
0065         }
0066         for (auto &areaConfig : it.value()) {
0067             setConfig.deleteGroup(areaConfig);
0068         }
0069     }
0070 
0071     m_emptyWorkingSet = new WorkingSet(QStringLiteral("empty"));
0072 
0073     if(!(Core::self()->setupFlags() & Core::NoUi)) {
0074         setupActions();
0075     }
0076 }
0077 
0078 void WorkingSetController::cleanup()
0079 {
0080     auto area = Core::self()->uiControllerInternal()->activeArea();
0081     if (area && !area->workingSet().isEmpty()) {
0082         Q_ASSERT(m_workingSets.contains(area->workingSet()));
0083         m_workingSets[area->workingSet()]->saveFromArea(area);
0084     }
0085 
0086     const auto oldWorkingSet = m_workingSets;
0087     for (WorkingSet* set : oldWorkingSet) {
0088         qCDebug(WORKINGSET) << "set" << set->id() << "persistent" << set->isPersistent() << "has areas:" << set->hasConnectedAreas() << "files" << set->fileList();
0089         if(!set->isPersistent() && !set->hasConnectedAreas()) {
0090             qCDebug(WORKINGSET) << "deleting";
0091             set->deleteSet(true, true);
0092         }
0093         delete set;
0094     }
0095 
0096     m_workingSets.clear();
0097 
0098     delete m_emptyWorkingSet;
0099     m_emptyWorkingSet = nullptr;
0100 }
0101 
0102 const QString WorkingSetController::makeSetId(const QString& prefix) const
0103 {
0104     QString newId;
0105     const uint maxRetries = 10;
0106     auto* randomGenerator = QRandomGenerator::global();
0107     for (uint retry = 2; retry <= maxRetries; retry++) {
0108         const auto random = randomGenerator->bounded(10000000);
0109         newId = QStringLiteral("%1_%2").arg(prefix).arg(random);
0110         WorkingSetIconParameters params(newId);
0111         for (WorkingSet* set : m_workingSets) {
0112             if(set->isEmpty()) {
0113                 continue;
0114             }
0115             // The last retry will always generate a valid set
0116             if(retry != maxRetries && WorkingSetIconParameters(set->id()).similarity(params) >= retry*8) {
0117                 newId = QString();
0118                 break;
0119             }
0120         }
0121         if(! newId.isEmpty()) {
0122             break;
0123         }
0124     }
0125     return newId;
0126 }
0127 
0128 WorkingSet* WorkingSetController::newWorkingSet(const QString& prefix)
0129 {
0130     return workingSet(makeSetId(prefix));
0131 }
0132 
0133 WorkingSet* WorkingSetController::workingSet(const QString& id)
0134 {
0135     if(id.isEmpty())
0136         return m_emptyWorkingSet;
0137 
0138     auto workingSetIt = m_workingSets.find(id);
0139     if (workingSetIt == m_workingSets.end()) {
0140         auto* set = new WorkingSet(id);
0141         connect(set, &WorkingSet::aboutToRemove,
0142                 this, &WorkingSetController::aboutToRemoveWorkingSet);
0143         workingSetIt= m_workingSets.insert(id, set);
0144         emit workingSetAdded(set);
0145     }
0146 
0147     return *workingSetIt;
0148 }
0149 
0150 QWidget* WorkingSetController::createSetManagerWidget(MainWindow* parent, Sublime::Area* fixedArea) {
0151     if (fixedArea) {
0152         return new WorkingSetWidget(fixedArea, parent);
0153     } else {
0154         return new ClosedWorkingSetsWidget(parent);
0155     }
0156 }
0157 
0158 void WorkingSetController::setupActions()
0159 {
0160 }
0161 
0162 ActiveToolTip* WorkingSetController::tooltip() const
0163 {
0164     return m_tooltip;
0165 }
0166 
0167 void WorkingSetController::showToolTip(WorkingSet* set, const QPoint& pos)
0168 {
0169     delete m_tooltip;
0170 
0171     auto* window = static_cast<KDevelop::MainWindow*>(Core::self()->uiControllerInternal()->activeMainWindow());
0172 
0173     m_tooltip = new KDevelop::ActiveToolTip(window, pos);
0174     auto* layout = new QVBoxLayout(m_tooltip);
0175     layout->setContentsMargins(0, 0, 0, 0);
0176     auto* widget = new WorkingSetToolTipWidget(m_tooltip, set, window);
0177     layout->addWidget(widget);
0178     m_tooltip->resize( m_tooltip->sizeHint() );
0179 
0180     connect(widget, &WorkingSetToolTipWidget::shouldClose, m_tooltip.data(), &ActiveToolTip::close);
0181 
0182     ActiveToolTip::showToolTip(m_tooltip);
0183 }
0184 
0185 void WorkingSetController::showGlobalToolTip()
0186 {
0187     auto* window = static_cast<KDevelop::MainWindow*>(Core::self()->uiControllerInternal()->activeMainWindow());
0188 
0189     showToolTip(workingSet(window->area()->workingSet()),
0190                               window->mapToGlobal(window->geometry().topRight()));
0191 
0192     connect(m_hideToolTipTimer, &QTimer::timeout,  m_tooltip.data(), &ActiveToolTip::deleteLater);
0193     m_hideToolTipTimer->start();
0194     connect(m_tooltip.data(), &ActiveToolTip::mouseIn, m_hideToolTipTimer, &QTimer::stop);
0195     connect(m_tooltip.data(), &ActiveToolTip::mouseOut, m_hideToolTipTimer, QOverload<>::of(&QTimer::start));
0196 }
0197 
0198 WorkingSetToolTipWidget* WorkingSetController::workingSetToolTip()
0199 {
0200     if(!m_tooltip)
0201         showGlobalToolTip();
0202 
0203     m_hideToolTipTimer->start();
0204 
0205     if(m_tooltip)
0206     {
0207         auto* widget = m_tooltip->findChild<WorkingSetToolTipWidget*>();
0208         Q_ASSERT(widget);
0209         return widget;
0210     }
0211     return nullptr;
0212 }
0213 
0214 void WorkingSetController::nextDocument()
0215 {
0216     auto widget = workingSetToolTip();
0217     if (widget) {
0218         widget->nextDocument();
0219     }
0220 }
0221 
0222 void WorkingSetController::previousDocument()
0223 {
0224     auto widget = workingSetToolTip();
0225     if (widget) {
0226         widget->previousDocument();
0227     }
0228 }
0229 
0230 void WorkingSetController::initializeController( UiController* controller )
0231 {
0232   connect( controller, &UiController::areaCreated, this, &WorkingSetController::areaCreated );
0233 }
0234 
0235 QList< WorkingSet* > WorkingSetController::allWorkingSets() const
0236 {
0237   return m_workingSets.values();
0238 }
0239 
0240 void WorkingSetController::areaCreated( Sublime::Area* area )
0241 {
0242     if (!area->workingSet().isEmpty()) {
0243         WorkingSet* set = workingSet( area->workingSet() );
0244         set->connectArea( area );
0245     }
0246 
0247     connect(area, &Sublime::Area::changingWorkingSet,
0248             this, &WorkingSetController::changingWorkingSet);
0249     connect(area, &Sublime::Area::changedWorkingSet,
0250             this, &WorkingSetController::changedWorkingSet);
0251     connect(area, &Sublime::Area::viewAdded,
0252             this, &WorkingSetController::viewAdded);
0253     connect(area, &Sublime::Area::clearWorkingSet,
0254             this, &WorkingSetController::clearWorkingSet);
0255 }
0256 
0257 void WorkingSetController::saveArea(Sublime::Area* area)
0258 {
0259     if (area && !area->workingSet().isEmpty()) {
0260         workingSet(area->workingSet())->saveFromArea(area);
0261     }
0262 }
0263 
0264 void WorkingSetController::changingWorkingSet(Sublime::Area *area, Sublime::Area *oldArea, const QString &from, const QString &to)
0265 {
0266     qCDebug(WORKINGSET) << "changing working-set from" << from << oldArea << "to" << to << area;
0267     if (from == to)
0268         return;
0269 
0270     if (!from.isEmpty()) {
0271         WorkingSet* oldSet = workingSet(from);
0272         oldSet->disconnectArea(area);
0273         if (!oldSet->id().isEmpty() && area == oldArea) {
0274             oldSet->saveFromArea(area);
0275         }
0276     }
0277 }
0278 
0279 void WorkingSetController::changedWorkingSet(Sublime::Area *area, Sublime::Area *oldArea, const QString &from, const QString &to)
0280 {
0281     qCDebug(WORKINGSET) << "changed working-set from" << from << "to" << to << "area" << area;
0282     if ((oldArea == area && from == to) || m_changingWorkingSet) {
0283         return;
0284     }
0285 
0286     if (!to.isEmpty()) {
0287         WorkingSet* newSet = workingSet(to);
0288         newSet->connectArea(area);
0289         newSet->loadToArea(area);
0290     } else {
0291         // Clear silently, any user-interaction should have happened before
0292         area->clearViews(true);
0293     }
0294 
0295     emit workingSetSwitched();
0296 }
0297 
0298 void WorkingSetController::viewAdded( Sublime::AreaIndex* , Sublime::View* )
0299 {
0300     auto* area = qobject_cast< Sublime::Area* >(sender());
0301     Q_ASSERT(area);
0302 
0303     if (area->workingSet().isEmpty()) {
0304         //Spawn a new working-set
0305         m_changingWorkingSet = true;
0306         WorkingSet* set = Core::self()->workingSetControllerInternal()->newWorkingSet(area->objectName());
0307         qCDebug(WORKINGSET) << "Spawned new working-set" << set->id() << "because a view was added";
0308         set->setPersistent(area->workingSetPersistent());
0309         set->connectArea(area);
0310         set->saveFromArea(area);
0311         area->setWorkingSet(set->id(), area->workingSetPersistent());
0312         m_changingWorkingSet = false;
0313     }
0314 }
0315 
0316 void WorkingSetController::clearWorkingSet(Sublime::Area * area)
0317 {
0318     const QString workingSetId = area->workingSet();
0319     if (workingSetId.isEmpty()) {
0320         // Nothing to do - area has no working set
0321         return;
0322     }
0323 
0324     WorkingSet* set = workingSet(workingSetId);
0325     set->deleteSet(true);
0326 
0327     WorkingSet* newSet = workingSet(workingSetId);
0328     newSet->connectArea(area);
0329     newSet->loadToArea(area);
0330     Q_ASSERT(newSet->fileList().isEmpty());
0331 }
0332 
0333 #include "moc_workingsetcontroller.cpp"