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

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 // BEGIN Includes
0010 #include "kateviewmanager.h"
0011 
0012 #include "kateapp.h"
0013 #include "katemainwindow.h"
0014 #include "kateupdatedisabler.h"
0015 #include "welcomeview/welcomeview.h"
0016 #include <kwidgetsaddons_version.h>
0017 
0018 #include <KTextEditor/Attribute>
0019 #include <KTextEditor/Document>
0020 #include <KTextEditor/View>
0021 
0022 #include <KAboutData>
0023 #include <KActionCollection>
0024 #include <KConfig>
0025 #include <KConfigGroup>
0026 #include <KLocalizedString>
0027 #include <KMessageBox>
0028 #include <KSharedConfig>
0029 #include <KToolBar>
0030 #include <KXMLGUIFactory>
0031 
0032 #include <QFileDialog>
0033 #include <QTimer>
0034 
0035 // END Includes
0036 
0037 static constexpr qint64 FileSizeAboveToAskUserIfProceedWithOpen = 10 * 1024 * 1024; // 10MB should suffice
0038 
0039 KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent)
0040     : KateSplitter(parentW)
0041     , m_mainWindow(parent)
0042     , m_blockViewCreationAndActivation(false)
0043     , m_minAge(0)
0044     , m_guiMergedView(nullptr)
0045 {
0046     setObjectName(QStringLiteral("KateViewManager"));
0047 
0048     // start in home if we are not started from a terminal
0049     m_lastOpenDialogUrl =
0050         QUrl::fromLocalFile(KateApp::isInsideTerminal() ? QDir::currentPath() : QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
0051 
0052     // we don't allow full collapse, see bug 366014
0053     setChildrenCollapsible(false);
0054 
0055     // important, set them up, as we use them in other methodes
0056     setupActions();
0057 
0058     KateViewSpace *vs = new KateViewSpace(this, nullptr);
0059     addWidget(vs);
0060 
0061     vs->setActive(true);
0062     m_viewSpaceList.push_back(vs);
0063 
0064     connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotViewChanged);
0065 
0066     /**
0067      * before document is really deleted: cleanup all views!
0068      */
0069     connect(KateApp::self()->documentManager(), &KateDocManager::documentWillBeDeleted, this, &KateViewManager::documentWillBeDeleted);
0070 
0071     /**
0072      * handle document deletion transactions
0073      * disable view creation in between
0074      * afterwards ensure we have views ;)
0075      */
0076     connect(KateApp::self()->documentManager(), &KateDocManager::aboutToDeleteDocuments, this, &KateViewManager::aboutToDeleteDocuments);
0077     connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewManager::documentsDeleted);
0078 
0079     // we want to trigger showing of the welcome view or a new document
0080     showWelcomeViewOrNewDocumentIfNeeded();
0081 
0082     // enforce configured limit
0083     readConfig();
0084 
0085     // handle config changes
0086     connect(KateApp::self(), &KateApp::configurationChanged, this, &KateViewManager::readConfig);
0087 }
0088 
0089 KateViewManager::~KateViewManager()
0090 {
0091     /**
0092      * remove the single client that is registered at the factory, if any
0093      */
0094     if (m_guiMergedView) {
0095         mainWindow()->guiFactory()->removeClient(m_guiMergedView);
0096         m_guiMergedView = nullptr;
0097     }
0098 }
0099 
0100 void KateViewManager::readConfig()
0101 {
0102     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0103     KConfigGroup cgGeneral = KConfigGroup(config, QStringLiteral("General"));
0104     m_sdiMode = cgGeneral.readEntry("SDI Mode", false);
0105 }
0106 
0107 void KateViewManager::slotScrollSynchedViews(KTextEditor::View *view)
0108 {
0109     //  Match current view
0110     QHash<KTextEditor::View *, ScrollSynchronisation::ScrollBarInfo>::const_iterator currentViewInfo = m_scrollSynchronisation.viewScrollInfo.constFind(view);
0111     //  Delta scroll in reference scroll value
0112     m_scrollSynchronisation.referenceScrollValue = currentViewInfo.value().scrollBar->value() - currentViewInfo.value().initScrollValue;
0113     //  Delta scroll in all other views
0114     for (auto otherViewInfo = m_scrollSynchronisation.viewScrollInfo.constBegin(), end = m_scrollSynchronisation.viewScrollInfo.constEnd();
0115          otherViewInfo != end;
0116          ++otherViewInfo) {
0117         if (otherViewInfo != currentViewInfo) {
0118             otherViewInfo.value().scrollBar->setValue(m_scrollSynchronisation.referenceScrollValue + otherViewInfo.value().initScrollValue);
0119         }
0120     }
0121 }
0122 
0123 void KateViewManager::setupActions()
0124 {
0125     /**
0126      * view splitting
0127      */
0128     m_splitViewVert = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert"));
0129     m_splitViewVert->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0130     m_splitViewVert->setText(i18n("Split Ve&rtical"));
0131     m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewVert, Qt::CTRL | Qt::SHIFT | Qt::Key_L);
0132     connect(m_splitViewVert, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVert);
0133 
0134     m_splitViewVert->setWhatsThis(i18n("Split the currently active view vertically into two views."));
0135 
0136     m_splitViewHoriz = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz"));
0137     m_splitViewHoriz->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0138     m_splitViewHoriz->setText(i18n("Split &Horizontal"));
0139     m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewHoriz, Qt::CTRL | Qt::SHIFT | Qt::Key_T);
0140     connect(m_splitViewHoriz, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHoriz);
0141 
0142     m_splitViewHoriz->setWhatsThis(i18n("Split the currently active view horizontally into two views."));
0143 
0144     m_splitViewVertMove = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert_move_doc"));
0145     m_splitViewVertMove->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0146     m_splitViewVertMove->setText(i18n("Move Document to New Vertical Split"));
0147     connect(m_splitViewVertMove, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVertMoveDoc);
0148 
0149     m_splitViewVertMove->setWhatsThis(
0150         i18n("Split the currently active view vertically into two views "
0151              "and move the currently active document to right view."));
0152 
0153     m_splitViewHorizMove = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz_move_doc"));
0154     m_splitViewHorizMove->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom")));
0155     m_splitViewHorizMove->setText(i18n("Move Document to New Horizontal Split"));
0156     connect(m_splitViewHorizMove, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHorizMoveDoc);
0157 
0158     m_splitViewHorizMove->setWhatsThis(
0159         i18n("Split the currently active view horizontally into two views "
0160              "and move the currently active document to view below."));
0161 
0162     m_closeView = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_current_space"));
0163     m_closeView->setIcon(QIcon::fromTheme(QStringLiteral("view-close")));
0164     m_closeView->setText(i18n("Cl&ose Current View"));
0165     m_mainWindow->actionCollection()->setDefaultShortcut(m_closeView, Qt::CTRL | Qt::SHIFT | Qt::Key_R);
0166     connect(m_closeView, &QAction::triggered, this, &KateViewManager::slotCloseCurrentViewSpace, Qt::QueuedConnection);
0167 
0168     m_closeView->setWhatsThis(i18n("Close the currently active split view"));
0169 
0170     m_closeOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_others"));
0171     m_closeOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-close")));
0172     m_closeOtherViews->setText(i18n("Close Inactive Views"));
0173     connect(m_closeOtherViews, &QAction::triggered, this, &KateViewManager::slotCloseOtherViews, Qt::QueuedConnection);
0174 
0175     m_closeOtherViews->setWhatsThis(i18n("Close every view but the active one"));
0176 
0177     m_hideOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_hide_others"));
0178     m_hideOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
0179     m_hideOtherViews->setText(i18n("Hide Inactive Views"));
0180     m_hideOtherViews->setCheckable(true);
0181     connect(m_hideOtherViews, &QAction::triggered, this, &KateViewManager::slotHideOtherViews, Qt::QueuedConnection);
0182 
0183     m_hideOtherViews->setWhatsThis(i18n("Hide every view but the active one"));
0184 
0185     m_toggleSplitterOrientation = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_toggle"));
0186     m_toggleSplitterOrientation->setText(i18n("Toggle Orientation"));
0187     connect(m_toggleSplitterOrientation, &QAction::triggered, this, &KateViewManager::toggleSplitterOrientation, Qt::QueuedConnection);
0188 
0189     m_toggleSplitterOrientation->setWhatsThis(i18n("Toggles the orientation of the current split view"));
0190 
0191     goNext = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_next_split_view"));
0192     goNext->setText(i18n("Next Split View"));
0193     goNext->setIcon(QIcon::fromTheme(QStringLiteral("go-next-view")));
0194     m_mainWindow->actionCollection()->setDefaultShortcut(goNext, Qt::Key_F8);
0195     connect(goNext, &QAction::triggered, this, &KateViewManager::activateNextView);
0196 
0197     goNext->setWhatsThis(i18n("Make the next split view the active one."));
0198 
0199     goPrev = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_prev_split_view"));
0200     goPrev->setText(i18n("Previous Split View"));
0201     goPrev->setIcon(QIcon::fromTheme(QStringLiteral("go-previous-view")));
0202     m_mainWindow->actionCollection()->setDefaultShortcut(goPrev, Qt::SHIFT | Qt::Key_F8);
0203     connect(goPrev, &QAction::triggered, this, &KateViewManager::activatePrevView);
0204 
0205     goPrev->setWhatsThis(i18n("Make the previous split view the active one."));
0206 
0207     goLeft = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_left_split_view"));
0208     goLeft->setText(i18n("Left Split View"));
0209     goLeft->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0210     // m_mainWindow->actionCollection()->setDefaultShortcut(goLeft, Qt::ALT | Qt::Key_Left);
0211     connect(goLeft, &QAction::triggered, this, &KateViewManager::activateLeftView);
0212 
0213     goLeft->setWhatsThis(i18n("Make the split view intuitively on the left the active one."));
0214 
0215     goRight = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_right_split_view"));
0216     goRight->setText(i18n("Right Split View"));
0217     goRight->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0218     // m_mainWindow->actionCollection()->setDefaultShortcut(goRight, Qt::ALT | Qt::Key_Right);
0219     connect(goRight, &QAction::triggered, this, &KateViewManager::activateRightView);
0220 
0221     goRight->setWhatsThis(i18n("Make the split view intuitively on the right the active one."));
0222 
0223     goUp = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_upward_split_view"));
0224     goUp->setText(i18n("Upward Split View"));
0225     goUp->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0226     // m_mainWindow->actionCollection()->setDefaultShortcut(goUp, Qt::ALT | Qt::Key_Up);
0227     connect(goUp, &QAction::triggered, this, &KateViewManager::activateUpwardView);
0228 
0229     goUp->setWhatsThis(i18n("Make the split view intuitively upward the active one."));
0230 
0231     goDown = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_downward_split_view"));
0232     goDown->setText(i18n("Downward Split View"));
0233     goDown->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
0234     // m_mainWindow->actionCollection()->setDefaultShortcut(goDown, Qt::ALT | Qt::Key_Down);
0235     connect(goDown, &QAction::triggered, this, &KateViewManager::activateDownwardView);
0236 
0237     goDown->setWhatsThis(i18n("Make the split view intuitively downward the active one."));
0238 
0239     QAction *a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_right"));
0240     a->setText(i18n("Move Splitter Right"));
0241     connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterRight);
0242 
0243     a->setWhatsThis(i18n("Move the splitter of the current view to the right"));
0244 
0245     a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_left"));
0246     a->setText(i18n("Move Splitter Left"));
0247     connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterLeft);
0248 
0249     a->setWhatsThis(i18n("Move the splitter of the current view to the left"));
0250 
0251     a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_up"));
0252     a->setText(i18n("Move Splitter Up"));
0253     connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterUp);
0254 
0255     a->setWhatsThis(i18n("Move the splitter of the current view up"));
0256 
0257     a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_down"));
0258     a->setText(i18n("Move Splitter Down"));
0259     connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterDown);
0260 
0261     a->setWhatsThis(i18n("Move the splitter of the current view down"));
0262 
0263     a = m_mainWindow->actionCollection()->addAction(QStringLiteral("viewspace_focus_nav_bar"));
0264     a->setText(i18n("Focus Navigation Bar"));
0265     a->setToolTip(i18n("Focus the navigation bar"));
0266     m_mainWindow->actionCollection()->setDefaultShortcut(a, Qt::CTRL | Qt::SHIFT | Qt::Key_Period);
0267     connect(a, &QAction::triggered, this, [this] {
0268         activeViewSpace()->focusNavigationBar();
0269     });
0270 
0271     a = m_mainWindow->actionCollection()->addAction(QStringLiteral("help_welcome_page"));
0272     a->setText(i18n("Welcome Page"));
0273     a->setIcon(qApp->windowIcon());
0274     connect(a, &QAction::triggered, this, [this]() {
0275         showWelcomeView();
0276     });
0277     a->setWhatsThis(i18n("Show the welcome page"));
0278 }
0279 
0280 void KateViewManager::updateViewSpaceActions()
0281 {
0282     const bool multipleViewSpaces = m_viewSpaceList.size() > 1;
0283     m_closeView->setEnabled(multipleViewSpaces);
0284     m_closeOtherViews->setEnabled(multipleViewSpaces);
0285     m_toggleSplitterOrientation->setEnabled(multipleViewSpaces);
0286     m_hideOtherViews->setEnabled(multipleViewSpaces);
0287     goNext->setEnabled(multipleViewSpaces);
0288     goPrev->setEnabled(multipleViewSpaces);
0289 
0290     // only allow to split if we have a view that we could show in the new view space
0291     const bool allowSplit = activeViewSpace();
0292     m_splitViewVert->setEnabled(allowSplit);
0293     m_splitViewHoriz->setEnabled(allowSplit);
0294 
0295     // only allow move if we have more than one document in the current view space
0296     const bool allowMove = allowSplit && (activeViewSpace()->numberOfRegisteredDocuments() > 1);
0297     m_splitViewVertMove->setEnabled(allowMove);
0298     m_splitViewHorizMove->setEnabled(allowMove);
0299 }
0300 
0301 void KateViewManager::slotDocumentNew()
0302 {
0303     // open new window for SDI case
0304     if (m_sdiMode && !m_views.empty()) {
0305         auto mainWindow = m_mainWindow->newWindow();
0306         mainWindow->viewManager()->createView();
0307     } else {
0308         createView();
0309     }
0310 }
0311 
0312 void KateViewManager::slotDocumentOpen()
0313 {
0314     // try to start dialog in useful dir: either dir of current doc or last used one
0315     KTextEditor::View *const cv = activeView();
0316     QUrl startUrl = cv ? cv->document()->url() : QUrl();
0317     if (startUrl.isValid()) {
0318         m_lastOpenDialogUrl = startUrl;
0319     } else {
0320         startUrl = m_lastOpenDialogUrl;
0321     }
0322     // if file is not local, then remove filename from url
0323     QList<QUrl> urls;
0324     if (startUrl.isLocalFile()) {
0325         urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl);
0326     } else {
0327         urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl.adjusted(QUrl::RemoveFilename));
0328     }
0329 
0330     /**
0331      * emit size warning, for local files
0332      */
0333     QString fileListWithTooLargeFiles;
0334     for (const QUrl &url : qAsConst(urls)) {
0335         if (!url.isLocalFile()) {
0336             continue;
0337         }
0338 
0339         const auto size = QFile(url.toLocalFile()).size();
0340         if (size > FileSizeAboveToAskUserIfProceedWithOpen) {
0341             fileListWithTooLargeFiles += QStringLiteral("<li>%1 (%2MB)</li>").arg(url.fileName()).arg(size / 1024 / 1024);
0342         }
0343     }
0344     if (!fileListWithTooLargeFiles.isEmpty()) {
0345         const QString text = i18n(
0346             "<p>You are attempting to open one or more large files:</p><ul>%1</ul><p>Do you want to proceed?</p><p><strong>Beware that kate may stop "
0347             "responding for some time when opening large files.</strong></p>",
0348             fileListWithTooLargeFiles);
0349         const auto ret = KMessageBox::warningTwoActions(this, text, i18n("Opening Large File"), KStandardGuiItem::cont(), KStandardGuiItem::stop());
0350         if (ret == KMessageBox::SecondaryAction) {
0351             return;
0352         }
0353     }
0354 
0355     // activate view of last opened document
0356     KateDocumentInfo docInfo;
0357     docInfo.openedByUser = true;
0358     if (KTextEditor::Document *lastID = openUrls(urls, QString(), docInfo)) {
0359         for (const QUrl &url : qAsConst(urls)) {
0360             m_mainWindow->addRecentOpenedFile(url);
0361         }
0362         activateView(lastID);
0363     }
0364 }
0365 
0366 void KateViewManager::slotDocumentClose(KTextEditor::Document *document)
0367 {
0368     // Close document
0369     if (KateApp::self()->documentManager()->closeDocument(document) && KateApp::self()->documentManager()->documentList().size() == 0) {
0370         // Close window if specified
0371         if (m_mainWindow->modCloseAfterLast()) {
0372             KateApp::self()->shutdownKate(m_mainWindow);
0373         }
0374 
0375         KSharedConfig::Ptr config = KSharedConfig::openConfig();
0376         KConfigGroup cgGeneral = KConfigGroup(config, QStringLiteral("General"));
0377 
0378         // Otherwise, show the welcome view
0379         if (cgGeneral.readEntry("Show welcome view for new window", true)) {
0380             showWelcomeView();
0381         }
0382     }
0383 }
0384 
0385 void KateViewManager::slotDocumentClose()
0386 {
0387     if (auto vs = activeViewSpace()) {
0388         if (auto w = vs->currentWidget()) {
0389             vs->closeTabWithWidget(w);
0390             return;
0391         }
0392 
0393         // no active view, do nothing
0394         auto view = activeView();
0395         if (!view) {
0396             return;
0397         }
0398 
0399         vs->closeDocument(view->document());
0400     }
0401 }
0402 
0403 KTextEditor::Document *
0404 KateViewManager::openUrl(const QUrl &url, const QString &encoding, bool activate, bool ignoreForRecentFiles, const KateDocumentInfo &docInfo)
0405 {
0406     auto doc = openUrls({url}, encoding, docInfo);
0407     if (!doc) {
0408         return nullptr;
0409     }
0410 
0411     if (!ignoreForRecentFiles) {
0412         m_mainWindow->addRecentOpenedFile(doc->url());
0413     }
0414 
0415     if (activate) {
0416         activateView(doc);
0417     }
0418 
0419     return doc;
0420 }
0421 
0422 KTextEditor::Document *KateViewManager::openUrls(const QList<QUrl> &urls, const QString &encoding, const KateDocumentInfo &docInfo)
0423 {
0424     // remember if we have just one view with an unmodified untitled document, if yes, we close that one
0425     // same heuristics we had before in the document manager for the single untitled doc, but this works for multiple main windows
0426     KTextEditor::Document *docToClose = nullptr;
0427     if (m_views.size() == 1) {
0428         auto singleDoc = m_views.begin()->first->document();
0429         if (!singleDoc->isModified() && singleDoc->url().isEmpty() && singleDoc->views().size() == 1) {
0430             docToClose = singleDoc;
0431         }
0432     }
0433 
0434     // try to open all given files, early out if nothing done
0435     auto docs = KateApp::self()->documentManager()->openUrls(urls, encoding, docInfo);
0436     if (docs.empty()) {
0437         return nullptr;
0438     }
0439 
0440     bool first = true;
0441     KTextEditor::Document *lastDocInThisViewManager = nullptr;
0442     for (auto doc : docs) {
0443         // it we have a document to close, we can use this window for the first document even in SDI mode
0444         // try to re-use open views to avoid massive windows spawning, see bug 474775
0445         if (!m_sdiMode || (first && docToClose) || m_views.empty() || activeViewSpace()->hasDocument(doc)) {
0446             // forward to currently active view space
0447             activeViewSpace()->registerDocument(doc);
0448             connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded);
0449             lastDocInThisViewManager = doc;
0450             first = false;
0451             continue;
0452         }
0453 
0454         // open new window for SDI case
0455         auto mainWindow = m_mainWindow->newWindow();
0456         mainWindow->viewManager()->openViewForDoc(doc);
0457         first = false;
0458     }
0459 
0460     // if we had some single untitled doc around, trigger close
0461     // do this delayed to avoid havoc
0462     if (docToClose) {
0463         QTimer::singleShot(0, docToClose, [docToClose]() {
0464             KateApp::self()->documentManager()->closeDocument(docToClose);
0465         });
0466     }
0467 
0468     // return the last document we opened in this view manager if any
0469     return lastDocInThisViewManager;
0470 }
0471 
0472 KTextEditor::View *KateViewManager::openUrlWithView(const QUrl &url, const QString &encoding)
0473 {
0474     KTextEditor::Document *doc = openUrls({url}, encoding);
0475     if (!doc) {
0476         return nullptr;
0477     }
0478 
0479     m_mainWindow->addRecentOpenedFile(doc->url());
0480 
0481     return activateView(doc);
0482 }
0483 
0484 void KateViewManager::openUrl(const QUrl &url)
0485 {
0486     openUrl(url, QString());
0487 }
0488 
0489 void KateViewManager::openUrlOrProject(const QUrl &url)
0490 {
0491     if (!url.isLocalFile()) {
0492         openUrl(url);
0493         return;
0494     }
0495 
0496     const QDir dir(url.toLocalFile());
0497     if (!dir.exists()) {
0498         openUrl(url);
0499         return;
0500     }
0501 
0502     QString text;
0503     if (KateApp::isKWrite()) {
0504         text = i18n("%1 cannot open folders", KAboutData::applicationData().displayName());
0505         KMessageBox::error(mainWindow(), text);
0506         return;
0507     }
0508 
0509     // try to open the folder
0510     static const QString projectPluginId = QStringLiteral("kateprojectplugin");
0511     QObject *projectPluginView = mainWindow()->pluginView(projectPluginId);
0512     if (!projectPluginView) {
0513         // try to find and enable the Projects plugin
0514         KatePluginList &pluginList = KateApp::self()->pluginManager()->pluginList();
0515         KatePluginList::iterator i = std::find_if(pluginList.begin(), pluginList.end(), [](const KatePluginInfo &pluginInfo) {
0516             return pluginInfo.metaData.pluginId() == projectPluginId;
0517         });
0518 
0519         QString text;
0520         if (i == pluginList.end()) {
0521             text = i18n("The plugin required to open folders was not found");
0522             KMessageBox::error(mainWindow(), text);
0523             return;
0524         }
0525 
0526         KatePluginInfo &projectPluginInfo = *i;
0527         text = i18n("In order to open folders, the <b>%1</b> plugin must be enabled. Enable it?", projectPluginInfo.metaData.name());
0528         if (KMessageBox::questionTwoActions(mainWindow(),
0529                                             text,
0530                                             i18nc("@title:window", "Open Folder"),
0531                                             KGuiItem(i18nc("@action:button", "Enable"), QStringLiteral("dialog-ok")),
0532                                             KStandardGuiItem::cancel())
0533             == KMessageBox::SecondaryAction) {
0534             return;
0535         }
0536 
0537         if (!KateApp::self()->pluginManager()->loadPlugin(&projectPluginInfo)) {
0538             text = i18n("Failed to enable <b>%1</b> plugin", projectPluginInfo.metaData.name());
0539             KMessageBox::error(mainWindow(), text);
0540             return;
0541         }
0542 
0543         KateApp::self()->pluginManager()->enablePluginGUI(&projectPluginInfo);
0544         projectPluginView = mainWindow()->pluginView(projectPluginId);
0545     }
0546 
0547     Q_ASSERT(projectPluginView);
0548     QMetaObject::invokeMethod(projectPluginView, "openDirectoryOrProject", Q_ARG(const QDir &, dir));
0549 }
0550 
0551 KTextEditor::View *KateViewManager::openViewForDoc(KTextEditor::Document *doc)
0552 {
0553     // forward to currently active view space
0554     auto viewspace = activeViewSpace();
0555     viewspace->registerDocument(doc);
0556     connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded);
0557 
0558     return activateView(doc, viewspace);
0559 }
0560 
0561 void KateViewManager::addPositionToHistory(const QUrl &url, KTextEditor::Cursor pos)
0562 {
0563     if (KateViewSpace *avs = activeViewSpace()) {
0564         avs->addPositionToHistory(url, pos, /* calledExternally: */ true);
0565     }
0566 }
0567 
0568 KateMainWindow *KateViewManager::mainWindow()
0569 {
0570     return m_mainWindow;
0571 }
0572 
0573 void KateViewManager::aboutToDeleteDocuments(const QList<KTextEditor::Document *> &)
0574 {
0575     /**
0576      * block view creation until the transaction is done
0577      * this shall not stack!
0578      */
0579     Q_ASSERT(!m_blockViewCreationAndActivation);
0580     m_blockViewCreationAndActivation = true;
0581 
0582     /**
0583      * disable updates hard (we can't use KateUpdateDisabler here, we have delayed signal
0584      */
0585     mainWindow()->setUpdatesEnabled(false);
0586 }
0587 
0588 void KateViewManager::documentsDeleted(const QList<KTextEditor::Document *> &)
0589 {
0590     /**
0591      * again allow view creation
0592      */
0593     m_blockViewCreationAndActivation = false;
0594 
0595     /**
0596      * ensure we don't end up with empty tabs in some view spaces
0597      * we did block view creation, re-trigger it
0598      */
0599     auto viewspace = activeViewSpace();
0600     for (auto vs : m_viewSpaceList) {
0601         if (vs != viewspace) {
0602             vs->ensureViewForCurrentTab();
0603         }
0604     }
0605     setActiveSpace(viewspace);
0606     // Do it last for active view space so it remains active
0607     activeViewSpace()->ensureViewForCurrentTab();
0608 
0609     /**
0610      * reactivate will ensure we really merge up the GUI again
0611      * this might be missed as above we had m_blockViewCreationAndActivation set to true
0612      * see bug 426605, no view XMLGUI stuff merged after tab close
0613      */
0614     replugActiveView();
0615 
0616     // trigger action update
0617     updateViewSpaceActions();
0618 
0619     /**
0620      * enable updates hard (we can't use KateUpdateDisabler here, we have delayed signal
0621      */
0622     mainWindow()->setUpdatesEnabled(true);
0623 }
0624 
0625 void KateViewManager::documentSavedOrUploaded(KTextEditor::Document *doc, bool)
0626 {
0627     m_mainWindow->addRecentOpenedFile(doc->url());
0628 }
0629 
0630 KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateViewSpace *vs)
0631 {
0632     if (m_blockViewCreationAndActivation) {
0633         return nullptr;
0634     }
0635 
0636     // create doc
0637     if (!doc) {
0638         doc = KateApp::self()->documentManager()->createDoc();
0639     }
0640 
0641     /**
0642      * ensure the initial welcome view vanishes as soon as we have some real view!
0643      */
0644     hideWelcomeView(vs);
0645 
0646     /**
0647      * create view, registers its XML gui itself
0648      * pass the view the correct main window
0649      */
0650     KTextEditor::View *view = (vs ? vs : activeViewSpace())->createView(doc);
0651 
0652     /**
0653      * remember this view, active == false, min age set
0654      */
0655     m_views[view].lruAge = m_minAge--;
0656 
0657     // disable settings dialog action
0658     delete view->actionCollection()->action(QStringLiteral("set_confdlg"));
0659 
0660     // clang-format off
0661     connect(view, SIGNAL(dropEventPass(QDropEvent*)), mainWindow(), SLOT(slotDropEvent(QDropEvent*)));
0662     // clang-format on
0663     connect(view, &KTextEditor::View::focusIn, this, &KateViewManager::activateSpace);
0664 
0665     viewCreated(view);
0666 
0667     if (!vs) {
0668         activateView(view);
0669     }
0670 
0671     updateViewSpaceActions();
0672 
0673     /**
0674      * connect to signal here so we can handle post-load
0675      * set cursor position for this view if we need to
0676      * do this after the view is properly registered, else we might trigger assertions
0677      * about unknown views in the connected lambdas
0678      */
0679     KateDocumentInfo *docInfo = KateApp::self()->documentManager()->documentInfo(doc);
0680     if (docInfo->startCursor.isValid()) {
0681         KTextEditor::Cursor c = docInfo->startCursor; // Was a cursor position requested?
0682         docInfo->startCursor = KTextEditor::Cursor::invalid(); // do this only once
0683 
0684         if (!doc->url().isLocalFile()) {
0685             std::shared_ptr<QMetaObject::Connection> conn(new QMetaObject::Connection());
0686             auto handler = [view, conn, c](KTextEditor::Document *) {
0687                 QObject::disconnect(*conn);
0688                 view->setCursorPosition(c);
0689             };
0690 
0691             *conn = connect(doc, &KTextEditor::Document::textChanged, view, handler);
0692         } else if (c.isValid()) {
0693             view->setCursorPosition(c);
0694         }
0695     }
0696 
0697     return view;
0698 }
0699 
0700 bool KateViewManager::deleteView(KTextEditor::View *view)
0701 {
0702     if (!view) {
0703         return true;
0704     }
0705 
0706     KateViewSpace *viewspace = static_cast<KateViewSpace *>(view->parent()->parent());
0707     viewspace->removeView(view);
0708     updateViewSpaceActions();
0709 
0710     /**
0711      * deregister if needed
0712      */
0713     if (m_guiMergedView == view) {
0714         mainWindow()->guiFactory()->removeClient(m_guiMergedView);
0715         m_guiMergedView = nullptr;
0716     }
0717 
0718     // remove view from mapping and memory !!
0719     m_views.erase(view);
0720     delete view;
0721     return true;
0722 }
0723 
0724 KateViewSpace *KateViewManager::activeViewSpace()
0725 {
0726     for (auto vs : m_viewSpaceList) {
0727         if (vs->isActiveSpace()) {
0728             return vs;
0729         }
0730     }
0731 
0732     // none active, so use the first we grab
0733     if (!m_viewSpaceList.empty()) {
0734         m_viewSpaceList.front()->setActive(true);
0735         return m_viewSpaceList.front();
0736     }
0737 
0738     Q_ASSERT(false);
0739     return nullptr;
0740 }
0741 
0742 QWidgetList KateViewManager::widgets() const
0743 {
0744     QWidgetList widgets;
0745     for (auto *vs : m_viewSpaceList) {
0746         widgets << vs->widgets();
0747     }
0748     return widgets;
0749 }
0750 
0751 bool KateViewManager::removeWidget(QWidget *w)
0752 {
0753     for (auto *vs : m_viewSpaceList) {
0754         if (vs->closeTabWithWidget(w)) {
0755             return true;
0756         }
0757     }
0758     return false;
0759 }
0760 
0761 bool KateViewManager::activateWidget(QWidget *w)
0762 {
0763     for (auto *vs : m_viewSpaceList) {
0764         if (vs->activateWidget(w)) {
0765             return true;
0766         }
0767     }
0768     return false;
0769 }
0770 
0771 KTextEditor::View *KateViewManager::activeView()
0772 {
0773     return m_guiMergedView;
0774 }
0775 
0776 void KateViewManager::setActiveSpace(KateViewSpace *vs)
0777 {
0778     if (auto activeSpace = activeViewSpace()) {
0779         activeSpace->setActive(false);
0780     }
0781 
0782     vs->setActive(true);
0783 
0784     // signal update history buttons in mainWindow
0785     Q_EMIT historyBackEnabled(vs->isHistoryBackEnabled());
0786     Q_EMIT historyForwardEnabled(vs->isHistoryForwardEnabled());
0787 }
0788 
0789 void KateViewManager::activateSpace(KTextEditor::View *v)
0790 {
0791     if (!v) {
0792         return;
0793     }
0794 
0795     KateViewSpace *vs = static_cast<KateViewSpace *>(v->parent()->parent());
0796 
0797     if (!vs->isActiveSpace()) {
0798         setActiveSpace(vs);
0799         activateView(v);
0800     }
0801 }
0802 
0803 void KateViewManager::replugActiveView()
0804 {
0805     if (m_guiMergedView) {
0806         mainWindow()->guiFactory()->removeClient(m_guiMergedView);
0807         mainWindow()->guiFactory()->addClient(m_guiMergedView);
0808     }
0809 }
0810 
0811 void KateViewManager::activateView(KTextEditor::View *view)
0812 {
0813     if (!view) {
0814         if (m_guiMergedView) {
0815             mainWindow()->guiFactory()->removeClient(m_guiMergedView);
0816             m_guiMergedView = nullptr;
0817         }
0818         Q_EMIT viewChanged(nullptr);
0819         updateViewSpaceActions();
0820         return;
0821     }
0822 
0823     if (!m_guiMergedView || m_guiMergedView != view) {
0824         // avoid flicker
0825         KateUpdateDisabler disableUpdates(mainWindow());
0826 
0827         if (!activeViewSpace()->showView(view)) {
0828             // since it wasn't found, give'em a new one
0829             createView(view->document());
0830             return;
0831         }
0832 
0833         bool toolbarVisible = mainWindow()->toolBar()->isVisible();
0834         if (toolbarVisible) {
0835             mainWindow()->toolBar()->hide(); // hide to avoid toolbar flickering
0836         }
0837 
0838         if (m_guiMergedView) {
0839             mainWindow()->guiFactory()->removeClient(m_guiMergedView);
0840             m_guiMergedView = nullptr;
0841         }
0842 
0843         if (!m_blockViewCreationAndActivation) {
0844             mainWindow()->guiFactory()->addClient(view);
0845             m_guiMergedView = view;
0846         }
0847 
0848         if (toolbarVisible) {
0849             mainWindow()->toolBar()->show();
0850         }
0851 
0852         auto it = m_views.find(view);
0853         Q_ASSERT(it != m_views.end());
0854         ViewData &viewData = it->second;
0855         // remember age of this view
0856         viewData.lruAge = m_minAge--;
0857 
0858         Q_EMIT viewChanged(view);
0859 
0860         updateViewSpaceActions();
0861     }
0862 }
0863 
0864 KTextEditor::View *KateViewManager::activateView(DocOrWidget docOrWidget, KateViewSpace *vs)
0865 {
0866     // activate existing view if possible
0867     auto viewspace = vs ? vs : activeViewSpace();
0868     if (viewspace->showView(docOrWidget)) {
0869         // Only activateView if viewspace is active
0870         if (viewspace == activeViewSpace()) {
0871             // This will be null if currentView is not a KTE::View
0872             activateView(viewspace->currentView());
0873             return activeView();
0874         }
0875         return viewspace->currentView();
0876     }
0877 
0878     // create new view otherwise
0879     Q_ASSERT(docOrWidget.doc());
0880     auto v = createView(docOrWidget.doc(), vs);
0881     // If the requesting viewspace is the activeViewSpace, we activate the view
0882     if (vs == activeViewSpace()) {
0883         activateView(v);
0884     }
0885     return activeView();
0886 }
0887 
0888 void KateViewManager::slotViewChanged()
0889 {
0890     auto view = activeView();
0891     if (view && !view->hasFocus()) {
0892         view->setFocus();
0893     }
0894 }
0895 
0896 void KateViewManager::activateNextView()
0897 {
0898     auto it = std::find(m_viewSpaceList.begin(), m_viewSpaceList.end(), activeViewSpace());
0899     ++it;
0900 
0901     if (it == m_viewSpaceList.end()) {
0902         it = m_viewSpaceList.begin();
0903     }
0904 
0905     setActiveSpace(*it);
0906     activateView((*it)->currentView());
0907 }
0908 
0909 void KateViewManager::activatePrevView()
0910 {
0911     auto it = std::find(m_viewSpaceList.begin(), m_viewSpaceList.end(), activeViewSpace());
0912     if (it == m_viewSpaceList.begin()) {
0913         it = --m_viewSpaceList.end();
0914     } else {
0915         --it;
0916     }
0917 
0918     setActiveSpace(*it);
0919     activateView((*it)->currentView());
0920 }
0921 
0922 void KateViewManager::activateLeftView()
0923 {
0924     activateIntuitiveNeighborView(Qt::Horizontal, 0);
0925 }
0926 
0927 void KateViewManager::activateRightView()
0928 {
0929     activateIntuitiveNeighborView(Qt::Horizontal, 1);
0930 }
0931 
0932 void KateViewManager::activateUpwardView()
0933 {
0934     activateIntuitiveNeighborView(Qt::Vertical, 0);
0935 }
0936 
0937 void KateViewManager::activateDownwardView()
0938 {
0939     activateIntuitiveNeighborView(Qt::Vertical, 1);
0940 }
0941 
0942 void KateViewManager::activateIntuitiveNeighborView(Qt::Orientation o, int dir)
0943 {
0944     KateViewSpace *currentViewSpace = activeViewSpace();
0945     if (!currentViewSpace) {
0946         return;
0947     }
0948     KateViewSpace *target = findIntuitiveNeighborView(qobject_cast<KateSplitter *>(currentViewSpace->parent()), currentViewSpace, o, dir);
0949     if (target) {
0950         setActiveSpace(target);
0951         activateView(target->currentView());
0952     }
0953 }
0954 
0955 KateViewSpace *KateViewManager::findIntuitiveNeighborView(KateSplitter *splitter, QWidget *widget, Qt::Orientation o, int dir)
0956 {
0957     Q_ASSERT(dir == 0 || dir == 1); // 0 for right or up ; 1 for left or down
0958     if (!splitter) {
0959         return nullptr;
0960     }
0961     if (splitter->count() == 1) { // root view space, nothing to do
0962         return nullptr;
0963     }
0964     int widgetIndex = splitter->indexOf(widget);
0965     // if the orientation is the same as the desired move, maybe we can move in the current splitter
0966     if (splitter->orientation() == o && ((dir == 1 && widgetIndex == 0) || (dir == 0 && widgetIndex == 1))) {
0967         // make our move
0968         QWidget *target = splitter->widget(dir);
0969         // try to see if we are next to a view space and move to it
0970         KateViewSpace *targetViewSpace = qobject_cast<KateViewSpace *>(target);
0971         if (targetViewSpace) {
0972             return targetViewSpace;
0973         }
0974         // otherwise we found a splitter and need to go down the splitter tree to the desired view space
0975         KateSplitter *targetSplitter = qobject_cast<KateSplitter *>(target);
0976         // we already made our move so we need to go down to the opposite direction :
0977         // e.g.: [ active | [ [ x | y ] | z ] ] going right we find a splitter and want to go to x
0978         int childIndex = 1 - dir;
0979         QWidget *targetChild = nullptr;
0980         while (targetSplitter) {
0981             if (targetSplitter->orientation() == o) { // as long as the orientation is the same we move down in the desired direction
0982                 targetChild = targetSplitter->widget(childIndex);
0983             } else { // otherwise we need to find in which of the two child to go based on the cursor position
0984                 QPoint cursorCoord = activeView()->mapToGlobal(activeView()->cursorPositionCoordinates());
0985                 QPoint targetSplitterCoord = targetSplitter->widget(0)->mapToGlobal(QPoint(0, 0));
0986                 if ((o == Qt::Horizontal && (targetSplitterCoord.y() + targetSplitter->sizes()[0] > cursorCoord.y()))
0987                     || (o == Qt::Vertical && (targetSplitterCoord.x() + targetSplitter->sizes()[0] > cursorCoord.x()))) {
0988                     targetChild = targetSplitter->widget(0);
0989                 } else {
0990                     targetChild = targetSplitter->widget(1);
0991                 }
0992             }
0993             // if we found a view space we're done!
0994             targetViewSpace = qobject_cast<KateViewSpace *>(targetChild);
0995             if (targetViewSpace) {
0996                 return targetViewSpace;
0997             }
0998             // otherwise continue
0999             targetSplitter = qobject_cast<KateSplitter *>(targetChild);
1000         }
1001     }
1002     // otherwise try to go up in the splitter tree
1003     return findIntuitiveNeighborView(qobject_cast<KateSplitter *>(splitter->parent()), splitter, o, dir);
1004 }
1005 
1006 void KateViewManager::documentWillBeDeleted(KTextEditor::Document *doc)
1007 {
1008     /**
1009      * collect all views of that document that belong to this manager
1010      */
1011     QList<KTextEditor::View *> closeList;
1012     const auto views = doc->views();
1013     for (KTextEditor::View *v : views) {
1014         if (m_views.find(v) != m_views.end()) {
1015             closeList.append(v);
1016         }
1017     }
1018 
1019     while (!closeList.isEmpty()) {
1020         deleteView(closeList.takeFirst());
1021     }
1022 }
1023 
1024 void KateViewManager::closeView(KTextEditor::View *view)
1025 {
1026     // we can only close views that are in our viewspaces
1027     if (!view || !view->parent() || !view->parent()->parent()) {
1028         return;
1029     }
1030 
1031     // get the viewspace if possible and close the document there => should close the passed view
1032     // will close the full document, too, if last
1033     auto viewSpace = qobject_cast<KateViewSpace *>(view->parent()->parent());
1034     if (viewSpace) {
1035         viewSpace->closeDocument(view->document());
1036     }
1037 }
1038 
1039 void KateViewManager::moveViewToViewSpace(KateViewSpace *dest, KateViewSpace *src, DocOrWidget docOrWidget)
1040 {
1041     // We always have an active view at this point which is what we are moving
1042     Q_ASSERT(activeView() || src->currentWidget());
1043 
1044     // Are we trying to drop into some other mainWindow of current app session?
1045     // shouldn't happen, but just a safe guard
1046     if (src->viewManager() != dest->viewManager()) {
1047         return;
1048     }
1049 
1050     QWidget *view = src->takeView(docOrWidget);
1051     if (!view) {
1052         qWarning() << Q_FUNC_INFO << "Unexpected null view when trying to drag the view to a different viewspace" << docOrWidget.qobject();
1053         return;
1054     }
1055 
1056     dest->addView(view);
1057     setActiveSpace(dest);
1058     auto kteView = qobject_cast<KTextEditor::View *>(view);
1059     if (kteView) {
1060         activateView(kteView->document(), dest);
1061     } else {
1062         activateView(view, dest);
1063     }
1064 }
1065 
1066 void KateViewManager::splitViewSpace(KateViewSpace *vs, // = 0
1067                                      Qt::Orientation o,
1068                                      bool moveDocument) // = Qt::Horizontal
1069 {
1070     // emergency: fallback to activeViewSpace, and if still invalid, abort
1071     if (!vs) {
1072         vs = activeViewSpace();
1073     }
1074     if (!vs) {
1075         return;
1076     }
1077     if (moveDocument && vs->numberOfRegisteredDocuments() <= 1) {
1078         return;
1079     }
1080 
1081     // if we have no current view, splitting makes ATM no sense, as we have
1082     // nothing to put into the new viewspace
1083     if (!vs->currentView() && !vs->currentWidget()) {
1084         return;
1085     }
1086 
1087     // get current splitter, and abort if null
1088     KateSplitter *currentSplitter = qobject_cast<KateSplitter *>(vs->parent());
1089     if (!currentSplitter) {
1090         return;
1091     }
1092 
1093     // avoid flicker
1094     KateUpdateDisabler disableUpdates(mainWindow());
1095 
1096     // index where to insert new splitter/viewspace
1097     const int index = currentSplitter->indexOf(vs);
1098 
1099     // create new viewspace
1100     KateViewSpace *vsNew = new KateViewSpace(this);
1101 
1102     // only 1 children -> we are the root container. So simply set the orientation
1103     // and add the new view space, then correct the sizes to 50%:50%
1104     if (currentSplitter->count() == 1) {
1105         if (currentSplitter->orientation() != o) {
1106             currentSplitter->setOrientation(o);
1107         }
1108         QList<int> sizes = currentSplitter->sizes();
1109         sizes << (sizes.first() - currentSplitter->handleWidth()) / 2;
1110         sizes[0] = sizes[1];
1111         currentSplitter->insertWidget(index + 1, vsNew);
1112         currentSplitter->setSizes(sizes);
1113     } else {
1114         // create a new KateSplitter and replace vs with the splitter. vs and newVS are
1115         // the new children of the new KateSplitter
1116         KateSplitter *newContainer = new KateSplitter(o);
1117 
1118         // we don't allow full collapse, see bug 366014
1119         newContainer->setChildrenCollapsible(false);
1120 
1121         QList<int> currentSizes = currentSplitter->sizes();
1122 
1123         newContainer->addWidget(vs);
1124         newContainer->addWidget(vsNew);
1125         currentSplitter->insertWidget(index, newContainer);
1126         newContainer->show();
1127 
1128         // fix sizes of children of old and new splitter
1129         currentSplitter->setSizes(currentSizes);
1130         QList<int> newSizes = newContainer->sizes();
1131         newSizes[0] = (newSizes[0] + newSizes[1] - newContainer->handleWidth()) / 2;
1132         newSizes[1] = newSizes[0];
1133         newContainer->setSizes(newSizes);
1134     }
1135 
1136     m_viewSpaceList.push_back(vsNew);
1137     activeViewSpace()->setActive(false);
1138     vsNew->setActive(true);
1139     vsNew->show();
1140 
1141     if (moveDocument) {
1142         if (vs->currentWidget()) {
1143             moveViewToViewSpace(vsNew, vs, vs->currentWidget());
1144         } else {
1145             moveViewToViewSpace(vsNew, vs, vs->currentView()->document());
1146         }
1147     } else {
1148         auto v = vs->currentView();
1149         createView(v ? v->document() : nullptr);
1150     }
1151 
1152     updateViewSpaceActions();
1153 }
1154 
1155 void KateViewManager::slotSynchroniseScrolling(bool checked)
1156 {
1157     KTextEditor::View *userSelectedView = activeViewSpace()->currentView();
1158     auto newSynchedViewInfo = m_scrollSynchronisation.viewScrollInfo.constFind(userSelectedView);
1159     // If already added to sync, then desync
1160     if (newSynchedViewInfo != m_scrollSynchronisation.viewScrollInfo.constEnd()) {
1161         Q_ASSERT(!checked);
1162         disconnect(userSelectedView, &KTextEditor::View::verticalScrollPositionChanged, this, &KateViewManager::slotScrollSynchedViews);
1163         m_scrollSynchronisation.viewScrollInfo.remove(newSynchedViewInfo.key());
1164         m_scrollSynchronisation.synchedViewSpaces.remove(activeViewSpace());
1165         if (m_scrollSynchronisation.synchedViewSpaces.isEmpty()) {
1166             disconnect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotChangeSynchedView);
1167         }
1168         return;
1169     }
1170     // If invalid, do nothing
1171     if (!userSelectedView) {
1172         return;
1173     }
1174     // If not already added to sync, then add to sync
1175     Q_ASSERT(checked);
1176     ScrollSynchronisation::ScrollBarInfo scrollBarInfo = m_scrollSynchronisation.getViewScrollBarInfo(userSelectedView);
1177     if (!scrollBarInfo.scrollBar) {
1178         // This case should normally not occur
1179         // but if it is being used, implement:
1180         // Uncheck m_toggleSynchronisedScrolling
1181         return;
1182     }
1183     // Connect scrolling to slotSynchedView
1184     connect(userSelectedView, &KTextEditor::View::verticalScrollPositionChanged, this, &KateViewManager::slotScrollSynchedViews, Qt::UniqueConnection);
1185     m_scrollSynchronisation.viewScrollInfo.insert(userSelectedView, scrollBarInfo);
1186     // Remove from group of views, when view is deleted
1187     connect(userSelectedView, &KTextEditor::View::destroyed, this, [this](QObject *obj) {
1188         m_scrollSynchronisation.viewScrollInfo.remove(static_cast<KTextEditor::View *>(obj));
1189     });
1190     // To cater for multiple views per viewspace
1191     Q_ASSERT_X(!m_scrollSynchronisation.synchedViewSpaces.contains(activeViewSpace()),
1192                "void KateViewManager::slotSynchroniseScrolling()",
1193                "Somehow m_synchedViewSpaces already contains the viewspace currently selected");
1194     m_scrollSynchronisation.synchedViewSpaces.insert(activeViewSpace(), activeViewSpace()->currentView());
1195     // Change stored view whenever user changes tab
1196     connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotChangeSynchedView, Qt::UniqueConnection);
1197     // Remove from group of viewSpaces when the viewSpace is deleted
1198     connect(
1199         activeViewSpace(),
1200         &KateViewSpace::destroyed,
1201         this,
1202         [this](QObject *obj) {
1203             if (m_scrollSynchronisation.synchedViewSpaces.contains(static_cast<KateViewSpace *>(obj))) {
1204                 m_scrollSynchronisation.synchedViewSpaces.remove(static_cast<KateViewSpace *>(obj));
1205             }
1206         },
1207         Qt::QueuedConnection);
1208 }
1209 
1210 void KateViewManager::slotChangeSynchedView(KTextEditor::View *currentView)
1211 {
1212     if (m_scrollSynchronisation.synchedViewSpaces.constFind(activeViewSpace()) == m_scrollSynchronisation.synchedViewSpaces.constEnd()) {
1213         // If currently selected view is not a part of scroll-synched viewspace do nothing
1214         return;
1215     }
1216     // Remove previous view from scroll-sync
1217     KTextEditor::View *previousView = m_scrollSynchronisation.synchedViewSpaces.constFind(activeViewSpace()).value();
1218     if (previousView == currentView) {
1219         // If this function has been called just because the activeViewSpace was changed then don't
1220         // waste time
1221         return;
1222     }
1223     disconnect(previousView, &KTextEditor::View::verticalScrollPositionChanged, this, &KateViewManager::slotScrollSynchedViews);
1224     m_scrollSynchronisation.viewScrollInfo.remove(previousView);
1225     // Add the currentView to scroll-sync
1226     ScrollSynchronisation::ScrollBarInfo scrollBarInfo = m_scrollSynchronisation.getViewScrollBarInfo(currentView);
1227     if (!scrollBarInfo.scrollBar) {
1228         return;
1229     }
1230     // Connect scrolling to slotSynchedView
1231     connect(currentView, &KTextEditor::View::verticalScrollPositionChanged, this, &KateViewManager::slotScrollSynchedViews, Qt::UniqueConnection);
1232     m_scrollSynchronisation.viewScrollInfo.insert(currentView, scrollBarInfo);
1233     // Remove from group of views, when view is deleted
1234     connect(currentView, &KTextEditor::View::destroyed, this, [this](QObject *obj) {
1235         m_scrollSynchronisation.viewScrollInfo.remove(static_cast<KTextEditor::View *>(obj));
1236     });
1237     // Add the new view alongside the viewspace already in list
1238     m_scrollSynchronisation.synchedViewSpaces[activeViewSpace()] = currentView;
1239 }
1240 
1241 void KateViewManager::closeViewSpace(KTextEditor::View *view)
1242 {
1243     KateViewSpace *space;
1244 
1245     if (view) {
1246         space = static_cast<KateViewSpace *>(view->parent()->parent());
1247     } else {
1248         space = activeViewSpace();
1249     }
1250     removeViewSpace(space);
1251 }
1252 
1253 void KateViewManager::toggleSplitterOrientation()
1254 {
1255     KateViewSpace *vs = activeViewSpace();
1256     if (!vs) {
1257         return;
1258     }
1259 
1260     // get current splitter, and abort if null
1261     KateSplitter *currentSplitter = qobject_cast<KateSplitter *>(vs->parent());
1262     if (!currentSplitter || (currentSplitter->count() == 1)) {
1263         return;
1264     }
1265 
1266     // avoid flicker
1267     KateUpdateDisabler disableUpdates(mainWindow());
1268 
1269     // toggle orientation
1270     if (currentSplitter->orientation() == Qt::Horizontal) {
1271         currentSplitter->setOrientation(Qt::Vertical);
1272     } else {
1273         currentSplitter->setOrientation(Qt::Horizontal);
1274     }
1275 }
1276 
1277 void KateViewManager::onViewSpaceEmptied(KateViewSpace *vs)
1278 {
1279     // If we have more than one viewspaces and this viewspace
1280     // got empty, remove it
1281     if (m_viewSpaceList.size() > 1) {
1282         removeViewSpace(vs);
1283         return;
1284     }
1285 
1286     // we want to trigger showing of the welcome view or a new document
1287     showWelcomeViewOrNewDocumentIfNeeded();
1288 }
1289 
1290 int KateViewManager::viewspaceCountForDoc(KTextEditor::Document *doc) const
1291 {
1292     return std::count_if(m_viewSpaceList.begin(), m_viewSpaceList.end(), [doc](KateViewSpace *vs) {
1293         return vs->hasDocument(doc);
1294     });
1295 }
1296 
1297 bool KateViewManager::tabsVisible() const
1298 {
1299     return std::count_if(m_viewSpaceList.begin(), m_viewSpaceList.end(), [](KateViewSpace *vs) {
1300         return vs->tabCount() > 1;
1301     });
1302 }
1303 
1304 bool KateViewManager::docOnlyInOneViewspace(KTextEditor::Document *doc) const
1305 {
1306     return (viewspaceCountForDoc(doc) == 1) && !KateApp::self()->documentVisibleInOtherWindows(doc, m_mainWindow);
1307 }
1308 
1309 void KateViewManager::setShowUrlNavBar(bool show)
1310 {
1311     if (show != m_showUrlNavBar) {
1312         m_showUrlNavBar = show;
1313         Q_EMIT showUrlNavBarChanged(show);
1314     }
1315 }
1316 
1317 bool KateViewManager::showUrlNavBar() const
1318 {
1319     return m_showUrlNavBar;
1320 }
1321 
1322 bool KateViewManager::viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2)
1323 {
1324     if (!view1 || !view2) {
1325         return false;
1326     }
1327     if (m_viewSpaceList.size() == 1) {
1328         return true;
1329     }
1330 
1331     KateViewSpace *vs1 = static_cast<KateViewSpace *>(view1->parent()->parent());
1332     KateViewSpace *vs2 = static_cast<KateViewSpace *>(view2->parent()->parent());
1333     return vs1 && (vs1 == vs2);
1334 }
1335 
1336 void KateViewManager::removeViewSpace(KateViewSpace *viewspace)
1337 {
1338     // abort if viewspace is 0
1339     if (!viewspace) {
1340         return;
1341     }
1342 
1343     // abort if this is the last viewspace
1344     if (m_viewSpaceList.size() < 2) {
1345         return;
1346     }
1347 
1348     // check if any widget has "shouldClose" method to block closing
1349     // otherwise we don't need to prompt the user
1350     auto viewspaceCanClose = [viewspace] {
1351         const auto widgets = viewspace->widgets();
1352         for (auto w : widgets) {
1353             bool shouldClose = true; // by default all widgets are closable
1354             QMetaObject::invokeMethod(w, "shouldClose", Q_RETURN_ARG(bool, shouldClose));
1355             if (!shouldClose) {
1356                 return false;
1357             }
1358         }
1359         return true;
1360     };
1361 
1362     const bool viewspaceHasWidgets = viewspace->hasWidgets();
1363     if (viewspaceHasWidgets && !viewspaceCanClose()) {
1364         return;
1365     }
1366 
1367     // get current splitter
1368     KateSplitter *currentSplitter = qobject_cast<KateSplitter *>(viewspace->parent());
1369 
1370     // no splitter found, bah
1371     if (!currentSplitter) {
1372         return;
1373     }
1374 
1375     //
1376     // 1. get LRU document list from current viewspace
1377     // 2. delete current view space
1378     // 3. add LRU documents from deleted viewspace to new active viewspace
1379     //
1380 
1381     // backup list of known documents to have tab buttons
1382     const auto documentList = viewspace->documentList();
1383 
1384     // avoid flicker
1385     KateUpdateDisabler disableUpdates(mainWindow());
1386 
1387     // delete views of the viewspace
1388     while (viewspace->currentView()) {
1389         deleteView(viewspace->currentView());
1390     }
1391 
1392     if (viewspaceHasWidgets) {
1393         // emit widget removed
1394         const auto widgets = viewspace->widgets();
1395         for (auto w : widgets) {
1396             Q_EMIT mainWindow()->widgetRemoved(w);
1397         }
1398     }
1399 
1400     // cu viewspace
1401     m_viewSpaceList.erase(std::remove(m_viewSpaceList.begin(), m_viewSpaceList.end(), viewspace), m_viewSpaceList.end());
1402     delete viewspace;
1403 
1404     // at this point, the splitter has exactly 1 child
1405     Q_ASSERT(currentSplitter->count() == 1);
1406 
1407     // if we are not the root splitter, move the child one level up and delete
1408     // the splitter then.
1409     if (currentSplitter != this) {
1410         // get parent splitter
1411         KateSplitter *parentSplitter = qobject_cast<KateSplitter *>(currentSplitter->parent());
1412 
1413         // only do magic if found ;)
1414         if (parentSplitter) {
1415             int index = parentSplitter->indexOf(currentSplitter);
1416 
1417             // save current splitter size, as the removal of currentSplitter looses the info
1418             QList<int> parentSizes = parentSplitter->sizes();
1419             parentSplitter->insertWidget(index, currentSplitter->widget(0));
1420             delete currentSplitter;
1421             // now restore the sizes again
1422             parentSplitter->setSizes(parentSizes);
1423         }
1424     } else if (KateSplitter *splitter = qobject_cast<KateSplitter *>(currentSplitter->widget(0))) {
1425         // we are the root splitter and have only one child, which is also a splitter
1426         // -> eliminate the redundant splitter and move both children into the root splitter
1427         QList<int> sizes = splitter->sizes();
1428         // adapt splitter orientation to the splitter we are about to delete
1429         currentSplitter->setOrientation(splitter->orientation());
1430         currentSplitter->addWidget(splitter->widget(0));
1431         currentSplitter->addWidget(splitter->widget(0));
1432         delete splitter;
1433         currentSplitter->setSizes(sizes);
1434     }
1435 
1436     // add the known documents to the current view space to not loose tab buttons
1437     auto avs = activeViewSpace();
1438     for (auto doc : documentList) {
1439         // TODO: unify handling
1440         if (doc.doc()) {
1441             avs->registerDocument(doc.doc());
1442         }
1443         // We skip widgets because they will be destroyed when viewspace closes
1444         // avs->addWidgetAsTab(doc.widget());
1445     }
1446 
1447     // find the view that is now active.
1448     KTextEditor::View *v = avs->currentView();
1449     if (v) {
1450         activateView(v);
1451     }
1452 
1453     updateViewSpaceActions();
1454 
1455     Q_EMIT viewChanged(v);
1456 }
1457 
1458 void KateViewManager::slotCloseOtherViews()
1459 {
1460     // avoid flicker
1461     KateUpdateDisabler disableUpdates(mainWindow());
1462 
1463     const KateViewSpace *active = activeViewSpace();
1464     const auto viewSpaces = m_viewSpaceList;
1465     for (KateViewSpace *v : viewSpaces) {
1466         if (active != v) {
1467             removeViewSpace(v);
1468         }
1469     }
1470 }
1471 
1472 void KateViewManager::slotHideOtherViews(bool hideOthers)
1473 {
1474     // avoid flicker
1475     KateUpdateDisabler disableUpdates(mainWindow());
1476 
1477     const KateViewSpace *active = activeViewSpace();
1478     for (KateViewSpace *v : m_viewSpaceList) {
1479         if (active != v) {
1480             v->setVisible(!hideOthers);
1481         }
1482     }
1483 
1484     // disable the split actions, if we are in single-view-mode
1485     m_splitViewVert->setDisabled(hideOthers);
1486     m_splitViewHoriz->setDisabled(hideOthers);
1487     m_closeView->setDisabled(hideOthers);
1488     m_closeOtherViews->setDisabled(hideOthers);
1489     m_toggleSplitterOrientation->setDisabled(hideOthers);
1490 }
1491 
1492 /**
1493  * session config functions
1494  */
1495 void KateViewManager::saveViewConfiguration(KConfigGroup &config)
1496 {
1497     // set Active ViewSpace to 0, just in case there is none active (would be
1498     // strange) and config somehow has previous value set
1499     config.writeEntry("Active ViewSpace", 0);
1500 
1501     m_splitterIndex = 0;
1502     saveSplitterConfig(this, config.config(), config.name());
1503 }
1504 
1505 void KateViewManager::restoreViewConfiguration(const KConfigGroup &config)
1506 {
1507     /**
1508      * remove the single client that is registered at the factory, if any
1509      */
1510     if (m_guiMergedView) {
1511         mainWindow()->guiFactory()->removeClient(m_guiMergedView);
1512         m_guiMergedView = nullptr;
1513     }
1514 
1515     /**
1516      * delete viewspaces, they will delete the views
1517      */
1518     qDeleteAll(m_viewSpaceList);
1519     m_viewSpaceList.clear();
1520 
1521     /**
1522      * delete mapping of now deleted views
1523      */
1524     m_views.clear();
1525 
1526     /**
1527      * kill all previous existing sub-splitters, just to be sure
1528      * e.g. important if one restores a config in an existing window with some splitters
1529      */
1530     while (count() > 0) {
1531         delete widget(0);
1532     }
1533 
1534     // reset lru history, too!
1535     m_minAge = 0;
1536 
1537     // start recursion for the root splitter (Splitter 0)
1538     restoreSplitter(config.config(), config.name() + QStringLiteral("-Splitter 0"), this, config.name());
1539 
1540     // finally, make the correct view from the last session active
1541     size_t lastViewSpace = config.readEntry("Active ViewSpace", 0);
1542     if (lastViewSpace > m_viewSpaceList.size()) {
1543         lastViewSpace = 0;
1544     }
1545     if (lastViewSpace < m_viewSpaceList.size()) {
1546         setActiveSpace(m_viewSpaceList.at(lastViewSpace));
1547         // activate correct view (wish #195435, #188764)
1548         activateView(m_viewSpaceList.at(lastViewSpace)->currentView());
1549         // give view the focus to avoid focus stealing by toolviews / plugins
1550         m_viewSpaceList.at(lastViewSpace)->setFocus();
1551     }
1552 
1553     // emergency
1554     if (m_viewSpaceList.empty()) {
1555         // kill bad children
1556         while (count()) {
1557             delete widget(0);
1558         }
1559 
1560         KateViewSpace *vs = new KateViewSpace(this, nullptr);
1561         addWidget(vs);
1562         vs->setActive(true);
1563         m_viewSpaceList.push_back(vs);
1564     } else {
1565         // remove any empty viewspaces
1566         // use a copy, m_viewSpaceList wil be modified
1567         const auto copy = m_viewSpaceList;
1568         for (auto *vs : copy) {
1569             if (vs->documentList().isEmpty()) {
1570                 onViewSpaceEmptied(vs);
1571             }
1572         }
1573     }
1574 
1575     updateViewSpaceActions();
1576 
1577     // we want to trigger showing of the welcome view or a new document
1578     showWelcomeViewOrNewDocumentIfNeeded();
1579 }
1580 
1581 QString KateViewManager::saveSplitterConfig(KateSplitter *s, KConfigBase *configBase, const QString &viewConfGrp)
1582 {
1583     /**
1584      * avoid to export invisible view spaces
1585      * else they will stick around for ever in sessions
1586      * bug 358266 - code initially done during load
1587      * bug 381433 - moved code to save
1588      */
1589 
1590     /**
1591      * create new splitter name, might be not used
1592      */
1593     const auto grp = QString(viewConfGrp + QStringLiteral("-Splitter %1")).arg(m_splitterIndex);
1594     ++m_splitterIndex;
1595 
1596     // a KateSplitter has two children, either KateSplitters and/or KateViewSpaces
1597     // special case: root splitter might have only one child (just for info)
1598     QStringList childList;
1599     const auto sizes = s->sizes();
1600     for (int idx = 0; idx < s->count(); ++idx) {
1601         // skip empty sized invisible ones, if not last one, we need one thing at least
1602         if ((sizes[idx] == 0) && ((idx + 1 < s->count()) || !childList.empty())) {
1603             continue;
1604         }
1605 
1606         // For KateViewSpaces, ask them to save the file list.
1607         auto obj = s->widget(idx);
1608         if (auto kvs = qobject_cast<KateViewSpace *>(obj)) {
1609             auto it = std::find(m_viewSpaceList.begin(), m_viewSpaceList.end(), kvs);
1610             int idx = (int)std::distance(m_viewSpaceList.begin(), it);
1611 
1612             childList.append(QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(idx));
1613             kvs->saveConfig(configBase, idx, viewConfGrp);
1614             // save active viewspace
1615             if (kvs->isActiveSpace()) {
1616                 KConfigGroup viewConfGroup(configBase, viewConfGrp);
1617                 viewConfGroup.writeEntry("Active ViewSpace", idx);
1618             }
1619         }
1620         // for KateSplitters, recurse
1621         else if (auto splitter = qobject_cast<KateSplitter *>(obj)) {
1622             childList.append(saveSplitterConfig(splitter, configBase, viewConfGrp));
1623         }
1624     }
1625 
1626     // if only one thing, skip splitter config export, if not top splitter
1627     if ((s != this) && (childList.size() == 1)) {
1628         return childList.at(0);
1629     }
1630 
1631     // Save sizes, orient, children for this splitter
1632     KConfigGroup config(configBase, grp);
1633     config.writeEntry("Sizes", sizes);
1634     config.writeEntry("Orientation", int(s->orientation()));
1635     config.writeEntry("Children", childList);
1636     return grp;
1637 }
1638 
1639 void KateViewManager::restoreSplitter(const KConfigBase *configBase, const QString &group, KateSplitter *parent, const QString &viewConfGrp)
1640 {
1641     KConfigGroup config(configBase, group);
1642 
1643     parent->setOrientation(static_cast<Qt::Orientation>(config.readEntry("Orientation", int(Qt::Horizontal))));
1644 
1645     const QStringList children = config.readEntry("Children", QStringList());
1646     for (const auto &str : children) {
1647         // for a viewspace, create it and open all documents therein.
1648         if (str.startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) {
1649             KateViewSpace *vs = new KateViewSpace(this, nullptr);
1650             m_viewSpaceList.push_back(vs);
1651             // make active so that the view created in restoreConfig has this
1652             // new view space as parent.
1653             setActiveSpace(vs);
1654 
1655             parent->addWidget(vs);
1656             vs->restoreConfig(this, configBase, str);
1657             vs->show();
1658         } else {
1659             // for a splitter, recurse
1660             auto newContainer = new KateSplitter(parent);
1661 
1662             // we don't allow full collapse, see bug 366014
1663             newContainer->setChildrenCollapsible(false);
1664 
1665             restoreSplitter(configBase, str, newContainer, viewConfGrp);
1666         }
1667     }
1668 
1669     // set sizes
1670     parent->setSizes(config.readEntry("Sizes", QList<int>()));
1671     parent->show();
1672 }
1673 
1674 void KateViewManager::moveSplitter(Qt::Key key, int repeats)
1675 {
1676     if (repeats < 1) {
1677         return;
1678     }
1679 
1680     KateViewSpace *vs = activeViewSpace();
1681     if (!vs) {
1682         return;
1683     }
1684 
1685     KateSplitter *currentSplitter = qobject_cast<KateSplitter *>(vs->parent());
1686 
1687     if (!currentSplitter) {
1688         return;
1689     }
1690     if (currentSplitter->count() == 1) {
1691         return;
1692     }
1693 
1694     int move = 4 * repeats;
1695     // try to use font height in pixel to move splitter
1696     {
1697         KTextEditor::Attribute::Ptr attrib(vs->currentView()->defaultStyleAttribute(KSyntaxHighlighting::Theme::TextStyle::Normal));
1698         QFontMetrics fm(attrib->font());
1699         move = fm.height() * repeats;
1700     }
1701 
1702     QWidget *currentWidget = static_cast<QWidget *>(vs);
1703     bool foundSplitter = false;
1704 
1705     // find correct splitter to be moved
1706     while (currentSplitter && currentSplitter->count() != 1) {
1707         if (currentSplitter->orientation() == Qt::Horizontal && (key == Qt::Key_Right || key == Qt::Key_Left)) {
1708             foundSplitter = true;
1709         }
1710 
1711         if (currentSplitter->orientation() == Qt::Vertical && (key == Qt::Key_Up || key == Qt::Key_Down)) {
1712             foundSplitter = true;
1713         }
1714 
1715         // if the views within the current splitter can be resized, resize them
1716         if (foundSplitter) {
1717             QList<int> currentSizes = currentSplitter->sizes();
1718             int index = currentSplitter->indexOf(currentWidget);
1719 
1720             if ((index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) || (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down))) {
1721                 currentSizes[index] -= move;
1722             }
1723 
1724             if ((index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) || (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up))) {
1725                 currentSizes[index] += move;
1726             }
1727 
1728             if (index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) {
1729                 currentSizes[index + 1] -= move;
1730             }
1731 
1732             if (index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) {
1733                 currentSizes[index + 1] += move;
1734             }
1735 
1736             if (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down)) {
1737                 currentSizes[index - 1] += move;
1738             }
1739 
1740             if (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up)) {
1741                 currentSizes[index - 1] -= move;
1742             }
1743 
1744             currentSplitter->setSizes(currentSizes);
1745             break;
1746         }
1747 
1748         currentWidget = static_cast<QWidget *>(currentSplitter);
1749         // the parent of the current splitter will become the current splitter
1750         currentSplitter = qobject_cast<KateSplitter *>(currentSplitter->parent());
1751     }
1752 }
1753 
1754 void KateViewManager::hideWelcomeView(KateViewSpace *vs)
1755 {
1756     if (auto welcomeView = qobject_cast<WelcomeView *>(vs ? vs : activeViewSpace()->currentWidget())) {
1757         QTimer::singleShot(0, welcomeView, [this, welcomeView]() {
1758             mainWindow()->removeWidget(welcomeView);
1759         });
1760     }
1761 }
1762 
1763 void KateViewManager::showWelcomeViewOrNewDocumentIfNeeded()
1764 {
1765     // delay the action
1766     QTimer::singleShot(0, this, [this]() {
1767         // we really want to show up only if nothing is in the current view space
1768         // this guard versus double invocation of this function, too
1769         if (activeViewSpace() && (activeViewSpace()->currentView() || activeViewSpace()->currentWidget()))
1770             return;
1771 
1772         // the user can decide: welcome page or a new untitled document for a new window?
1773         KSharedConfig::Ptr config = KSharedConfig::openConfig();
1774         KConfigGroup cgGeneral = KConfigGroup(config, QStringLiteral("General"));
1775         if (!m_welcomeViewAlreadyShown && cgGeneral.readEntry("Show welcome view for new window", true)) {
1776             showWelcomeView();
1777         } else {
1778             // don't use slotDocumentNew() to avoid dummy new windows in SDI mode
1779             createView();
1780         }
1781         triggerActiveViewFocus();
1782     });
1783 }
1784 
1785 void KateViewManager::showWelcomeView()
1786 {
1787     // Find and activate existing welcome view
1788     if (activeViewSpace()) {
1789         const auto widgets = activeViewSpace()->widgets();
1790         for (const auto &widget : widgets) {
1791             if (qobject_cast<WelcomeView *>(widget)) {
1792                 activateWidget(widget);
1793                 return;
1794             }
1795         }
1796     }
1797 
1798     auto welcomeView = new WelcomeView(this);
1799     mainWindow()->addWidget(welcomeView);
1800     m_welcomeViewAlreadyShown = true;
1801 }
1802 
1803 void KateViewManager::triggerActiveViewFocus()
1804 {
1805     // delay the focus, needed e.g. on startup
1806     QTimer::singleShot(0, this, [this]() {
1807         // no active view space, bad!
1808         if (!activeViewSpace()) {
1809             return;
1810         }
1811 
1812         // else: try to focus either the current active view or widget
1813         if (auto v = activeViewSpace()->currentView()) {
1814             v->setFocus();
1815             return;
1816         }
1817         if (auto v = activeViewSpace()->currentWidget()) {
1818             v->setFocus();
1819             return;
1820         }
1821     });
1822 }
1823 
1824 #include "moc_kateviewmanager.cpp"