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