File indexing completed on 2024-04-28 05:49:35

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0003    SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
0004    SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
0005 
0006    SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #pragma once
0010 
0011 #include "doc_or_widget.h"
0012 #include "katedocmanager.h"
0013 #include "kateprivate_export.h"
0014 #include "katesplitter.h"
0015 #include "kateviewspace.h"
0016 
0017 #include <QList>
0018 #include <QPointer>
0019 #include <QScrollBar>
0020 
0021 #include <unordered_map>
0022 
0023 namespace KTextEditor
0024 {
0025 class View;
0026 class Document;
0027 }
0028 
0029 class KateDocumentInfo;
0030 
0031 class KConfigGroup;
0032 class KConfigBase;
0033 class KateMainWindow;
0034 class KateViewSpace;
0035 
0036 class KATE_PRIVATE_EXPORT KateViewManager : public KateSplitter
0037 {
0038     Q_OBJECT
0039 
0040     friend class KateViewManagementTests;
0041 
0042 public:
0043     KateViewManager(QWidget *parentW, KateMainWindow *parent);
0044     ~KateViewManager() override;
0045 
0046     /**
0047      * triggered delayed focus set to current active view
0048      */
0049     void triggerActiveViewFocus();
0050 
0051 private:
0052     /**
0053      * create all actions needed for the view manager
0054      */
0055     void setupActions();
0056 
0057     void updateViewSpaceActions();
0058 
0059 public:
0060     /* This will save the splitter configuration */
0061     void saveViewConfiguration(KConfigGroup &group);
0062 
0063     /* restore it */
0064     void restoreViewConfiguration(const KConfigGroup &group);
0065 
0066     KTextEditor::Document *openUrl(const QUrl &url,
0067                                    const QString &encoding,
0068                                    bool activate = true,
0069                                    bool ignoreForRecentFiles = false,
0070                                    const KateDocumentInfo &docInfo = KateDocumentInfo());
0071 
0072     KTextEditor::Document *openUrls(const QList<QUrl> &url, const QString &encoding, const KateDocumentInfo &docInfo = KateDocumentInfo());
0073 
0074     KTextEditor::View *openUrlWithView(const QUrl &url, const QString &encoding);
0075 
0076     KTextEditor::View *openViewForDoc(KTextEditor::Document *doc);
0077 
0078 public Q_SLOTS:
0079     void openUrl(const QUrl &url);
0080     void openUrlOrProject(const QUrl &url);
0081     void addPositionToHistory(const QUrl &url, KTextEditor::Cursor pos);
0082 
0083 public:
0084     void closeView(KTextEditor::View *view);
0085     KateMainWindow *mainWindow();
0086 
0087     void moveViewToViewSpace(KateViewSpace *dest, KateViewSpace *src, DocOrWidget doc);
0088 
0089 private Q_SLOTS:
0090     void activateView(KTextEditor::View *view);
0091     void activateSpace(KTextEditor::View *v);
0092 
0093 public Q_SLOTS:
0094     void slotDocumentNew();
0095     void slotDocumentOpen();
0096     void slotDocumentClose();
0097     void slotDocumentClose(KTextEditor::Document *document);
0098 
0099     void setActiveSpace(KateViewSpace *vs);
0100 
0101     void activateNextView();
0102     void activatePrevView();
0103     void activateLeftView();
0104     void activateRightView();
0105     void activateUpwardView();
0106     void activateDownwardView();
0107 
0108 private:
0109     void activateIntuitiveNeighborView(Qt::Orientation o, int dir);
0110     KateViewSpace *findIntuitiveNeighborView(KateSplitter *splitter, QWidget *widget, Qt::Orientation o, int dir);
0111 
0112 Q_SIGNALS:
0113     void viewChanged(KTextEditor::View *);
0114     void viewCreated(KTextEditor::View *);
0115 
0116     void historyBackEnabled(bool e);
0117     void historyForwardEnabled(bool e);
0118 
0119     void showUrlNavBarChanged(bool);
0120 
0121 public:
0122     /**
0123      * create and activate a new view for doc, if doc == 0, then
0124      * create a new document.
0125      * Can return NULL.
0126      */
0127     KTextEditor::View *createView(KTextEditor::Document *doc = nullptr, KateViewSpace *vs = nullptr);
0128 
0129     bool deleteView(KTextEditor::View *view);
0130 
0131 private:
0132     void moveViewtoSplit(KTextEditor::View *view);
0133     void moveViewtoStack(KTextEditor::View *view);
0134 
0135     /* Save the configuration of a single splitter.
0136      * If child splitters are found, it calls it self with those as the argument.
0137      * If a viewspace child is found, it is asked to save its filelist.
0138      */
0139     QString saveSplitterConfig(KateSplitter *s, KConfigBase *config, const QString &viewConfGrp);
0140 
0141     /** Restore a single splitter.
0142      * This is all the work is done for @see saveSplitterConfig()
0143      */
0144     void restoreSplitter(const KConfigBase *config, const QString &group, KateSplitter *parent, const QString &viewConfGrp);
0145 
0146     void removeViewSpace(KateViewSpace *viewspace);
0147 
0148 public:
0149     KTextEditor::View *activeView();
0150     KateViewSpace *activeViewSpace();
0151 
0152     /// Returns all non-KTE::View widgets in this viewManager
0153     QWidgetList widgets() const;
0154     bool removeWidget(QWidget *w);
0155     bool activateWidget(QWidget *w);
0156 
0157 private Q_SLOTS:
0158     void slotViewChanged();
0159 
0160     void documentWillBeDeleted(KTextEditor::Document *doc);
0161 
0162     void documentSavedOrUploaded(KTextEditor::Document *document, bool saveAs);
0163 
0164     /**
0165      * This signal is emitted before the documents batch is going to be deleted
0166      *
0167      * note that the batch can be interrupted in the middle and only some
0168      * of the documents may be actually deleted. See documentsDeleted() signal.
0169      *
0170      * @param documents documents we want to delete, may not be deleted
0171      */
0172     void aboutToDeleteDocuments(const QList<KTextEditor::Document *> &documents);
0173 
0174     /**
0175      * This signal is emitted after the documents batch was deleted
0176      *
0177      * This is the batch closing signal for aboutToDeleteDocuments
0178      * @param documents the documents that weren't deleted after all
0179      */
0180     void documentsDeleted(const QList<KTextEditor::Document *> &documents);
0181 
0182     /**
0183      * Read and apply the config for this view manager.
0184      */
0185     void readConfig();
0186 
0187     /**
0188      * Scroll all synched views according to the delta scroll of the given view
0189      * @param view  Other synchronised views will be scrolled according to this
0190      */
0191     void slotScrollSynchedViews(KTextEditor::View *view);
0192 
0193 public Q_SLOTS:
0194     /**
0195      * Splits a KateViewSpace into two in the following steps:
0196      * 1. create a KateSplitter in the parent of the KateViewSpace to be split
0197      * 2. move the to-be-split KateViewSpace to the new splitter
0198      * 3. create new KateViewSpace and added to the new splitter
0199      * 4. create KateView to populate the new viewspace.
0200      * 5. The new KateView is made the active one, because createView() does that.
0201      * If no viewspace is provided, the result of activeViewSpace() is used.
0202      * The orientation of the new splitter is determined by the value of o.
0203      * Note: horizontal splitter means vertically aligned views.
0204      */
0205     void splitViewSpace(KateViewSpace *vs = nullptr, Qt::Orientation o = Qt::Horizontal, bool moveDocument = false);
0206 
0207     /**
0208      * Set the synchronisation of scrolling of multiple views according to user input
0209      */
0210     void slotSynchroniseScrolling(bool checked);
0211 
0212     /**
0213      * Sets the currentView as the scroll-synched view
0214      * and removes synchronisation for the previous view in the same viewSpace
0215      * @param synched scroll for the activeViewSpace() will be set to scrollbar found in this view
0216      */
0217     void slotChangeSynchedView(KTextEditor::View *currentView);
0218 
0219     /**
0220      * Close the view space that contains the given view. If no view was
0221      * given, then the active view space will be closed instead.
0222      */
0223     void closeViewSpace(KTextEditor::View *view = nullptr);
0224 
0225     /**
0226      * @returns true of the two given views share the same view space.
0227      */
0228     bool viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2);
0229 
0230     /**
0231      * activate view for given document
0232      * @param doc document to activate view for
0233      * @param vs the target viewspace in which the doc should be activated. If it is
0234      * null, activeViewSpace will be used instead
0235      */
0236     KTextEditor::View *activateView(DocOrWidget docOrWidget, KateViewSpace *vs = nullptr);
0237 
0238     /** Splits the active viewspace horizontally */
0239     void slotSplitViewSpaceHoriz()
0240     {
0241         splitViewSpace(nullptr, Qt::Vertical);
0242     }
0243 
0244     /** Splits the active viewspace vertically */
0245     void slotSplitViewSpaceVert()
0246     {
0247         splitViewSpace();
0248     }
0249 
0250     /** Splits the active viewspace horizontally and moves the active document to the viewspace below */
0251     void slotSplitViewSpaceHorizMoveDoc()
0252     {
0253         splitViewSpace(nullptr, Qt::Vertical, true);
0254     }
0255 
0256     /** Splits the active viewspace vertically and moves the active document to the right viewspace */
0257     void slotSplitViewSpaceVertMoveDoc()
0258     {
0259         splitViewSpace(nullptr, Qt::Horizontal, true);
0260     }
0261 
0262     /**  moves the splitter according to the key that has been pressed */
0263     void moveSplitter(Qt::Key key, int repeats = 1);
0264 
0265     /** moves the splitter to the right  */
0266     void moveSplitterRight()
0267     {
0268         moveSplitter(Qt::Key_Right);
0269     }
0270 
0271     /** moves the splitter to the left  */
0272     void moveSplitterLeft()
0273     {
0274         moveSplitter(Qt::Key_Left);
0275     }
0276 
0277     /** moves the splitter up  */
0278     void moveSplitterUp()
0279     {
0280         moveSplitter(Qt::Key_Up);
0281     }
0282 
0283     /** moves the splitter down  */
0284     void moveSplitterDown()
0285     {
0286         moveSplitter(Qt::Key_Down);
0287     }
0288 
0289     /** closes the current view space. */
0290     void slotCloseCurrentViewSpace()
0291     {
0292         closeViewSpace();
0293     }
0294 
0295     /** closes every view but the active one */
0296     void slotCloseOtherViews();
0297 
0298     /** hide every view but the active one */
0299     void slotHideOtherViews(bool hideOthers);
0300 
0301     void replugActiveView();
0302 
0303     /**
0304      * Toogle the orientation of current split view
0305      */
0306     void toggleSplitterOrientation();
0307 
0308     /**
0309      * Get a list of all views.
0310      * @return all views sorted by their last use, most recently used first
0311      */
0312     QList<KTextEditor::View *> views() const
0313     {
0314         std::vector<std::pair<KTextEditor::View *, qint64>> sorted;
0315 
0316         // extract into a list
0317         std::transform(m_views.begin(), m_views.end(), std::back_inserter(sorted), [](const std::pair<KTextEditor::View *, ViewData> &p) {
0318             return std::pair<KTextEditor::View *, qint64>{p.first, p.second.lruAge};
0319         });
0320         // sort the views based on lru
0321         std::sort(sorted.begin(), sorted.end(), [](const std::pair<KTextEditor::View *, qint64> &l, const std::pair<KTextEditor::View *, qint64> &r) {
0322             return l.second < r.second;
0323         });
0324 
0325         // extract the views only and return
0326         QList<KTextEditor::View *> ret;
0327         ret.reserve(sorted.size());
0328         std::transform(sorted.begin(), sorted.end(), std::back_inserter(ret), [](const std::pair<KTextEditor::View *, qint64> &p) {
0329             return p.first;
0330         });
0331         return ret;
0332     }
0333 
0334     void onViewSpaceEmptied(KateViewSpace *vs);
0335 
0336     // Returns the number of viewspaces this document is registered in
0337     int viewspaceCountForDoc(KTextEditor::Document *doc) const;
0338     // returns true if @p doc exists only in one viewspace
0339     bool docOnlyInOneViewspace(KTextEditor::Document *doc) const;
0340     // returns true if any viewspace in this viewmanager has its tabbar visible
0341     bool tabsVisible() const;
0342 
0343     void setShowUrlNavBar(bool show);
0344     bool showUrlNavBar() const;
0345 
0346     void hideWelcomeView(KateViewSpace *vs);
0347     void showWelcomeViewOrNewDocumentIfNeeded();
0348     void showWelcomeView();
0349 
0350 private:
0351     KateMainWindow *m_mainWindow;
0352 
0353     QAction *m_splitViewVert = nullptr;
0354     QAction *m_splitViewHoriz = nullptr;
0355     QAction *m_splitViewVertMove = nullptr;
0356     QAction *m_splitViewHorizMove = nullptr;
0357     QAction *m_closeView = nullptr;
0358     QAction *m_closeOtherViews = nullptr;
0359     QAction *m_toggleSplitterOrientation = nullptr;
0360     QAction *m_hideOtherViews = nullptr;
0361     QAction *goNext = nullptr;
0362     QAction *goPrev = nullptr;
0363     QAction *goLeft = nullptr;
0364     QAction *goRight = nullptr;
0365     QAction *goUp = nullptr;
0366     QAction *goDown = nullptr;
0367 
0368     std::vector<KateViewSpace *> m_viewSpaceList;
0369 
0370     bool m_blockViewCreationAndActivation;
0371 
0372     bool m_showUrlNavBar = false;
0373 
0374     int m_splitterIndex = 0; // used during saving splitter config.
0375 
0376     /**
0377      * View meta data
0378      */
0379     class ViewData
0380     {
0381     public:
0382         /**
0383          * lru age of the view
0384          * important: smallest age ==> latest used view
0385          */
0386         qint64 lruAge = 0;
0387     };
0388 
0389     /**
0390      * central storage of all views known in the view manager
0391      * maps the view to meta data
0392      */
0393     std::unordered_map<KTextEditor::View *, ViewData> m_views;
0394 
0395     struct ScrollSynchronisation {
0396         /**
0397          * stores a reference scroll value according to which,
0398          * all others will be set
0399          */
0400         int referenceScrollValue = 0;
0401         /**
0402          * Keeps track of all viewspace having a scroll-synched view
0403          * to help with finding the required view when user changes the tab
0404          */
0405         QHash<KateViewSpace *, KTextEditor::View *> synchedViewSpaces;
0406 
0407         /**
0408          * @struct ScrollBarInfo
0409          * @param scrollBar stores the pointer to the vertical scroll bar of the view
0410          * @param initScrollValue stores the offset position of the scroll bar to the reference scroll value using which the sync will occur
0411          */
0412         struct ScrollBarInfo {
0413             QScrollBar *scrollBar;
0414             int initScrollValue;
0415         };
0416 
0417         /**
0418          * stores the information required for synchronisation of scrolling between multiple split views
0419          * Key - KTextEditor::View* stores the pointer to the corresp view
0420          * Value - ScrollBarInfo stores relevant information regarding the scrollbar of the synched view
0421          */
0422         QHash<KTextEditor::View *, ScrollBarInfo> viewScrollInfo;
0423 
0424         ScrollBarInfo getViewScrollBarInfo(KTextEditor::View *view)
0425         {
0426             const QList<QScrollBar *> scrollBars = view->findChildren<QScrollBar *>();
0427             // Cannot use std::find_if because QList<>::last() is inclusive
0428             ScrollSynchronisation::ScrollBarInfo scrollBarInfo;
0429             scrollBarInfo.scrollBar = [scrollBars] {
0430                 for (auto scrollBar : scrollBars) {
0431                     if (qstrcmp(scrollBar->metaObject()->className(), "KateScrollBar") == 0) {
0432                         return scrollBar;
0433                     }
0434                 }
0435                 Q_ASSERT_X(false,
0436                            "void "
0437                            "KateViewManager::ScrollSynchronisation::getViewScrollBarInfo("
0438                            "KTextEditor::View *currentView)",
0439                            "No QScrollBar* named \"KateScrollBar\" found in the selected View");
0440                 return static_cast<QScrollBar *>(nullptr);
0441             }();
0442             if (scrollBarInfo.scrollBar) {
0443                 scrollBarInfo.initScrollValue = scrollBarInfo.scrollBar->value() - this->referenceScrollValue;
0444             }
0445             return scrollBarInfo;
0446         }
0447     } m_scrollSynchronisation;
0448 
0449     /**
0450      * current minimal age
0451      */
0452     qint64 m_minAge;
0453 
0454     /**
0455      * the view that is ATM merged to the xml gui factory
0456      */
0457     QPointer<KTextEditor::View> m_guiMergedView;
0458 
0459     /**
0460      * last url of open file dialog, used if current document has no valid url
0461      */
0462     QUrl m_lastOpenDialogUrl;
0463 
0464     /**
0465      * SDI mode: open every new document in a new window in most cases
0466      */
0467     bool m_sdiMode = false;
0468 
0469     /**
0470      * was the welcome view already shown?
0471      * ensures it doesn't auto popup multiple times
0472      */
0473     bool m_welcomeViewAlreadyShown = false;
0474 };