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, 2005 Anders Lund <anders.lund@lund.tdcadsl.dk>
0005 
0006    SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 #include "kateviewspace.h"
0009 
0010 #include "diffwidget.h"
0011 #include "kateapp.h"
0012 #include "katedocmanager.h"
0013 #include "katefileactions.h"
0014 #include "katemainwindow.h"
0015 #include "katesessionmanager.h"
0016 #include "kateupdatedisabler.h"
0017 #include "kateurlbar.h"
0018 #include "kateviewmanager.h"
0019 #include "tabmimedata.h"
0020 
0021 #include <KAcceleratorManager>
0022 #include <KActionCollection>
0023 #include <KConfigGroup>
0024 #include <KLocalizedString>
0025 #include <KSharedConfig>
0026 #include <KTextEditor/Editor>
0027 
0028 #include <QHelpEvent>
0029 #include <QMenu>
0030 #include <QMessageBox>
0031 #include <QRubberBand>
0032 #include <QScopedValueRollback>
0033 #include <QStackedWidget>
0034 #include <QTimer>
0035 #include <QToolButton>
0036 #include <QToolTip>
0037 #include <QWhatsThis>
0038 
0039 // BEGIN KateViewSpace
0040 KateViewSpace::KateViewSpace(KateViewManager *viewManager, QWidget *parent, const char *name)
0041     : QWidget(parent)
0042     , m_viewManager(viewManager)
0043     , m_isActiveSpace(false)
0044 {
0045     setObjectName(QString::fromLatin1(name));
0046     QVBoxLayout *layout = new QVBoxLayout(this);
0047     layout->setSpacing(0);
0048     layout->setContentsMargins(0, 0, 0, 0);
0049 
0050     // BEGIN tab bar
0051     QHBoxLayout *hLayout = new QHBoxLayout();
0052     hLayout->setSpacing(0);
0053     hLayout->setContentsMargins(0, 0, 0, 0);
0054 
0055     // add left <-> right history buttons
0056     m_historyBack = new QToolButton(this);
0057     m_historyBack->setToolTip(i18n("Go to Previous Location"));
0058     m_historyBack->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0059     m_historyBack->setAutoRaise(true);
0060     KAcceleratorManager::setNoAccel(m_historyBack);
0061     hLayout->addWidget(m_historyBack);
0062     connect(m_historyBack, &QToolButton::clicked, this, [this] {
0063         goBack();
0064     });
0065     m_historyBack->setEnabled(false);
0066 
0067     m_historyForward = new QToolButton(this);
0068     m_historyForward->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0069     m_historyForward->setToolTip(i18n("Go to Next Location"));
0070     m_historyForward->setAutoRaise(true);
0071     KAcceleratorManager::setNoAccel(m_historyForward);
0072     hLayout->addWidget(m_historyForward);
0073     connect(m_historyForward, &QToolButton::clicked, this, [this] {
0074         goForward();
0075     });
0076     m_historyForward->setEnabled(false);
0077 
0078     // add tab bar
0079     m_tabBar = new KateTabBar(this);
0080     connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0081     connect(m_tabBar, &KateTabBar::tabCloseRequested, this, &KateViewSpace::closeTabRequest, Qt::QueuedConnection);
0082     connect(m_tabBar, &KateTabBar::contextMenuRequest, this, &KateViewSpace::showContextMenu, Qt::QueuedConnection);
0083     connect(m_tabBar, &KateTabBar::newTabRequested, this, &KateViewSpace::createNewDocument);
0084     connect(m_tabBar, SIGNAL(activateViewSpaceRequested()), this, SLOT(makeActive()));
0085     hLayout->addWidget(m_tabBar);
0086 
0087     // add quick open
0088     m_quickOpen = new QToolButton(this);
0089     m_quickOpen->setAutoRaise(true);
0090     KAcceleratorManager::setNoAccel(m_quickOpen);
0091     hLayout->addWidget(m_quickOpen);
0092 
0093     // forward tab bar quick open action to global quick open action
0094     QAction *bridge = new QAction(QIcon::fromTheme(QStringLiteral("quickopen")), i18nc("indicator for more documents", "+%1", 100), this);
0095     m_quickOpen->setDefaultAction(bridge);
0096     QAction *quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open"));
0097     Q_ASSERT(quickOpen);
0098     bridge->setToolTip(quickOpen->toolTip());
0099     bridge->setWhatsThis(i18n("Click here to switch to the Quick Open view."));
0100     connect(bridge, &QAction::triggered, quickOpen, &QAction::trigger);
0101 
0102     // add vertical split view space
0103     m_split = new QToolButton(this);
0104     m_split->setAutoRaise(true);
0105     m_split->setPopupMode(QToolButton::InstantPopup);
0106     m_split->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
0107     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_vert")));
0108     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_horiz")));
0109     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_vert_move_doc")));
0110     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_horiz_move_doc")));
0111     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_current_space")));
0112     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_others")));
0113     m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_hide_others")));
0114     m_split->setToolTip(i18n("Split View"));
0115     m_split->setWhatsThis(i18n("Control view space splitting"));
0116 
0117     //  add action for Synchronous scrolling. This needs to be one per ViewSpace instance
0118     m_toggleSynchronisedScrolling = new KToggleAction(i18n("S&ynchronize Scrolling"), this);
0119     m_toggleSynchronisedScrolling->setIcon(QIcon::fromTheme(QStringLiteral("view-sync")));
0120     m_toggleSynchronisedScrolling->setWhatsThis(i18n("Synchronize Scrolling of this split-view with other synchronized split-views"));
0121     m_split->addAction(m_toggleSynchronisedScrolling);
0122     connect(m_toggleSynchronisedScrolling, &QAction::triggered, m_viewManager, &KateViewManager::slotSynchroniseScrolling);
0123 
0124     m_split->installEventFilter(this); // on click, active this view space
0125     hLayout->addWidget(m_split);
0126 
0127     layout->addLayout(hLayout);
0128 
0129     // on click, active this view space, register this late, we need m_quickOpen and Co. inside the filter
0130     m_historyBack->installEventFilter(this);
0131     m_historyForward->installEventFilter(this);
0132     m_quickOpen->installEventFilter(this);
0133     m_split->installEventFilter(this);
0134     // END tab bar
0135 
0136     m_urlBar = new KateUrlBar(this);
0137 
0138     // like other editors, we try to re-use documents, of not modified
0139     connect(m_urlBar, &KateUrlBar::openUrlRequested, this, [this](const QUrl &url, Qt::KeyboardModifiers mod) {
0140         // try if reuse of view make sense
0141         const bool shiftPress = mod == Qt::ShiftModifier;
0142         if (!shiftPress && !KateApp::self()->documentManager()->findDocument(url)) {
0143             if (auto activeView = m_viewManager->activeView()) {
0144                 KateDocumentInfo *info = KateApp::self()->documentManager()->documentInfo(activeView->document());
0145                 if (info && !info->wasDocumentEverModified) {
0146                     activeView->document()->openUrl(url);
0147                     return;
0148                 }
0149             }
0150         }
0151 
0152         // default: open a new document or switch there
0153         m_viewManager->openUrl(url);
0154     });
0155     layout->addWidget(m_urlBar);
0156 
0157     stack = new QStackedWidget(this);
0158     stack->setFocus();
0159     stack->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding));
0160     layout->addWidget(stack);
0161 
0162     m_group.clear();
0163 
0164     // connect signal to hide/show statusbar
0165     connect(m_viewManager->mainWindow(), &KateMainWindow::tabBarToggled, this, &KateViewSpace::tabBarToggled);
0166     connect(m_viewManager, &KateViewManager::showUrlNavBarChanged, this, &KateViewSpace::urlBarToggled);
0167 
0168     connect(this, &KateViewSpace::viewSpaceEmptied, m_viewManager, &KateViewManager::onViewSpaceEmptied);
0169 
0170     // we accept drops (tabs from other viewspaces / windows)
0171     setAcceptDrops(true);
0172 
0173     m_layout.tabBarLayout = hLayout;
0174     m_layout.mainLayout = layout;
0175 
0176     // apply config, will init tabbar
0177     readConfig();
0178 
0179     // handle config changes
0180     connect(KateApp::self(), &KateApp::configurationChanged, this, &KateViewSpace::readConfig);
0181 
0182     // ensure we show/hide tabbar if needed
0183     connect(m_viewManager, &KateViewManager::viewCreated, this, &KateViewSpace::updateTabBar);
0184     connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewSpace::updateTabBar);
0185     connect(m_viewManager->mainWindow(), &KateMainWindow::widgetAdded, this, &KateViewSpace::updateTabBar);
0186     connect(m_viewManager->mainWindow(), &KateMainWindow::widgetRemoved, this, &KateViewSpace::updateTabBar);
0187 }
0188 
0189 KateViewSpace::~KateViewSpace() = default;
0190 
0191 void KateViewSpace::readConfig()
0192 {
0193     // get tab config
0194     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0195     KConfigGroup cgGeneral = KConfigGroup(config, QStringLiteral("General"));
0196     m_autoHideTabBar = cgGeneral.readEntry("Auto Hide Tabs", KateApp::isKWrite());
0197 
0198     // init tab bar
0199     tabBarToggled();
0200 }
0201 
0202 bool KateViewSpace::eventFilter(QObject *obj, QEvent *event)
0203 {
0204     QToolButton *button = qobject_cast<QToolButton *>(obj);
0205 
0206     // quick open button: show tool tip with shortcut
0207     if (button == m_quickOpen && event->type() == QEvent::ToolTip) {
0208         QHelpEvent *e = static_cast<QHelpEvent *>(event);
0209         QAction *quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open"));
0210         Q_ASSERT(quickOpen);
0211         QToolTip::showText(e->globalPos(), button->toolTip() + QStringLiteral(" (%1)").arg(quickOpen->shortcut().toString()), button);
0212         return true;
0213     }
0214 
0215     // quick open button: What's This
0216     if (button == m_quickOpen && event->type() == QEvent::WhatsThis) {
0217         QHelpEvent *e = static_cast<QHelpEvent *>(event);
0218         const int hiddenDocs = hiddenDocuments();
0219         QString helpText = (hiddenDocs == 0)
0220             ? i18n("Click here to switch to the Quick Open view.")
0221             : i18np("Currently, there is one more document open. To see all open documents, switch to the Quick Open view by clicking here.",
0222                     "Currently, there are %1 more documents open. To see all open documents, switch to the Quick Open view by clicking here.",
0223                     hiddenDocs);
0224         QWhatsThis::showText(e->globalPos(), helpText, m_quickOpen);
0225         return true;
0226     }
0227 
0228     // on mouse press on view space bar tool buttons: activate this space
0229     if (button && !isActiveSpace() && event->type() == QEvent::MouseButtonPress) {
0230         m_viewManager->setActiveSpace(this);
0231         if (currentView()) {
0232             m_viewManager->activateView(currentView()->document(), this);
0233         }
0234     }
0235     return false;
0236 }
0237 
0238 void KateViewSpace::updateTabBar()
0239 {
0240     if (!m_autoHideTabBar || !m_viewManager->mainWindow()->showTabBar()) {
0241         return;
0242     }
0243 
0244     // toggle hide/show if state changed
0245     if (m_viewManager->tabsVisible() != m_tabBar->isVisible()) {
0246         tabBarToggled();
0247     }
0248 }
0249 
0250 void KateViewSpace::tabBarToggled()
0251 {
0252     KateUpdateDisabler updatesDisabled(m_viewManager->mainWindow());
0253 
0254     bool show = m_viewManager->mainWindow()->showTabBar();
0255 
0256     // we might want to auto hide if just one document is open
0257     if (show && m_autoHideTabBar) {
0258         show = tabCount() > 1 || m_viewManager->tabsVisible();
0259     }
0260 
0261     const bool urlBarVisible = m_viewManager->showUrlNavBar();
0262 
0263     bool showButtons = true;
0264 
0265     m_tabBar->setVisible(show);
0266     if (show) {
0267         m_layout.tabBarLayout->removeWidget(m_urlBar);
0268         int afterTabLayout = m_layout.mainLayout->indexOf(m_layout.tabBarLayout);
0269         m_layout.mainLayout->insertWidget(afterTabLayout + 1, m_urlBar);
0270         showButtons = true;
0271     } else if (!show && !urlBarVisible) {
0272         // both hidden, hide buttons as well
0273         showButtons = false;
0274     } else if (!show && urlBarVisible) {
0275         // UrlBar is still visible. Move it up to take the place of tabbar
0276         int insertAt = m_layout.tabBarLayout->indexOf(m_historyForward) + 1;
0277         m_layout.tabBarLayout->insertWidget(insertAt, m_urlBar);
0278         showButtons = true;
0279     }
0280 
0281     m_historyBack->setVisible(showButtons);
0282     m_historyForward->setVisible(showButtons);
0283     m_split->setVisible(showButtons);
0284     m_quickOpen->setVisible(showButtons);
0285 }
0286 
0287 void KateViewSpace::urlBarToggled(bool show)
0288 {
0289     const bool tabBarVisible = m_viewManager->mainWindow()->showTabBar();
0290     bool showButtons = true;
0291     if (!show && !tabBarVisible) {
0292         // Tab bar was already hidden, now url bar is also hidden
0293         // so hide the buttons as well
0294         showButtons = false;
0295     } else if (show && !tabBarVisible) {
0296         // Tabbar is hidden, but we now need to show url bar
0297         // Move it up to take the place of tabbar
0298         int insertAt = m_layout.tabBarLayout->indexOf(m_historyForward) + 1;
0299         m_layout.tabBarLayout->insertWidget(insertAt, m_urlBar);
0300         // make sure buttons are visible
0301         showButtons = true;
0302     }
0303 
0304     m_historyBack->setVisible(showButtons);
0305     m_historyForward->setVisible(showButtons);
0306     m_split->setVisible(showButtons);
0307     m_quickOpen->setVisible(showButtons);
0308 }
0309 
0310 KTextEditor::View *KateViewSpace::createView(KTextEditor::Document *doc)
0311 {
0312     // do nothing if we already have some view
0313     if (const auto it = m_docToView.find(doc); it != m_docToView.end()) {
0314         return it->second;
0315     }
0316 
0317     /**
0318      * Create a fresh view
0319      */
0320     KTextEditor::View *v = doc->createView(stack, m_viewManager->mainWindow()->wrapper());
0321 
0322     // our framework relies on the existence of the status bar
0323     v->setStatusBarEnabled(true);
0324 
0325     // restore the config of this view if possible
0326     if (!m_group.isEmpty()) {
0327         QString fn = v->document()->url().toString();
0328         if (!fn.isEmpty()) {
0329             QString vgroup = QStringLiteral("%1 %2").arg(m_group, fn);
0330 
0331             KateSession::Ptr as = KateApp::self()->sessionManager()->activeSession();
0332             if (as->config() && as->config()->hasGroup(vgroup)) {
0333                 KConfigGroup cg(as->config(), vgroup);
0334                 v->readSessionConfig(cg);
0335             }
0336         }
0337     }
0338 
0339     {
0340         // If this doc already has a view then the cursor position
0341         // might be different from what is in config, try to
0342         // restore position from one of the views
0343         const auto views = doc->views();
0344         if (views.size() > 1) {
0345             for (auto view : views) {
0346                 if (view != v) {
0347                     v->setCursorPosition(view->cursorPosition());
0348                     break;
0349                 }
0350             }
0351         }
0352     }
0353 
0354     connect(v, &KTextEditor::View::cursorPositionChanged, this, [this](KTextEditor::View *view, const KTextEditor::Cursor &newPosition) {
0355         if (!m_blockAddHistory && view && view->document())
0356             addPositionToHistory(view->document()->url(), newPosition);
0357     });
0358 
0359     // register document
0360     registerDocument(doc);
0361 
0362     // view shall still be not registered
0363     Q_ASSERT(m_docToView.find(doc) == m_docToView.end());
0364 
0365     // insert View into stack
0366     stack->addWidget(v);
0367     m_docToView[doc] = v;
0368 
0369     return v;
0370 }
0371 
0372 void KateViewSpace::removeView(KTextEditor::View *v)
0373 {
0374     // remove view mappings, if we have none, this document didn't have any view here at all, just ignore it
0375     const auto it = m_docToView.find(v->document());
0376     if (it == m_docToView.end()) {
0377         return;
0378     }
0379     m_docToView.erase(it);
0380 
0381     // ...and now: remove from view space
0382     stack->removeWidget(v);
0383 
0384     // Remove the doc now!
0385     // Why do this now? Because otherwise it messes up the LRU
0386     // because we get two "currentChanged" signals
0387     // - First signal when we "showView" below
0388     // - Second comes soon after when v->document() is destroyed
0389     // Handling (blocking) both signals here is necessary
0390     m_tabBar->blockSignals(true);
0391     documentDestroyed(v->document());
0392     m_tabBar->blockSignals(false);
0393 
0394     // switch to most recently used rather than letting stack choose one
0395     // (last element could well be v->document() being removed here)
0396     for (auto rit = m_registeredDocuments.rbegin(); rit != m_registeredDocuments.rend(); ++rit) {
0397         if (auto doc = rit->doc()) {
0398             m_viewManager->activateView(doc, this);
0399             break;
0400         } else if (auto wid = rit->widget()) {
0401             activateWidget(wid);
0402             break;
0403         }
0404     }
0405 
0406     // Did we just loose our last doc?
0407     // Send a delayed signal. Delay is important as we want to kill
0408     // the viewspace after the view transfer was done
0409     if (m_tabBar->count() == 0 && m_registeredDocuments.empty()) {
0410         QMetaObject::invokeMethod(
0411             this,
0412             [this] {
0413                 Q_EMIT viewSpaceEmptied(this);
0414             },
0415             Qt::QueuedConnection);
0416     }
0417 }
0418 
0419 void KateViewSpace::saveViewConfig(KTextEditor::View *v)
0420 {
0421     if (!v) {
0422         return;
0423     }
0424     const auto url = v->document()->url();
0425     if (!url.isEmpty() && !m_group.isEmpty()) {
0426         const QString vgroup = QStringLiteral("%1 %2").arg(m_group, url.toString());
0427         const KateSession::Ptr as = KateApp::self()->sessionManager()->activeSession();
0428         if (as->config()) {
0429             KConfigGroup viewGroup(as->config(), vgroup);
0430             v->writeSessionConfig(viewGroup);
0431         }
0432     }
0433 }
0434 
0435 bool KateViewSpace::showView(DocOrWidget docOrWidget)
0436 {
0437     /**
0438      * nothing can be done if we have no view ready here
0439      */
0440     auto it = m_docToView.find(docOrWidget.doc());
0441     if (it == m_docToView.end()) {
0442         if (!docOrWidget.widget()) {
0443             return false;
0444         } else if (docOrWidget.widget() && !m_registeredDocuments.contains(docOrWidget)) {
0445             qWarning() << "Unexpected unregistred widget" << docOrWidget.widget() << ", please add it first";
0446             return false;
0447         }
0448     }
0449 
0450     /**
0451      * update mru list order
0452      */
0453     const int index = m_registeredDocuments.lastIndexOf(docOrWidget);
0454     // move view to end of list
0455     if (index < 0) {
0456         return false;
0457     }
0458     // move view to end of list
0459     m_registeredDocuments.removeAt(index);
0460     m_registeredDocuments.push_back(docOrWidget);
0461 
0462     /**
0463      * show the wanted view
0464      */
0465     if (it != m_docToView.end()) {
0466         KTextEditor::View *kv = it->second;
0467         stack->setCurrentWidget(kv);
0468         kv->show();
0469     } else {
0470         stack->setCurrentWidget(m_registeredDocuments.back().widget());
0471     }
0472 
0473     /**
0474      * we need to avoid that below's index changes will mess with current view
0475      */
0476     disconnect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0477 
0478     /**
0479      * follow current view
0480      */
0481     m_tabBar->setCurrentDocument(docOrWidget);
0482 
0483     // track tab changes again
0484     connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0485     return true;
0486 }
0487 
0488 void KateViewSpace::changeView(int idx)
0489 {
0490     if (idx == -1) {
0491         return;
0492     }
0493 
0494     // make sure we open the view in this view space
0495     if (!isActiveSpace()) {
0496         m_viewManager->setActiveSpace(this);
0497     }
0498 
0499     const auto docOrWidget = m_tabBar->tabDocument(idx);
0500 
0501     // tell the view manager to show the view
0502     m_viewManager->activateView(docOrWidget, this);
0503 }
0504 
0505 KTextEditor::View *KateViewSpace::currentView()
0506 {
0507     // might be 0 if the stack contains no view
0508     return qobject_cast<KTextEditor::View *>(stack->currentWidget());
0509 }
0510 
0511 bool KateViewSpace::isActiveSpace() const
0512 {
0513     return m_isActiveSpace;
0514 }
0515 
0516 void KateViewSpace::setActive(bool active)
0517 {
0518     m_isActiveSpace = active;
0519     m_tabBar->setActive(active);
0520 }
0521 
0522 void KateViewSpace::makeActive(bool focusCurrentView)
0523 {
0524     if (!isActiveSpace()) {
0525         m_viewManager->setActiveSpace(this);
0526         if (focusCurrentView && currentView()) {
0527             m_viewManager->activateView(currentView()->document(), this);
0528         }
0529     }
0530     Q_ASSERT(isActiveSpace());
0531 }
0532 
0533 void KateViewSpace::registerDocument(KTextEditor::Document *doc)
0534 {
0535     /**
0536      * ignore request to register something that is already known
0537      */
0538     if (m_registeredDocuments.contains(doc)) {
0539         return;
0540     }
0541 
0542     /**
0543      * remember our new document
0544      */
0545     m_registeredDocuments.insert(0, doc);
0546 
0547     /**
0548      * ensure we cleanup after document is deleted, e.g. we remove the tab bar button
0549      */
0550     connect(doc, &QObject::destroyed, this, &KateViewSpace::documentDestroyed);
0551 
0552     /**
0553      * register document is used in places that don't like view creation
0554      * therefore we must ensure the currentChanged doesn't trigger that
0555      */
0556     disconnect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0557 
0558     /**
0559      * create the tab for this document, if necessary
0560      */
0561     m_tabBar->setCurrentDocument(doc);
0562 
0563     /**
0564      * handle later document state changes
0565      */
0566     connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateViewSpace::updateDocumentName);
0567     connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateViewSpace::updateDocumentUrl);
0568     connect(doc, &KTextEditor::Document::modifiedChanged, this, [this](KTextEditor::Document *doc) {
0569         int tab = m_tabBar->documentIdx(doc);
0570         if (tab >= 0) {
0571             m_tabBar->setModifiedStateIcon(tab, doc);
0572         }
0573     });
0574 
0575     /**
0576      * allow signals again, now that the tab is there
0577      */
0578     connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0579 }
0580 
0581 void KateViewSpace::closeDocument(KTextEditor::Document *doc)
0582 {
0583     auto it = m_docToView.find(doc);
0584     if (it != m_docToView.end() && it->first) {
0585         saveViewConfig(it->second);
0586     }
0587 
0588     // If this is the only view of the document,
0589     // OR the doc has no views yet
0590     // just close the document and it will take
0591     // care of removing the view + cleaning up the doc
0592     if (m_viewManager->docOnlyInOneViewspace(doc)) {
0593         m_viewManager->slotDocumentClose(doc);
0594     } else {
0595         // KTE::view for this tab has been created yet?
0596         if (it != m_docToView.end()) {
0597             // - We have a view for this doc in this viewspace
0598             // - We have other views of this doc in other viewspaces
0599             // - Just remove the view in this viewspace
0600             m_viewManager->deleteView(it->second);
0601         } else {
0602             // We don't have a view for this doc in this viewspace
0603             // Just remove the document
0604             documentDestroyed(doc);
0605         }
0606     }
0607 
0608     /**
0609      * if this was the last doc, let viewManager know we are empty
0610      */
0611     if (m_registeredDocuments.empty() && m_tabBar->count() == 0) {
0612         Q_EMIT viewSpaceEmptied(this);
0613     }
0614 }
0615 
0616 bool KateViewSpace::acceptsDroppedTab(const QMimeData *md)
0617 {
0618     if (auto tabMimeData = qobject_cast<const TabMimeData *>(md)) {
0619         return this != tabMimeData->sourceVS && // must not be same viewspace
0620             viewManager() == tabMimeData->sourceVS->viewManager() && // for now we don't support dropping into different windows
0621             !hasDocument(tabMimeData->doc);
0622     }
0623     return TabMimeData::hasValidData(md);
0624 }
0625 
0626 void KateViewSpace::dragEnterEvent(QDragEnterEvent *e)
0627 {
0628     if (acceptsDroppedTab(e->mimeData())) {
0629         m_dropIndicator.reset(new QRubberBand(QRubberBand::Rectangle, this));
0630         m_dropIndicator->setGeometry(rect());
0631         m_dropIndicator->show();
0632         e->acceptProposedAction();
0633         return;
0634     }
0635 
0636     QWidget::dragEnterEvent(e);
0637 }
0638 
0639 void KateViewSpace::dragLeaveEvent(QDragLeaveEvent *e)
0640 {
0641     m_dropIndicator.reset();
0642     QWidget::dragLeaveEvent(e);
0643 }
0644 
0645 void KateViewSpace::dropEvent(QDropEvent *e)
0646 {
0647     if (auto mimeData = qobject_cast<const TabMimeData *>(e->mimeData())) {
0648         m_viewManager->moveViewToViewSpace(this, mimeData->sourceVS, mimeData->doc);
0649         m_dropIndicator.reset();
0650         e->accept();
0651         return;
0652     }
0653     auto droppedData = TabMimeData::data(e->mimeData());
0654     if (droppedData.has_value()) {
0655         auto doc = KateApp::self()->documentManager()->openUrl(droppedData.value().url);
0656         auto view = m_viewManager->activateView(doc, this);
0657         if (view) {
0658             view->setCursorPosition({droppedData.value().line, droppedData.value().col});
0659             m_dropIndicator.reset();
0660             e->accept();
0661             return;
0662         }
0663     }
0664 
0665     QWidget::dropEvent(e);
0666 }
0667 
0668 bool KateViewSpace::hasDocument(DocOrWidget doc) const
0669 {
0670     return m_registeredDocuments.contains(doc);
0671 }
0672 
0673 QWidget *KateViewSpace::takeView(DocOrWidget docOrWidget)
0674 {
0675     QWidget *ret;
0676     if (docOrWidget.doc()) {
0677         auto it = m_docToView.find(docOrWidget.doc());
0678         if (it == m_docToView.end()) {
0679             qWarning() << "Unexpected unable to find a view for " << docOrWidget.qobject();
0680             return nullptr;
0681         }
0682         ret = it->second;
0683         // remove it from the stack
0684         stack->removeWidget(ret);
0685         // remove it from our doc->view mapping
0686         m_docToView.erase(it);
0687         documentDestroyed(docOrWidget.doc());
0688     } else if (docOrWidget.widget()) {
0689         stack->removeWidget(docOrWidget.widget());
0690         m_registeredDocuments.removeAll(docOrWidget);
0691         m_tabBar->removeDocument(docOrWidget);
0692         ret = docOrWidget.widget();
0693     } else {
0694         qWarning() << "Unexpected docOrWidget: " << docOrWidget.qobject();
0695         ret = nullptr;
0696         Q_UNREACHABLE();
0697     }
0698 
0699     // Did we just loose our last doc?
0700     // Send a delayed signal. Delay is important as we want to kill
0701     // the viewspace after the view transfer was done
0702     if (m_tabBar->count() == 0 && m_registeredDocuments.empty()) {
0703         QMetaObject::invokeMethod(
0704             this,
0705             [this] {
0706                 Q_EMIT viewSpaceEmptied(this);
0707             },
0708             Qt::QueuedConnection);
0709     }
0710 
0711     return ret;
0712 }
0713 
0714 void KateViewSpace::addView(QWidget *w)
0715 {
0716     // We must not already have this widget
0717     Q_ASSERT(stack->indexOf(w) == -1);
0718 
0719     if (auto v = qobject_cast<KTextEditor::View *>(w)) {
0720         registerDocument(v->document());
0721         m_docToView[v->document()] = v;
0722         stack->addWidget(v);
0723     } else {
0724         addWidgetAsTab(w);
0725     }
0726 }
0727 
0728 void KateViewSpace::documentDestroyed(QObject *doc)
0729 {
0730     /**
0731      * WARNING: this pointer is half destroyed
0732      * only good enough to check pointer value e.g. for hashes
0733      */
0734     KTextEditor::Document *invalidDoc = static_cast<KTextEditor::Document *>(doc);
0735     if (m_registeredDocuments.removeAll(invalidDoc) == 0) {
0736         // do nothing if this document wasn't registered for this viewspace
0737         return;
0738     }
0739 
0740     /**
0741      * we shall have no views for this document at this point in time!
0742      */
0743     Q_ASSERT(m_docToView.find(invalidDoc) == m_docToView.end());
0744 
0745     // disconnect entirely
0746     disconnect(doc, nullptr, this, nullptr);
0747 
0748     /**
0749      * remove the tab for this document, if existing
0750      */
0751     m_tabBar->removeDocument(invalidDoc);
0752 }
0753 
0754 void KateViewSpace::updateDocumentName(KTextEditor::Document *doc)
0755 {
0756     // update tab button if available, might not be the case for tab limit set!wee
0757     const int buttonId = m_tabBar->documentIdx(doc);
0758     if (buttonId >= 0) {
0759         // BUG: 441278 We need to escape the & because it is used for accelerators/shortcut mnemonic by default
0760         QString tabName = doc->documentName();
0761         tabName.replace(QLatin1Char('&'), QLatin1String("&&"));
0762         m_tabBar->setTabText(buttonId, tabName);
0763     }
0764 }
0765 
0766 void KateViewSpace::updateDocumentUrl(KTextEditor::Document *doc)
0767 {
0768     // update tab button if available, might not be the case for tab limit set!
0769     const int buttonId = m_tabBar->documentIdx(doc);
0770     if (buttonId >= 0) {
0771         m_tabBar->setTabToolTip(buttonId, doc->url().toDisplayString(QUrl::PreferLocalFile));
0772         m_tabBar->setModifiedStateIcon(buttonId, doc);
0773     }
0774 }
0775 
0776 void KateViewSpace::closeTabRequest(int idx)
0777 {
0778     const auto docOrWidget = m_tabBar->tabData(idx).value<DocOrWidget>();
0779     auto *doc = docOrWidget.doc();
0780     if (!doc) {
0781         auto widget = docOrWidget.widget();
0782         if (!widget) {
0783             Q_ASSERT(false);
0784             return;
0785         }
0786         removeWidget(widget);
0787         return;
0788     }
0789 
0790     closeDocument(doc);
0791 }
0792 
0793 void KateViewSpace::removeWidget(QWidget *w)
0794 {
0795     bool shouldClose = true;
0796     QMetaObject::invokeMethod(w, "shouldClose", Q_RETURN_ARG(bool, shouldClose));
0797     if (shouldClose) {
0798         stack->removeWidget(w);
0799         m_registeredDocuments.removeOne(w);
0800 
0801         DocOrWidget widget(w);
0802         const auto idx = m_tabBar->documentIdx(widget);
0803         m_tabBar->blockSignals(true);
0804         m_tabBar->removeDocument(widget);
0805         m_tabBar->blockSignals(false);
0806 
0807         w->deleteLater();
0808         Q_EMIT m_viewManager->mainWindow()->widgetRemoved(w);
0809 
0810         // If some tab was removed, switch to most recently used doc
0811         if (idx >= 0) {
0812             // switch to most recently used doc
0813             for (auto rit = m_registeredDocuments.rbegin(); rit != m_registeredDocuments.rend(); ++rit) {
0814                 if (auto doc = rit->doc()) {
0815                     m_viewManager->activateView(doc, this);
0816                     break;
0817                 } else if (auto wid = rit->widget()) {
0818                     activateWidget(wid);
0819                     break;
0820                 }
0821             }
0822         }
0823 
0824         // if this was the last doc, let viewManager know we are empty
0825         if (m_registeredDocuments.empty() && m_tabBar->count() == 0) {
0826             Q_EMIT viewSpaceEmptied(this);
0827         }
0828     }
0829 }
0830 
0831 void KateViewSpace::createNewDocument()
0832 {
0833     // make sure we open the view in this view space
0834     if (!isActiveSpace()) {
0835         m_viewManager->setActiveSpace(this);
0836     }
0837 
0838     // create document
0839     KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc();
0840 
0841     // tell the view manager to show the document
0842     m_viewManager->activateView(doc, this);
0843 }
0844 
0845 void KateViewSpace::focusPrevTab()
0846 {
0847     const int id = m_tabBar->prevTab();
0848     if (id >= 0) {
0849         changeView(id);
0850     }
0851 }
0852 
0853 void KateViewSpace::focusNextTab()
0854 {
0855     const int id = m_tabBar->nextTab();
0856     if (id >= 0) {
0857         changeView(id);
0858     }
0859 }
0860 
0861 void KateViewSpace::addWidgetAsTab(QWidget *widget)
0862 {
0863     stack->addWidget(widget);
0864     // disconnect changeView, we are just adding the widget here
0865     // and don't want any unnecessary viewChanged signals
0866     disconnect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0867     m_tabBar->setCurrentDocument(widget);
0868     connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView);
0869     stack->setCurrentWidget(widget);
0870     m_registeredDocuments.push_back(widget);
0871     updateTabBar();
0872 }
0873 
0874 bool KateViewSpace::hasWidgets() const
0875 {
0876     return stack->count() > (int)m_docToView.size();
0877 }
0878 
0879 QWidget *KateViewSpace::currentWidget()
0880 {
0881     if (auto w = stack->currentWidget()) {
0882         return qobject_cast<KTextEditor::View *>(w) ? nullptr : w;
0883     }
0884     return nullptr;
0885 }
0886 
0887 QWidgetList KateViewSpace::widgets() const
0888 {
0889     QWidgetList widgets;
0890     for (int i = 0; i < m_tabBar->count(); ++i) {
0891         auto widget = m_tabBar->tabData(i).value<DocOrWidget>().widget();
0892         if (widget) {
0893             widgets << widget;
0894         }
0895     }
0896     return widgets;
0897 }
0898 
0899 bool KateViewSpace::closeTabWithWidget(QWidget *widget)
0900 {
0901     int index = m_registeredDocuments.indexOf(widget);
0902     if (index < 0) {
0903         return false;
0904     }
0905     removeWidget(widget);
0906     return true;
0907 }
0908 
0909 bool KateViewSpace::activateWidget(QWidget *widget)
0910 {
0911     if (stack->indexOf(widget) == -1) {
0912         return false;
0913     }
0914 
0915     stack->setCurrentWidget(widget);
0916     m_registeredDocuments.removeOne(widget);
0917     m_registeredDocuments.push_back(widget);
0918     m_tabBar->setCurrentDocument(DocOrWidget(widget));
0919     return true;
0920 }
0921 
0922 void KateViewSpace::focusNavigationBar()
0923 {
0924     if (!m_urlBar->isHidden()) {
0925         m_urlBar->open();
0926     }
0927 }
0928 
0929 void KateViewSpace::addPositionToHistory(const QUrl &url, KTextEditor::Cursor c, bool calledExternally)
0930 {
0931     // We don't care about invalid urls (Fixed Diff View / Untitled docs)
0932     if (!url.isValid()) {
0933         return;
0934     }
0935 
0936     // if same line, remove last entry
0937     // If new pos is same as "current pos", replace it with new one
0938     bool currPosIsInSameLine = false;
0939     if (currentLocation < m_locations.size()) {
0940         const auto &currentLoc = m_locations.at(currentLocation);
0941         currPosIsInSameLine = currentLoc.url == url && currentLoc.cursor.line() == c.line();
0942     }
0943 
0944     // Check if the location is at least "viewLineCount" away from the "current" position in m_locations
0945     if (const auto view = m_viewManager->activeView();
0946         view && !calledExternally && currentLocation < m_locations.size() && m_locations.at(currentLocation).url == url) {
0947         const int currentLine = m_locations.at(currentLocation).cursor.line();
0948         const int newPosLine = c.line();
0949         const int viewLineCount = view->lastDisplayedLine() - view->firstDisplayedLine();
0950         const int lowerBound = currentLine - viewLineCount;
0951         const int upperBound = currentLine + viewLineCount;
0952         if (lowerBound <= newPosLine && newPosLine <= upperBound) {
0953             if (currPosIsInSameLine) {
0954                 m_locations[currentLocation].cursor = c;
0955             }
0956             return;
0957         }
0958     }
0959 
0960     if (currPosIsInSameLine) {
0961         m_locations[currentLocation].cursor.setColumn(c.column());
0962         return;
0963     }
0964 
0965     // we are in the middle of jumps somewhere?
0966     if (!m_locations.empty() && currentLocation + 1 < m_locations.size()) {
0967         // erase all forward history
0968         m_locations.erase(m_locations.begin() + currentLocation + 1, m_locations.end());
0969     }
0970 
0971     /** this is our new forward **/
0972 
0973     m_locations.push_back({url, c});
0974     // set currentLocation as last
0975     currentLocation = m_locations.size() - 1;
0976     // disable forward button as we are at the end now
0977     m_historyForward->setEnabled(false);
0978     Q_EMIT m_viewManager->historyForwardEnabled(false);
0979 
0980     // renable back
0981     if (currentLocation > 0) {
0982         m_historyBack->setEnabled(true);
0983         Q_EMIT m_viewManager->historyBackEnabled(true);
0984     }
0985 
0986     // limit size to 50, remove first 10
0987     int toErase = (int)m_locations.size() - 50;
0988     if (toErase > 0) {
0989         m_locations.erase(m_locations.begin(), m_locations.begin() + toErase);
0990         currentLocation -= toErase;
0991     }
0992 }
0993 
0994 int KateViewSpace::hiddenDocuments() const
0995 {
0996     const auto hiddenDocs = KateApp::self()->documentManager()->documentList().size() - m_tabBar->count();
0997     Q_ASSERT(hiddenDocs >= 0);
0998     return hiddenDocs;
0999 }
1000 
1001 void KateViewSpace::showContextMenu(int idx, const QPoint &globalPos)
1002 {
1003     // right now, show no context menu on empty tab bar space
1004     if (idx < 0) {
1005         return;
1006     }
1007 
1008     auto activeView = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView();
1009     if (!activeView) {
1010         return; // the welcome screen is open
1011     }
1012 
1013     auto docOrWidget = m_tabBar->tabDocument(idx);
1014     auto activeDocument = activeView->document(); // used for compareUsing which is used with another
1015     if (!docOrWidget.doc()) {
1016         // This tab is holding some other widget
1017         // Show only "close tab" for now
1018         // maybe later allow adding context menu entries from the widgets
1019         // if needed
1020         QMenu menu(this);
1021         auto aCloseTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("Close Tab"));
1022         auto choice = menu.exec(globalPos);
1023         if (choice == aCloseTab) {
1024             // use single shot as this action can trigger deletion of this viewspace!
1025             QTimer::singleShot(0, this, [this, idx]() {
1026                 closeTabRequest(idx);
1027             });
1028         }
1029         return;
1030     }
1031     auto *doc = docOrWidget.doc();
1032 
1033     auto addActionFromCollection = [this](QMenu *menu, const char *action_name) {
1034         QAction *action = m_viewManager->mainWindow()->action(QLatin1StringView(action_name));
1035         return menu->addAction(action->icon(), action->text());
1036     };
1037 
1038     QMenu menu(this);
1039     QAction *aCloseTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("&Close Document"));
1040     QAction *aCloseOthers = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close-other")), i18n("Close Other &Documents"));
1041     QAction *aCloseAll = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close-all")), i18n("Close &All Documents"));
1042     menu.addSeparator();
1043     menu.addAction(KStandardAction::open(m_viewManager, &KateViewManager::slotDocumentOpen, this));
1044     QAction *aDetachTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-detach")), i18n("D&etach Document"));
1045     menu.addSeparator();
1046     aDetachTab->setWhatsThis(i18n("Opens the document in a new window and closes it in the current window"));
1047     menu.addSeparator();
1048     QAction *aCopyPath = addActionFromCollection(&menu, "file_copy_filepath");
1049     QAction *aOpenFolder = addActionFromCollection(&menu, "file_open_containing_folder");
1050     QAction *aFileProperties = addActionFromCollection(&menu, "file_properties");
1051     menu.addSeparator();
1052     QAction *aRenameFile = addActionFromCollection(&menu, "file_rename");
1053     QAction *aDeleteFile = addActionFromCollection(&menu, "file_delete");
1054     menu.addSeparator();
1055     QAction *compare = menu.addAction(i18n("Compare with Active Document"));
1056     compare->setIcon(QIcon::fromTheme(QStringLiteral("vcs-diff")));
1057     connect(compare, &QAction::triggered, this, [this, activeDocument, doc] {
1058         DiffWidgetManager::diffDocs(activeDocument, doc, m_viewManager->mainWindow()->wrapper());
1059     });
1060 
1061     QMenu *compareUsing = new QMenu(i18n("Compare with Active Document Using"), &menu);
1062     compareUsing->setIcon(QIcon::fromTheme(QStringLiteral("vcs-diff")));
1063     menu.addMenu(compareUsing);
1064 
1065     // if we have other documents, allow to close them
1066     aCloseOthers->setEnabled(KateApp::self()->documentManager()->documentList().size() > 1);
1067 
1068     // make it feasible to detach tabs if we have more then one
1069     aDetachTab->setEnabled(m_tabBar->count() > 1);
1070 
1071     if (doc->url().isEmpty()) {
1072         aCopyPath->setEnabled(false);
1073         aOpenFolder->setEnabled(false);
1074         aRenameFile->setEnabled(false);
1075         aDeleteFile->setEnabled(false);
1076         aFileProperties->setEnabled(false);
1077         compareUsing->setEnabled(false);
1078     }
1079 
1080     // both documents must have urls and must not be the same to have the compare feature enabled
1081     if (activeDocument->url().isEmpty() || activeDocument == doc) {
1082         compare->setEnabled(false);
1083         compareUsing->setEnabled(false);
1084     }
1085 
1086     if (compareUsing->isEnabled()) {
1087         for (auto &&diffTool : KateFileActions::supportedDiffTools()) {
1088             QAction *compareAction = compareUsing->addAction(diffTool.first);
1089 
1090             // we use the full path to safely execute the tool, disable action if no full path => tool not found
1091             compareAction->setData(diffTool.second);
1092             compareAction->setEnabled(!diffTool.second.isEmpty());
1093         }
1094     }
1095 
1096     QAction *choice = menu.exec(globalPos);
1097 
1098     if (!choice) {
1099         return;
1100     }
1101 
1102     if (choice == aCloseTab) {
1103         // use single shot as this action can trigger deletion of this viewspace!
1104         QTimer::singleShot(0, this, [this, idx]() {
1105             closeTabRequest(idx);
1106         });
1107     } else if (choice == aCloseOthers) {
1108         KateApp::self()->documentManager()->closeOtherDocuments(doc);
1109     } else if (choice == aCloseAll) {
1110         // use single shot as this action can trigger deletion of this viewspace!
1111         QTimer::singleShot(0, this, []() {
1112             KateApp::self()->documentManager()->closeAllDocuments();
1113         });
1114     } else if (choice == aCopyPath) {
1115         KateFileActions::copyFilePathToClipboard(doc);
1116     } else if (choice == aOpenFolder) {
1117         KateFileActions::openContainingFolder(doc);
1118     } else if (choice == aFileProperties) {
1119         KateFileActions::openFilePropertiesDialog(this, doc);
1120     } else if (choice == aRenameFile) {
1121         KateFileActions::renameDocumentFile(this, doc);
1122     } else if (choice == aDeleteFile) {
1123         KateFileActions::deleteDocumentFile(this, doc);
1124     } else if (choice->parent() == compareUsing) {
1125         QString actionData = choice->data().toString(); // name of the executable of the diff program
1126         if (!KateFileActions::compareWithExternalProgram(activeDocument, doc, actionData)) {
1127             QMessageBox::information(this,
1128                                      i18n("Could not start program"),
1129                                      i18n("The selected program could not be started. Maybe it is not installed."),
1130                                      QMessageBox::StandardButton::Ok);
1131         }
1132     } else if (choice == aDetachTab) {
1133         auto mainWindow = viewManager()->mainWindow()->newWindow();
1134         mainWindow->viewManager()->openViewForDoc(doc);
1135 
1136         // use single shot as this action can trigger deletion of this viewspace!
1137         QTimer::singleShot(0, this, [this, idx]() {
1138             closeTabRequest(idx);
1139         });
1140     }
1141 }
1142 
1143 void KateViewSpace::saveConfig(KConfigBase *config, int myIndex, const QString &viewConfGrp)
1144 {
1145     //   qCDebug(LOG_KATE)<<"KateViewSpace::saveConfig("<<myIndex<<", "<<viewConfGrp<<") - currentView: "<<currentView()<<")";
1146     QString groupname = QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(myIndex);
1147 
1148     // aggregate all views in view space (LRU ordered)
1149     std::vector<KTextEditor::View *> views;
1150     QStringList lruList;
1151     const auto docList = documentList();
1152     for (DocOrWidget docOrWidget : docList) {
1153         if (docOrWidget.widget()) {
1154             continue;
1155         }
1156         auto doc = docOrWidget.doc();
1157         lruList << doc->url().toString();
1158         auto it = m_docToView.find(doc);
1159         if (it != m_docToView.end()) {
1160             views.push_back(it->second);
1161         }
1162     }
1163 
1164     KConfigGroup group(config, groupname);
1165     group.writeEntry("Documents", lruList);
1166     group.writeEntry("Count", static_cast<int>(views.size()));
1167 
1168     if (currentView()) {
1169         group.writeEntry("Active View", currentView()->document()->url().toString());
1170     }
1171 
1172     // Save file list, including cursor position in this instance.
1173     int idx = 0;
1174     for (auto view : views) {
1175         const auto url = view->document()->url();
1176         if (!url.isEmpty()) {
1177             group.writeEntry(QStringLiteral("View %1").arg(idx), url.toString());
1178 
1179             // view config, group: "ViewSpace <n> url"
1180             QString vgroup = QStringLiteral("%1 %2").arg(groupname, url.toString());
1181             KConfigGroup viewGroup(config, vgroup);
1182             view->writeSessionConfig(viewGroup);
1183         }
1184 
1185         ++idx;
1186     }
1187 }
1188 
1189 void KateViewSpace::restoreConfig(KateViewManager *viewMan, const KConfigBase *config, const QString &groupname)
1190 {
1191     KConfigGroup group(config, groupname);
1192 
1193     // workaround for the weird bug where the tabbar sometimes becomes invisible after opening a session via the session chooser dialog or the --start cmd
1194     // option
1195     // TODO: Debug the actual reason for the bug. See https://invent.kde.org/utilities/kate/-/merge_requests/189
1196     m_tabBar->hide();
1197     m_tabBar->show();
1198 
1199     // set back bar status to configured variant
1200     tabBarToggled();
1201 
1202     // restore Document lru list so that all tabs from the last session reappear
1203     const QStringList lruList = group.readEntry("Documents", QStringList());
1204     for (const auto &url : lruList) {
1205         // ignore untitled stuff
1206         if (url.isEmpty()) {
1207             continue;
1208         }
1209 
1210         // ignore non-existing documents
1211         if (auto doc = KateApp::self()->documentManager()->findDocument(QUrl(url))) {
1212             registerDocument(doc);
1213         }
1214     }
1215 
1216     // restore active view properties
1217     const QString fn = group.readEntry("Active View");
1218     if (!fn.isEmpty()) {
1219         if (auto doc = KateApp::self()->documentManager()->findDocument(QUrl(fn))) {
1220             if (auto view = viewMan->createView(doc, this)) {
1221                 // view config, group: "ViewSpace <n> url"
1222                 const QString vgroup = QStringLiteral("%1 %2").arg(groupname, fn);
1223                 view->readSessionConfig(KConfigGroup(config, vgroup));
1224                 m_tabBar->setCurrentDocument(doc);
1225             }
1226         }
1227     }
1228 
1229     // ensure we update the urlbar at least once
1230     m_urlBar->updateForDocument(currentView() ? currentView()->document() : nullptr);
1231 
1232     m_group = groupname; // used for restroing view configs later
1233 }
1234 
1235 void KateViewSpace::goBack()
1236 {
1237     if (m_locations.empty() || currentLocation <= 0) {
1238         currentLocation = 0;
1239         return;
1240     }
1241 
1242     const auto &location = m_locations.at(currentLocation - 1);
1243     currentLocation--;
1244 
1245     if (currentLocation <= 0) {
1246         m_historyBack->setEnabled(false);
1247         Q_EMIT m_viewManager->historyBackEnabled(false);
1248     }
1249 
1250     if (auto v = m_viewManager->activeView()) {
1251         if (v->document()->url() == location.url) {
1252             QScopedValueRollback blocker(m_blockAddHistory, true);
1253             v->setCursorPosition(location.cursor);
1254             // enable forward
1255             m_historyForward->setEnabled(true);
1256             Q_EMIT m_viewManager->historyForwardEnabled(true);
1257             return;
1258         }
1259     }
1260 
1261     auto v = m_viewManager->openUrlWithView(location.url, QString());
1262     QScopedValueRollback blocker(m_blockAddHistory, true);
1263     v->setCursorPosition(location.cursor);
1264     // enable forward in viewspace + mainwindow
1265     m_historyForward->setEnabled(true);
1266     Q_EMIT m_viewManager->historyForwardEnabled(true);
1267 }
1268 
1269 bool KateViewSpace::isHistoryBackEnabled() const
1270 {
1271     return m_historyBack->isEnabled();
1272 }
1273 
1274 bool KateViewSpace::isHistoryForwardEnabled() const
1275 {
1276     return m_historyForward->isEnabled();
1277 }
1278 
1279 void KateViewSpace::goForward()
1280 {
1281     if (m_locations.empty()) {
1282         return;
1283     }
1284 
1285     // We are already at the last position
1286     if (currentLocation >= m_locations.size() - 1) {
1287         return;
1288     }
1289 
1290     const auto &location = m_locations.at(currentLocation + 1);
1291     currentLocation++;
1292 
1293     if (currentLocation + 1 >= m_locations.size()) {
1294         Q_EMIT m_viewManager->historyForwardEnabled(false);
1295         m_historyForward->setEnabled(false);
1296     }
1297 
1298     if (!location.url.isValid() || !location.cursor.isValid()) {
1299         m_locations.erase(m_locations.begin() + currentLocation);
1300         return;
1301     }
1302 
1303     m_historyBack->setEnabled(true);
1304     Q_EMIT m_viewManager->historyBackEnabled(true);
1305 
1306     if (auto v = m_viewManager->activeView()) {
1307         if (v->document()->url() == location.url) {
1308             QScopedValueRollback blocker(m_blockAddHistory, true);
1309             v->setCursorPosition(location.cursor);
1310             return;
1311         }
1312     }
1313 
1314     auto v = m_viewManager->openUrlWithView(location.url, QString());
1315     QScopedValueRollback blocker(m_blockAddHistory, true);
1316     v->setCursorPosition(location.cursor);
1317 }
1318 
1319 // END KateViewSpace
1320 
1321 #include "moc_kateviewspace.cpp"