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

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Alexander Dymo <adymo@kdevelop.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "area.h"
0008 
0009 #include <QMap>
0010 #include <QList>
0011 #include <QStringList>
0012 #include <QAction>
0013 #include <QPointer>
0014 
0015 #include "view.h"
0016 #include "document.h"
0017 #include "areaindex.h"
0018 #include "controller.h"
0019 #include <debug.h>
0020 
0021 namespace Sublime {
0022 
0023 // class AreaPrivate
0024 
0025 class AreaPrivate
0026 {
0027 public:
0028     AreaPrivate()
0029         : rootIndex(new RootAreaIndex)
0030         , currentIndex(rootIndex.data())
0031     {
0032     }
0033 
0034     AreaPrivate(const AreaPrivate &p)
0035      : title(p.title)
0036      , rootIndex(new RootAreaIndex(*(p.rootIndex)))
0037      , currentIndex(rootIndex.data())
0038      , controller(p.controller)
0039      , toolViewPositions()
0040      , desiredToolViews(p.desiredToolViews)
0041      , shownToolViews(p.shownToolViews)
0042      , iconName(p.iconName)
0043      , workingSet(p.workingSet)
0044      , workingSetPersists(p.workingSetPersists)
0045      , m_actions(p.m_actions)
0046     {
0047     }
0048 
0049     ~AreaPrivate()
0050     {
0051     }
0052 
0053     struct ViewFinder {
0054         explicit ViewFinder(View *_view): view(_view), index(nullptr) {}
0055         Area::WalkerMode operator() (AreaIndex *idx) {
0056             if (idx->hasView(view))
0057             {
0058                 index = idx;
0059                 return Area::StopWalker;
0060             }
0061             return Area::ContinueWalker;
0062         }
0063         View *view;
0064         AreaIndex *index;
0065     };
0066 
0067     struct ViewLister {
0068         Area::WalkerMode operator()(AreaIndex *idx) {
0069             views += idx->views();
0070             return Area::ContinueWalker;
0071         }
0072         QList<View*> views;
0073     };
0074 
0075     QString title;
0076 
0077     QScopedPointer<RootAreaIndex> rootIndex;
0078     AreaIndex *currentIndex;
0079     Controller *controller = nullptr;
0080 
0081     QList<View*> toolViews;
0082     QMap<View *, Sublime::Position> toolViewPositions;
0083     QMap<QString, Sublime::Position> desiredToolViews;
0084     QMap<Sublime::Position, QStringList> shownToolViews;
0085     QString iconName;
0086     QString workingSet;
0087     bool workingSetPersists = true;
0088     QPointer<View> activeView;
0089     QList<QAction*> m_actions;
0090 };
0091 
0092 // class Area
0093 
0094 Area::Area(Controller *controller, const QString &name, const QString &title)
0095     : QObject(controller)
0096     , d_ptr(new AreaPrivate())
0097 {
0098     Q_D(Area);
0099 
0100     // FIXME: using objectName seems fishy. Introduce areaType method,
0101     // or some such.
0102     setObjectName(name);
0103     d->title = title;
0104     d->controller = controller;
0105     d->iconName = QStringLiteral("kdevelop");
0106     initialize();
0107 }
0108 
0109 Area::Area(const Area &area)
0110     : QObject(area.controller())
0111     , d_ptr(new AreaPrivate(*(area.d_ptr)))
0112 {
0113     Q_D(Area);
0114 
0115     setObjectName(area.objectName());
0116 
0117     //clone tool views
0118     d->toolViews.clear();
0119     for (View* view : qAsConst(area.toolViews())) {
0120         addToolView(view->document()->createView(), area.toolViewPosition(view));
0121     }
0122     initialize();
0123 }
0124 
0125 void Area::initialize()
0126 {
0127     Q_D(Area);
0128 
0129     connect(this, &Area::viewAdded,
0130             d->controller, &Controller::notifyViewAdded);
0131     connect(this, &Area::aboutToRemoveView,
0132             d->controller, &Controller::notifyViewRemoved);
0133     connect(this, &Area::toolViewAdded,
0134             d->controller, &Controller::notifyToolViewAdded);
0135     connect(this, &Area::aboutToRemoveToolView,
0136             d->controller, &Controller::notifyToolViewRemoved);
0137     connect(this, &Area::toolViewMoved,
0138             d->controller, &Controller::toolViewMoved);
0139 
0140     /* In theory, ownership is passed to us, so should not bother detecting
0141     deletion outside.  */
0142     // Functor will be called after destructor has run -> capture controller pointer by value
0143     // otherwise we crash because we access the already freed pointer this->d
0144     auto controller = d->controller;
0145     connect(this, &Area::destroyed, controller, [this, controller](QObject* obj) {
0146         Q_ASSERT(obj == this);
0147         controller->removeArea(this);
0148     });
0149 }
0150 
0151 Area::~Area() = default;
0152 
0153 View* Area::activeView() const
0154 {
0155     Q_D(const Area);
0156 
0157     return d->activeView.data();
0158 }
0159 
0160 void Area::setActiveView(View* view)
0161 {
0162     Q_D(Area);
0163 
0164     d->activeView = view;
0165 }
0166 
0167 void Area::addView(View *view, AreaIndex *index, View *after)
0168 {
0169     //View *after = 0;
0170     if (!after  &&  controller()->openAfterCurrent()) {
0171         after = activeView();
0172     }
0173     index->add(view, after);
0174     connect(view, &View::positionChanged, this, &Area::positionChanged);
0175     qCDebug(SUBLIME) << "view added in" << this;
0176     connect(this, &Area::destroyed, view, &View::deleteLater);
0177     emit viewAdded(index, view);
0178 }
0179 
0180 void Area::addView(View *view, View *after)
0181 {
0182     Q_D(Area);
0183 
0184     AreaIndex *index = d->currentIndex;
0185     if (after)
0186     {
0187         AreaIndex *i = indexOf(after);
0188         if (i)
0189             index = i;
0190     }
0191     addView(view, index);
0192 }
0193 
0194 void Area::addView(View *view, View *viewToSplit, Qt::Orientation orientation)
0195 {
0196     AreaIndex *indexToSplit = indexOf(viewToSplit);
0197     addView(view, indexToSplit, orientation);
0198 }
0199 
0200 void Area::addView(View* view, AreaIndex* indexToSplit, Qt::Orientation orientation)
0201 {
0202     indexToSplit->split(view, orientation);
0203     emit viewAdded(indexToSplit, view);
0204     connect(this, &Area::destroyed, view, &View::deleteLater);
0205 }
0206 
0207 View* Area::removeView(View *view)
0208 {
0209     AreaIndex *index = indexOf(view);
0210     if (!index)
0211         return nullptr;
0212 
0213     emit aboutToRemoveView(index, view);
0214     index->remove(view);
0215     emit viewRemoved(index, view);
0216 
0217     return view;
0218 }
0219 
0220 AreaIndex *Area::indexOf(View *view)
0221 {
0222     Q_D(Area);
0223 
0224     AreaPrivate::ViewFinder f(view);
0225     walkViews(f, d->rootIndex.data());
0226     return f.index;
0227 }
0228 
0229 RootAreaIndex *Area::rootIndex() const
0230 {
0231     Q_D(const Area);
0232 
0233     return d->rootIndex.data();
0234 }
0235 
0236 void Area::addToolView(View *view, Position defaultPosition)
0237 {
0238     Q_D(Area);
0239 
0240     d->toolViews.append(view);
0241     const QString id = view->document()->documentSpecifier();
0242     const Position position = d->desiredToolViews.value(id, defaultPosition);
0243     d->desiredToolViews[id] = position;
0244     d->toolViewPositions[view] = position;
0245     emit toolViewAdded(view, position);
0246 }
0247 
0248 void Sublime::Area::raiseToolView(View * toolView)
0249 {
0250     emit requestToolViewRaise(toolView);
0251 }
0252 
0253 View* Area::removeToolView(View *view)
0254 {
0255     Q_D(Area);
0256 
0257     if (!d->toolViews.contains(view))
0258         return nullptr;
0259 
0260     emit aboutToRemoveToolView(view, d->toolViewPositions[view]);
0261     QString id = view->document()->documentSpecifier();
0262     qCDebug(SUBLIME) << this << "removed tool view " << id;
0263     d->desiredToolViews.remove(id);
0264     d->toolViews.removeAll(view);
0265     d->toolViewPositions.remove(view);
0266     return view;
0267 }
0268 
0269 void Area::moveToolView(View *toolView, Position newPosition)
0270 {
0271     Q_D(Area);
0272 
0273     if (!d->toolViews.contains(toolView))
0274         return;
0275 
0276     QString id = toolView->document()->documentSpecifier();
0277     d->desiredToolViews[id] = newPosition;
0278     d->toolViewPositions[toolView] = newPosition;
0279     emit toolViewMoved(toolView, newPosition);
0280 }
0281 
0282 const QList<View*> &Area::toolViews() const
0283 {
0284     Q_D(const Area);
0285 
0286     return d->toolViews;
0287 }
0288 
0289 Position Area::toolViewPosition(View *toolView) const
0290 {
0291     Q_D(const Area);
0292 
0293     return d->toolViewPositions[toolView];
0294 }
0295 
0296 Controller *Area::controller() const
0297 {
0298     Q_D(const Area);
0299 
0300     return d->controller;
0301 }
0302 
0303 QList<View*> Sublime::Area::views()
0304 {
0305     Q_D(Area);
0306 
0307     AreaPrivate::ViewLister lister;
0308     walkViews(lister, d->rootIndex.data());
0309     return lister.views;
0310 }
0311 
0312 QString Area::title() const
0313 {
0314     Q_D(const Area);
0315 
0316     return d->title;
0317 }
0318 
0319 void Area::setTitle(const QString &title)
0320 {
0321     Q_D(Area);
0322 
0323     d->title = title;
0324 }
0325 
0326 void Area::save(KConfigGroup& group) const
0327 {
0328     Q_D(const Area);
0329 
0330     QStringList desired;
0331     desired.reserve(d->desiredToolViews.size());
0332     for (auto i = d->desiredToolViews.begin(), e = d->desiredToolViews.end(); i != e; ++i) {
0333         desired << i.key() + QLatin1Char(':') + QString::number(static_cast<int>(i.value()));
0334     }
0335     group.writeEntry("desired views", desired);
0336     qCDebug(SUBLIME) << "save " << this << "wrote" << group.readEntry("desired views", "");
0337     group.writeEntry("view on left", shownToolViews(Sublime::Left));
0338     group.writeEntry("view on right", shownToolViews(Sublime::Right));
0339     group.writeEntry("view on top", shownToolViews(Sublime::Top));
0340     group.writeEntry("view on bottom", shownToolViews(Sublime::Bottom));
0341 }
0342 
0343 void Area::load(const KConfigGroup& group)
0344 {
0345     Q_D(Area);
0346 
0347     qCDebug(SUBLIME) << "loading areas config";
0348     d->desiredToolViews.clear();
0349     const QStringList desired = group.readEntry("desired views", QStringList());
0350     for (const QString& s : desired) {
0351         int i = s.indexOf(QLatin1Char(':'));
0352         if (i != -1)
0353         {
0354             QString id = s.left(i);
0355             int pos_i = s.midRef(i+1).toInt();
0356             auto pos = static_cast<Sublime::Position>(pos_i);
0357             if (pos != Sublime::Left && pos != Sublime::Right && pos != Sublime::Top && pos != Sublime::Bottom)
0358             {
0359                 pos = Sublime::Bottom;
0360             }
0361 
0362             d->desiredToolViews[id] = pos;
0363         }
0364     }
0365     setShownToolViews(Sublime::Left, group.readEntry("view on left", QStringList()));
0366     setShownToolViews(Sublime::Right,
0367                      group.readEntry("view on right", QStringList()));
0368     setShownToolViews(Sublime::Top, group.readEntry("view on top", QStringList()));
0369     setShownToolViews(Sublime::Bottom,
0370                      group.readEntry("view on bottom", QStringList()));
0371 }
0372 
0373 bool Area::wantToolView(const QString& id)
0374 {
0375     Q_D(Area);
0376 
0377     return (d->desiredToolViews.contains(id));
0378 }
0379 
0380 void Area::setShownToolViews(Sublime::Position pos, const QStringList& ids)
0381 {
0382     Q_D(Area);
0383 
0384     d->shownToolViews[pos] = ids;
0385 }
0386 
0387 QStringList Area::shownToolViews(Sublime::Position pos) const
0388 {
0389     Q_D(const Area);
0390 
0391     if (pos == Sublime::AllPositions) {
0392         QStringList allIds;
0393         allIds.reserve(d->shownToolViews.size());
0394         std::for_each(d->shownToolViews.constBegin(), d->shownToolViews.constEnd(), [&](const QStringList& ids) {
0395             allIds << ids;
0396         });
0397         return allIds;
0398     }
0399 
0400     return d->shownToolViews[pos];
0401 }
0402 
0403 void Area::setDesiredToolViews(
0404     const QMap<QString, Sublime::Position>& desiredToolViews)
0405 {
0406     Q_D(Area);
0407 
0408     d->desiredToolViews = desiredToolViews;
0409 }
0410 
0411 QString Area::iconName() const
0412 {
0413     Q_D(const Area);
0414 
0415     return d->iconName;
0416 }
0417 
0418 void Area::setIconName(const QString& iconName)
0419 {
0420     Q_D(Area);
0421 
0422     d->iconName = iconName;
0423 }
0424 
0425 void Area::positionChanged(View *view, int newPos)
0426 {
0427     qCDebug(SUBLIME) << view << newPos;
0428     AreaIndex *index = indexOf(view);
0429     index->moveViewPosition(view, newPos);
0430 }
0431 
0432 
0433 QString Area::workingSet() const
0434 {
0435     Q_D(const Area);
0436 
0437     return d->workingSet;
0438 }
0439 
0440 bool Area::workingSetPersistent() const
0441 {
0442     Q_D(const Area);
0443 
0444     return d->workingSetPersists;
0445 }
0446 
0447 void Area::setWorkingSet(const QString &name, bool persistent, Area *oldArea)
0448 {
0449     Q_D(Area);
0450 
0451     oldArea = oldArea ? oldArea : this;
0452     if (oldArea != this || name != d->workingSet) {
0453         qCDebug(SUBLIME) << this << "setting new working-set" << name;
0454         QString oldName = d->workingSet;
0455         emit changingWorkingSet(this, oldArea, oldName, name);
0456         d->workingSet = name;
0457         d->workingSetPersists = persistent;
0458         emit changedWorkingSet(this, oldArea, oldName, name);
0459     } else if (name.isEmpty()) {
0460         d->workingSetPersists = persistent;
0461     }
0462 }
0463 
0464 bool Area::closeView(View* view, bool silent)
0465 {
0466     QPointer<Document> doc = view->document();
0467 
0468     // We don't just delete the view, because if silent is false, we might need to ask the user.
0469     if(doc && !silent)
0470     {
0471         // Do some counting to check whether we need to ask the user for feedback
0472         qCDebug(SUBLIME) << "Closing view for" << view->document()->documentSpecifier() << "views" << view->document()->views().size() << "in area" << this;
0473         int viewsInCurrentArea = 0; // Number of views for the same document in the current area
0474         int viewsInOtherAreas = 0; // Number of views for the same document in other areas
0475         int viewsInOtherWorkingSets = 0; // Number of views for the same document in areas with different working-set
0476 
0477         for (View* otherView : qAsConst(doc.data()->views())) {
0478             Area* area = controller()->areaForView(otherView);
0479             if(area == this)
0480                 viewsInCurrentArea += 1;
0481             if(!area || (area != this))
0482                 viewsInOtherAreas += 1;
0483             if(area && area != this && area->workingSet() != workingSet())
0484                 viewsInOtherWorkingSets += 1;
0485         }
0486 
0487         if(viewsInCurrentArea == 1 && (viewsInOtherAreas == 0 || viewsInOtherWorkingSets == 0))
0488         {
0489             // Time to ask the user for feedback, because the document will be completely closed
0490             // due to working-set synchronization
0491             if( !doc.data()->askForCloseFeedback() )
0492                 return false;
0493         }
0494     }
0495 
0496     // otherwise we can silently close the view,
0497     // the document will still have an opened view somewhere
0498     delete removeView(view);
0499 
0500     return true;
0501 }
0502 
0503 void Area::clearViews(bool silent)
0504 {
0505     const auto views = this->views();
0506     for (Sublime::View* view : views) {
0507         closeView(view, silent);
0508     }
0509 }
0510 
0511 void Area::clearDocuments()
0512 {
0513     if (views().isEmpty())
0514         emit clearWorkingSet(this);
0515     else
0516         clearViews(true);
0517 }
0518 
0519 QList<QAction*> Area::actions() const
0520 {
0521     Q_D(const Area);
0522 
0523     return d->m_actions;
0524 }
0525 
0526 void Area::addAction(QAction* action)
0527 {
0528     Q_D(Area);
0529 
0530     Q_ASSERT(!d->m_actions.contains(action));
0531     connect(action, &QAction::destroyed, this, &Area::actionDestroyed);
0532     d->m_actions.append(action);
0533 }
0534 
0535 void Area::actionDestroyed(QObject* action)
0536 {
0537     Q_D(Area);
0538 
0539     d->m_actions.removeAll(qobject_cast<QAction*>(action));
0540 }
0541 
0542 }
0543 
0544 #include "moc_area.cpp"