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 ¤tLoc = 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"