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

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    SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com>
0006 
0007    SPDX-License-Identifier: LGPL-2.0-only
0008 */
0009 
0010 // BEGIN Includes
0011 #include "katemainwindow.h"
0012 #include <KColorSchemeMenu>
0013 
0014 #include "diagnostics/diagnosticview.h"
0015 #include "filehistorywidget.h"
0016 #include "kateapp.h"
0017 #include "kateconfigdialog.h"
0018 #include "katedocmanager.h"
0019 #include "katefileactions.h"
0020 #include "katemwmodonhddialog.h"
0021 #include "kateoutputview.h"
0022 #include "katepluginmanager.h"
0023 #include "katequickopen.h"
0024 #include "katesavemodifieddialog.h"
0025 #include "katesessionmanager.h"
0026 #include "katesessionsaction.h"
0027 #include "katestashmanager.h"
0028 #include "kateupdatedisabler.h"
0029 #include "kateviewspace.h"
0030 #include "ktexteditor_utils.h"
0031 #include "texthint/KateTextHintManager.h"
0032 
0033 #include <KActionCollection>
0034 #include <KActionMenu>
0035 #include <KColorSchemeManager>
0036 #include <KConfigGroup>
0037 #include <KEditToolBar>
0038 #include <KFileItem>
0039 #include <KHelpClient>
0040 #include <KIO/ListJob>
0041 #include <KLocalizedString>
0042 #include <KMessageBox>
0043 #include <KMultiTabBar>
0044 #include <KOpenWithDialog>
0045 #include <KRecentDocument>
0046 #include <KRecentFilesAction>
0047 #include <KSharedConfig>
0048 #include <KShortcutsDialog>
0049 #include <KStandardAction>
0050 #include <KToggleFullScreenAction>
0051 #include <KToolBar>
0052 #include <KWindowConfig>
0053 #include <KXMLGUIFactory>
0054 #include <kconfigwidgets_version.h>
0055 #include <kwidgetsaddons_version.h>
0056 
0057 #include <QApplication>
0058 #include <QDir>
0059 #include <QFontDatabase>
0060 #include <QKeySequence>
0061 #include <QList>
0062 #include <QMenu>
0063 #include <QMenuBar>
0064 #include <QMimeData>
0065 #include <QMimeDatabase>
0066 #include <QScreen>
0067 #include <QStackedWidget>
0068 #include <QTimer>
0069 #include <QToolButton>
0070 
0071 #include <ktexteditor/sessionconfiginterface.h>
0072 
0073 // END
0074 
0075 // shall windows close the documents only visible inside them if the are closed?
0076 static bool winClosesDocuments()
0077 {
0078     const auto config = KSharedConfig::openConfig();
0079     const KConfigGroup cgGeneral(config, QStringLiteral("General"));
0080     return cgGeneral.readEntry("Close documents with window", true);
0081 }
0082 
0083 KateMwModOnHdDialog *KateMainWindow::s_modOnHdDialog = nullptr;
0084 
0085 KateContainerStackedLayout::KateContainerStackedLayout(QWidget *parent)
0086     : QStackedLayout(parent)
0087 {
0088 }
0089 
0090 QSize KateContainerStackedLayout::sizeHint() const
0091 {
0092     if (currentWidget()) {
0093         return currentWidget()->sizeHint();
0094     }
0095     return QStackedLayout::sizeHint();
0096 }
0097 
0098 QSize KateContainerStackedLayout::minimumSize() const
0099 {
0100     if (currentWidget()) {
0101         return currentWidget()->minimumSize();
0102     }
0103     return QStackedLayout::minimumSize();
0104 }
0105 
0106 KateMainWindow::KateMainWindow(KConfig *sconfig, const QString &sgroup, bool userTriggered)
0107     : KateMDI::MainWindow(nullptr)
0108     , m_wrapper(new KTextEditor::MainWindow(this))
0109 {
0110     /**
0111      * we don't want any flicker here
0112      */
0113     KateUpdateDisabler disableUpdates(this);
0114 
0115     // start session restore if needed
0116     startRestore(sconfig, sgroup);
0117 
0118     // setup most important actions first, needed by setupMainWindow
0119     setupImportantActions();
0120 
0121     // setup the most important widgets
0122     setupMainWindow();
0123 
0124     // setup the actions
0125     setupActions();
0126 
0127     setStandardToolBarMenuEnabled(true);
0128     setXMLFile(QStringLiteral("kateui.rc"));
0129     createShellGUI(true);
0130 
0131     // Has to be after the setXMLFile() call above, so that m_diagView's actions are
0132     // merged after the Tools menu has been populated with the default actions
0133     setupDiagnosticsView(sconfig);
0134 
0135     // qCDebug(LOG_KATE) << "****************************************************************************" << sconfig;
0136 
0137     // register mainwindow in app
0138     KateApp::self()->addMainWindow(this);
0139 
0140     // enable plugin guis
0141     KateApp::self()->pluginManager()->enableAllPluginsGUI(this, sconfig);
0142 
0143     // caption update
0144     const auto documents = KateApp::self()->documentManager()->documentList();
0145     for (auto doc : documents) {
0146         slotDocumentCreated(doc);
0147     }
0148 
0149     connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateMainWindow::slotDocumentCreated);
0150     connect(KateApp::self(), &KateApp::configurationChanged, this, &KateMainWindow::readOptions);
0151 
0152     readOptions();
0153 
0154     if (sconfig && !userTriggered) {
0155         m_viewManager->restoreViewConfiguration(KConfigGroup(sconfig, sgroup));
0156     }
0157 
0158     // unstash
0159     // KateStashManager().popStash(m_viewManager);
0160 
0161     finishRestore();
0162 
0163     m_fileOpenRecent->loadEntries(KConfigGroup(sconfig, QStringLiteral("Recent Files")));
0164 
0165     setAcceptDrops(true);
0166 
0167     connect(KateApp::self()->sessionManager(), SIGNAL(sessionChanged()), this, SLOT(updateCaption()));
0168 
0169     connect(this, &KateMDI::MainWindow::sigShowPluginConfigPage, this, &KateMainWindow::showPluginConfigPage);
0170 
0171     connect(qApp, &QApplication::applicationStateChanged, this, &KateMainWindow::onApplicationStateChanged);
0172 
0173     // prior to this there was (possibly) no view, therefore not context menu.
0174     // Hence, we have to take care of the menu bar here
0175     toggleShowMenuBar(false);
0176 
0177     ensureHamburgerBarSize();
0178 
0179     // trigger proper focus restore
0180     m_viewManager->triggerActiveViewFocus();
0181 }
0182 
0183 KateMainWindow::~KateMainWindow()
0184 {
0185     // first, save our fallback window size ;)
0186     KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("MainWindow"));
0187     KWindowConfig::saveWindowSize(windowHandle(), cfg);
0188 
0189     // save other options ;=)
0190     saveOptions();
0191 
0192     // close all documents not visible in other windows, we did ask for permission in queryClose
0193     if (winClosesDocuments()) {
0194         auto docs = KateApp::self()->documentManager()->documentList();
0195         const bool canStash = KateApp::self()->stashManager()->canStash();
0196         docs.erase(std::remove_if(docs.begin(),
0197                                   docs.end(),
0198                                   [this, canStash](auto doc) {
0199                                       if (canStash && (doc->isModified() || doc->url().isEmpty())) {
0200                                           return true;
0201                                       }
0202                                       return KateApp::self()->documentVisibleInOtherWindows(doc, this);
0203                                   }),
0204                    docs.end());
0205         KateApp::self()->documentManager()->closeDocuments(docs, false);
0206     }
0207 
0208     // Delete diagnostics view earlier so that destruction is faster
0209     // If we delay it then each provider will get unregisted one by one
0210     // and slow down the destruction as we will be clearing diagnostics
0211     // for each provider individually
0212     delete m_diagView;
0213 
0214     // unregister mainwindow in app
0215     KateApp::self()->removeMainWindow(this);
0216 
0217     // disable all plugin guis, delete all pluginViews
0218     KateApp::self()->pluginManager()->disableAllPluginsGUI(this);
0219 
0220     // delete the view manager, before KateMainWindow's wrapper is dead
0221     delete m_viewManager;
0222     m_viewManager = nullptr;
0223 
0224     // kill the wrapper object, now that all views are dead
0225     delete m_wrapper;
0226     m_wrapper = nullptr;
0227 }
0228 
0229 QSize KateMainWindow::sizeHint() const
0230 {
0231     // ensure some proper sizing per default
0232     return (QSize(800, 600).expandedTo(minimumSizeHint())).expandedTo(screen()->availableSize() * 0.6).boundedTo(screen()->availableSize());
0233 }
0234 
0235 void KateMainWindow::setupImportantActions()
0236 {
0237     m_paShowStatusBar = KStandardAction::showStatusbar(this, SLOT(toggleShowStatusBar()), actionCollection());
0238     m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar"));
0239     m_paShowMenuBar = KStandardAction::showMenubar(this, SLOT(toggleShowMenuBar()), actionCollection());
0240 
0241     m_paShowTabBar = new KToggleAction(i18n("Show &Tabs"), this);
0242     actionCollection()->addAction(QStringLiteral("settings_show_tab_bar"), m_paShowTabBar);
0243     connect(m_paShowTabBar, &QAction::toggled, this, &KateMainWindow::toggleShowTabBar);
0244     m_paShowTabBar->setWhatsThis(i18n("Use this command to show or hide the tabs for the views"));
0245 
0246     m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this);
0247     actionCollection()->addAction(QStringLiteral("settings_show_full_path"), m_paShowPath);
0248     connect(m_paShowPath, SIGNAL(toggled(bool)), this, SLOT(updateCaption()));
0249     m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption"));
0250 
0251     m_paShowUrlNavBar = new KToggleAction(i18n("Show Navigation Bar"), this);
0252     actionCollection()->addAction(QStringLiteral("settings_show_url_nav_bar"), m_paShowUrlNavBar);
0253     connect(m_paShowUrlNavBar, &QAction::toggled, this, [this](bool v) {
0254         m_viewManager->setShowUrlNavBar(v);
0255     });
0256 
0257     // Load themes
0258     KColorSchemeManager *manager = new KColorSchemeManager(this);
0259     auto *colorSelectionMenu = KColorSchemeMenu::createMenu(manager, this);
0260     colorSelectionMenu->menu()->setTitle(i18n("&Window Color Scheme"));
0261     actionCollection()->addAction(QStringLiteral("colorscheme_menu"), colorSelectionMenu);
0262 
0263     QAction *a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("view_prev_tab"));
0264     a->setText(i18n("&Previous Tab"));
0265     a->setWhatsThis(i18n("Focus the previous tab."));
0266     actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabPrev());
0267     connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusPrevTab);
0268 
0269     a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("view_next_tab"));
0270     a->setText(i18n("&Next Tab"));
0271     a->setWhatsThis(i18n("Focus the next tab."));
0272     actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabNext());
0273     connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusNextTab);
0274 
0275     // the quick open action is used by the KateViewSpace "quick open button"
0276     a = actionCollection()->addAction(QStringLiteral("view_quick_open"));
0277     a->setIcon(QIcon::fromTheme(QStringLiteral("quickopen")));
0278     a->setText(i18n("&Quick Open"));
0279     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_O));
0280     connect(a, &QAction::triggered, this, &KateMainWindow::slotQuickOpen);
0281     a->setWhatsThis(i18n("Open a form to quick open documents."));
0282 
0283     // enable hamburger menu
0284     auto hamburgerMenu = static_cast<KHamburgerMenu *>(actionCollection()->addAction(KStandardAction::HamburgerMenu, QStringLiteral("hamburger_menu")));
0285     hamburgerMenu->setMenuBar(menuBar());
0286     hamburgerMenu->setShowMenuBarAction(m_paShowMenuBar);
0287 }
0288 
0289 void KateMainWindow::setupMainWindow()
0290 {
0291     m_viewManager = new KateViewManager(centralWidget(), this);
0292     centralWidget()->layout()->addWidget(m_viewManager);
0293     (static_cast<QBoxLayout *>(centralWidget()->layout()))->setStretchFactor(m_viewManager, 100);
0294 
0295     m_bottomViewBarContainer = new QWidget(centralWidget());
0296     centralWidget()->layout()->addWidget(m_bottomViewBarContainer);
0297     m_bottomContainerStack = new KateContainerStackedLayout(m_bottomViewBarContainer);
0298 
0299     if (KateApp::isKWrite()) {
0300         // Kwrite has nothing other than the view manager
0301         return;
0302     }
0303 
0304     /**
0305      * create generic output tool view
0306      * is used to display output of e.g. plugins
0307      */
0308     m_toolViewOutput = createToolView(nullptr /* toolview has no plugin it belongs to */,
0309                                       QStringLiteral("output"),
0310                                       KTextEditor::MainWindow::Bottom,
0311                                       QIcon::fromTheme(QStringLiteral("output_win")),
0312                                       i18n("Output"));
0313     m_outputView = new KateOutputView(this, m_toolViewOutput);
0314 }
0315 
0316 void KateMainWindow::setupActions()
0317 {
0318     QAction *a;
0319 
0320     actionCollection()
0321         ->addAction(KStandardAction::New, QStringLiteral("file_new"), m_viewManager, SLOT(slotDocumentNew()))
0322         ->setWhatsThis(i18n("Create a new document"));
0323     actionCollection()
0324         ->addAction(KStandardAction::Open, QStringLiteral("file_open"), m_viewManager, SLOT(slotDocumentOpen()))
0325         ->setWhatsThis(i18n("Open an existing document for editing"));
0326 
0327     m_fileOpenRecent = KStandardAction::openRecent(
0328         m_viewManager,
0329         [this](const QUrl &url) {
0330             viewManager()->openUrlOrProject(url);
0331         },
0332         this);
0333     m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount());
0334     actionCollection()->addAction(m_fileOpenRecent->objectName(), m_fileOpenRecent);
0335     m_fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));
0336 
0337     a = actionCollection()->addAction(QStringLiteral("file_save_all"));
0338     a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
0339     a->setText(i18n("Save A&ll"));
0340     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_L));
0341     connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::saveAll);
0342     a->setWhatsThis(i18n("Save all open, modified documents to disk."));
0343 
0344     a = actionCollection()->addAction(QStringLiteral("file_reload_all"));
0345     a->setText(i18n("&Reload All"));
0346     connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::reloadAll);
0347     a->setWhatsThis(i18n("Reload all open documents."));
0348 
0349     a = actionCollection()->addAction(QStringLiteral("file_copy_filepath"));
0350     a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy-path")));
0351     a->setText(i18n("Copy Location"));
0352     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0353         auto &&view = viewManager()->activeView();
0354         KateFileActions::copyFilePathToClipboard(view->document());
0355     });
0356     a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));
0357 
0358     a = actionCollection()->addAction(QStringLiteral("file_open_containing_folder"));
0359     a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder")));
0360     a->setText(i18n("&Open Containing Folder"));
0361     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0362         auto &&view = viewManager()->activeView();
0363         KateFileActions::openContainingFolder(view->document());
0364     });
0365     a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));
0366 
0367     a = actionCollection()->addAction(QStringLiteral("file_rename"));
0368     a->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
0369     a->setText(i18nc("@action:inmenu", "Rename..."));
0370     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0371         auto &&view = viewManager()->activeView();
0372         KateFileActions::renameDocumentFile(this, view->document());
0373     });
0374     a->setWhatsThis(i18n("Renames the file belonging to the current document."));
0375 
0376     a = actionCollection()->addAction(QStringLiteral("file_delete"));
0377     a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
0378     a->setText(i18nc("@action:inmenu", "Delete"));
0379     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0380         auto &&view = viewManager()->activeView();
0381         KateFileActions::deleteDocumentFile(this, view->document());
0382     });
0383     a->setWhatsThis(i18n("Deletes the file belonging to the current document."));
0384 
0385     a = actionCollection()->addAction(QStringLiteral("file_properties"));
0386     a->setIcon(QIcon::fromTheme(QStringLiteral("dialog-object-properties")));
0387     a->setText(i18n("Properties"));
0388     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0389         auto &&view = viewManager()->activeView();
0390         KateFileActions::openFilePropertiesDialog(this, view->document());
0391     });
0392     a->setWhatsThis(i18n("Deletes the file belonging to the current document."));
0393 
0394     a = actionCollection()->addAction(QStringLiteral("file_compare"));
0395     a->setText(i18n("Compare"));
0396     connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
0397         QMessageBox::information(this, i18n("Compare"), i18n("Use the Tabbar context menu to compare two documents"));
0398     });
0399     a->setWhatsThis(i18n("Shows a hint how to compare documents."));
0400 
0401     a = actionCollection()->addAction(QStringLiteral("file_close_orphaned"));
0402     a->setText(i18n("Close Orphaned"));
0403     connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::closeOrphaned);
0404     a->setWhatsThis(i18n("Close all documents in the file list that could not be reopened, because they are not accessible anymore."));
0405 
0406     a = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), m_viewManager, SLOT(slotDocumentClose()));
0407     a->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
0408     a->setWhatsThis(i18n("Close the current document."));
0409 
0410     a = actionCollection()->addAction(QStringLiteral("file_close_other"));
0411     a->setText(i18n("Close Other"));
0412     connect(a, SIGNAL(triggered()), this, SLOT(slotDocumentCloseOther()));
0413     a->setWhatsThis(i18n("Close other open documents."));
0414 
0415     a = actionCollection()->addAction(QStringLiteral("file_close_all"));
0416     a->setText(i18n("Clos&e All"));
0417     connect(a, &QAction::triggered, this, &KateMainWindow::slotDocumentCloseAll);
0418     a->setWhatsThis(i18n("Close all open documents."));
0419 
0420     a = actionCollection()->addAction(KStandardAction::Quit, QStringLiteral("file_quit"));
0421     // Qt::QueuedConnection: delay real shutdown, as we are inside menu action handling (bug #185708)
0422     connect(a, &QAction::triggered, this, &KateMainWindow::slotFileQuit, Qt::QueuedConnection);
0423     a->setWhatsThis(i18n("Close this window"));
0424 
0425     a = actionCollection()->addAction(QStringLiteral("view_new_view"));
0426     a->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
0427     a->setText(i18n("&New Window"));
0428     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_N));
0429     connect(a, &QAction::triggered, this, &KateMainWindow::newWindow);
0430     a->setWhatsThis(i18n("Create a new window."));
0431 
0432     m_showFullScreenAction = KStandardAction::fullScreen(nullptr, nullptr, this, this);
0433     actionCollection()->addAction(m_showFullScreenAction->objectName(), m_showFullScreenAction);
0434     actionCollection()->setDefaultShortcut(m_showFullScreenAction, Qt::Key_F11);
0435     connect(m_showFullScreenAction, &QAction::toggled, this, &KateMainWindow::slotFullScreen);
0436 
0437     documentOpenWith = new KActionMenu(i18n("Open W&ith"), this);
0438     actionCollection()->addAction(QStringLiteral("file_open_with"), documentOpenWith);
0439     documentOpenWith->setWhatsThis(i18n("Open the current document using another application registered for its file type, or an application of your choice."));
0440     connect(documentOpenWith->menu(), &QMenu::aboutToShow, this, &KateMainWindow::mSlotFixOpenWithMenu);
0441     connect(documentOpenWith->menu(), &QMenu::triggered, this, &KateMainWindow::slotOpenWithMenuAction);
0442 
0443     // no open with for KWrite ATM
0444     documentOpenWith->setVisible(KateApp::isKate());
0445 
0446     a = KStandardAction::keyBindings(this, SLOT(editKeys()), actionCollection());
0447     a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments."));
0448 
0449     a = KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection());
0450     a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s)."));
0451 
0452     QAction *settingsConfigure = KStandardAction::preferences(this, SLOT(slotConfigure()), actionCollection());
0453     settingsConfigure->setWhatsThis(i18n("Configure various aspects of this application and the editing component."));
0454 
0455     if (KateApp::self()->pluginManager()->pluginList().size() > 0) {
0456         a = actionCollection()->addAction(QStringLiteral("help_plugins_contents"));
0457         a->setText(i18n("&Plugins Handbook"));
0458         connect(a, &QAction::triggered, this, &KateMainWindow::pluginHelp);
0459         a->setWhatsThis(i18n("This shows help files for various available plugins."));
0460     }
0461 
0462     connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotWindowActivated);
0463     connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl);
0464     connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateBottomViewBar);
0465 
0466     // re-route signals to our wrapper
0467     connect(m_viewManager, &KateViewManager::viewChanged, m_wrapper, &KTextEditor::MainWindow::viewChanged);
0468     connect(m_viewManager, &KateViewManager::viewCreated, m_wrapper, &KTextEditor::MainWindow::viewCreated);
0469     connect(this, &KateMainWindow::unhandledShortcutOverride, m_wrapper, &KTextEditor::MainWindow::unhandledShortcutOverride);
0470 
0471     slotWindowActivated();
0472 
0473     // session actions, not for KWrite, create the full menu to be able to properly hide it
0474     if (KateApp::isKate()) {
0475         auto sessionsMenu = actionCollection()->addAction(QStringLiteral("sessions"));
0476         sessionsMenu->setText(i18n("Sess&ions"));
0477         sessionsMenu->setMenu(new QMenu(this));
0478 
0479         a = actionCollection()->addAction(QStringLiteral("sessions_new"));
0480         sessionsMenu->menu()->addAction(a);
0481         a->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
0482         a->setText(i18nc("Menu entry Session->New Session", "&New Session"));
0483         // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008)
0484         connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionNew, Qt::QueuedConnection);
0485 
0486         // recent sessions menu
0487         a = new KateSessionsAction(i18n("&Recent Sessions"), this, KateApp::self()->sessionManager(), false);
0488         sessionsMenu->menu()->addAction(a);
0489         actionCollection()->addAction(QStringLiteral("session_open_recent"), a);
0490 
0491         // session menu
0492         a = new KateSessionsAction(i18n("&All Sessions"), this, KateApp::self()->sessionManager(), true);
0493         sessionsMenu->menu()->addAction(a);
0494         actionCollection()->addAction(QStringLiteral("session_open_session"), a);
0495 
0496         a = actionCollection()->addAction(QStringLiteral("sessions_manage"));
0497         sessionsMenu->menu()->addAction(a);
0498         a->setIcon(QIcon::fromTheme(QStringLiteral("view-choose")));
0499         a->setText(i18n("&Manage Sessions..."));
0500         // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008)
0501         connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionManage, Qt::QueuedConnection);
0502 
0503         sessionsMenu->menu()->addSeparator();
0504 
0505         a = actionCollection()->addAction(QStringLiteral("sessions_save"));
0506         sessionsMenu->menu()->addAction(a);
0507         a->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
0508         a->setText(i18n("&Save Session"));
0509         connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSave);
0510 
0511         a = actionCollection()->addAction(QStringLiteral("sessions_save_as"));
0512         sessionsMenu->menu()->addAction(a);
0513         a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
0514         a->setText(i18n("Save Session &As..."));
0515         connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSaveAs);
0516     }
0517 
0518     // location history actions
0519     a = actionCollection()->addAction(QStringLiteral("view_history_back"));
0520     a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left")));
0521     a->setText(i18n("Go to Previous Location"));
0522     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_1));
0523     connect(a, &QAction::triggered, this, [this] {
0524         m_viewManager->activeViewSpace()->goBack();
0525     });
0526     connect(this->m_viewManager, &KateViewManager::historyBackEnabled, a, &QAction::setEnabled);
0527 
0528     a = actionCollection()->addAction(QStringLiteral("view_history_forward"));
0529     a->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
0530     a->setText(i18n("Go to Next Location"));
0531     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_1));
0532     connect(a, &QAction::triggered, this, [this] {
0533         m_viewManager->activeViewSpace()->goForward();
0534     });
0535     connect(this->m_viewManager, &KateViewManager::historyForwardEnabled, a, &QAction::setEnabled);
0536 
0537     a = actionCollection()->addAction(QStringLiteral("git_show_file_history"));
0538     a->setText(i18n("Show File Git History"));
0539     connect(a, &QAction::triggered, this, [this] {
0540         if (activeView()) {
0541             auto url = activeView()->document()->url();
0542             if (url.isValid() && url.isLocalFile()) {
0543                 FileHistory::showFileHistory(url.toLocalFile(), m_wrapper);
0544             }
0545         }
0546     });
0547 }
0548 
0549 void KateMainWindow::setupDiagnosticsView(KConfig *sconfig)
0550 {
0551     if (KateApp::isKWrite()) {
0552         return;
0553     }
0554 
0555     m_diagView = DiagnosticsView::instance(wrapper());
0556     m_diagView->readSessionConfig(KConfigGroup(sconfig, QStringLiteral("Kate Diagnostics")));
0557     // See comment in DiagnosticsView::DiagnosticsView()
0558     m_diagView->actionCollection()->addAssociatedWidget(m_viewManager);
0559 }
0560 
0561 void KateMainWindow::ensureHamburgerBarSize()
0562 {
0563     // Ensure the hamburger menu never gets pushed into the toolbar overflow
0564     // by setting a minimum size on the bar based on the button's size.
0565     if (auto *hamburgerBar = toolBar(QStringLiteral("hamburgerBar"))) {
0566         auto *hamburgerMenu = actionCollection()->action(QStringLiteral("hamburger_menu"));
0567         if (auto *hamburgerButton = hamburgerBar->widgetForAction(hamburgerMenu)) {
0568             int neededButtonWidth = hamburgerButton->minimumSizeHint().width();
0569             // Add toolbar margins.
0570             const QMargins combinedMargins = hamburgerBar->contentsMargins() + hamburgerBar->layout()->contentsMargins();
0571             neededButtonWidth += combinedMargins.left();
0572             neededButtonWidth += combinedMargins.right();
0573 
0574             // The dynamic spacer is also an action leading to spacing being added.
0575             // Not observable with Breeze but e.g. Fusion style has toolbar button spacing.
0576             if (hamburgerBar->actions().count() > 1) {
0577                 neededButtonWidth += hamburgerBar->layout()->spacing();
0578             }
0579 
0580             QSize minimumSize = hamburgerBar->minimumSize();
0581             minimumSize.setWidth(std::max(minimumSize.width(), neededButtonWidth));
0582             hamburgerBar->setMinimumSize(minimumSize);
0583         }
0584     }
0585 }
0586 
0587 void KateMainWindow::slotDocumentCloseAll()
0588 {
0589     if (!KateApp::self()->documentManager()->documentList().empty()
0590         && KMessageBox::warningContinueCancel(this,
0591                                               i18n("This will close all open documents. Are you sure you want to continue?"),
0592                                               i18n("Close all documents"),
0593                                               KStandardGuiItem::cont(),
0594                                               KStandardGuiItem::cancel(),
0595                                               QStringLiteral("closeAll"))
0596             != KMessageBox::Cancel) {
0597         if (queryClose_internal()) {
0598             KateApp::self()->documentManager()->closeAllDocuments(false);
0599         }
0600     }
0601 }
0602 
0603 void KateMainWindow::slotDocumentCloseOther(KTextEditor::Document *document)
0604 {
0605     if (KateApp::self()->documentManager()->documentList().size() > 1
0606         && KMessageBox::warningContinueCancel(this,
0607                                               i18n("This will close all open documents beside the current one. Are you sure you want to continue?"),
0608                                               i18n("Close all documents beside current one"),
0609                                               KStandardGuiItem::cont(),
0610                                               KStandardGuiItem::cancel(),
0611                                               QStringLiteral("closeOther"))
0612             != KMessageBox::Cancel) {
0613         if (queryClose_internal(document)) {
0614             KateApp::self()->documentManager()->closeOtherDocuments(document);
0615         }
0616     }
0617 }
0618 
0619 void KateMainWindow::slotDocumentCloseSelected(const QList<KTextEditor::Document *> &docList)
0620 {
0621     QList<KTextEditor::Document *> documents;
0622     for (KTextEditor::Document *doc : docList) {
0623         if (queryClose_internal(doc)) {
0624             documents.push_back(doc);
0625         }
0626     }
0627 
0628     KateApp::self()->documentManager()->closeDocuments(documents);
0629 }
0630 
0631 void KateMainWindow::slotDocumentCloseOther()
0632 {
0633     slotDocumentCloseOther(m_viewManager->activeView()->document());
0634 }
0635 
0636 bool KateMainWindow::queryClose_internal(KTextEditor::Document *doc, KateMainWindow *win)
0637 {
0638     // we want no auto saving during windows closing, we handle that explicitly
0639     KateSessionManager::AutoSaveBlocker blocker(KateApp::self()->sessionManager());
0640 
0641     const auto documentCount = KateApp::self()->documentManager()->documentList().size();
0642 
0643     if (!showModOnDiskPrompt(PromptEdited)) {
0644         return false;
0645     }
0646 
0647     std::vector<KTextEditor::Document *> modifiedDocuments = KateApp::self()->documentManager()->modifiedDocumentList();
0648 
0649     // filter out what the stashManager will itself stash
0650     const bool canStash = KateApp::self()->stashManager()->canStash();
0651     if (canStash) {
0652         modifiedDocuments.erase(std::remove_if(modifiedDocuments.begin(),
0653                                                modifiedDocuments.end(),
0654                                                [](auto doc) {
0655                                                    return KateApp::self()->stashManager()->willStashDoc(doc);
0656                                                }),
0657                                 modifiedDocuments.end());
0658     }
0659 
0660     // do we want to ignore some document?
0661     if (doc) {
0662         modifiedDocuments.erase(std::remove(modifiedDocuments.begin(), modifiedDocuments.end(), doc), modifiedDocuments.end());
0663     }
0664 
0665     // do we want to ignore all documents visible in other windows?
0666     if (win) {
0667         modifiedDocuments.erase(std::remove_if(modifiedDocuments.begin(),
0668                                                modifiedDocuments.end(),
0669                                                [win, canStash](auto doc) {
0670                                                    if (canStash && (doc->isModified() || doc->url().isEmpty())) {
0671                                                        return true;
0672                                                    }
0673                                                    return KateApp::self()->documentVisibleInOtherWindows(doc, win);
0674                                                }),
0675                                 modifiedDocuments.end());
0676     }
0677 
0678     // Remove all documents that can be closed without user confirmation
0679     modifiedDocuments.erase(std::remove_if(modifiedDocuments.begin(),
0680                                            modifiedDocuments.end(),
0681                                            [](KTextEditor::Document *d) {
0682                                                return !d->isModified() || (d->isEmpty() && d->url().isEmpty());
0683                                            }),
0684                             modifiedDocuments.end());
0685 
0686     bool shutdown = modifiedDocuments.empty();
0687     if (!shutdown) {
0688         shutdown = KateSaveModifiedDialog::queryClose(this, modifiedDocuments);
0689     }
0690 
0691     if (KateApp::self()->documentManager()->documentList().size() > documentCount) {
0692         KMessageBox::information(this, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted"));
0693         shutdown = false;
0694     }
0695 
0696     return shutdown;
0697 }
0698 
0699 /**
0700  * queryClose(), take care that after the last mainwindow the stuff is closed
0701  */
0702 bool KateMainWindow::queryClose()
0703 {
0704     // we want no auto saving during windows closing, we handle that explicitly
0705     KateSessionManager::AutoSaveBlocker blocker(KateApp::self()->sessionManager());
0706 
0707     // session saving, can we close all views ?
0708     // just test, not close them actually
0709     if (qApp->isSavingSession()) {
0710         return queryClose_internal();
0711     }
0712 
0713     // normal closing of window
0714     // if we are not the last window, just close the documents we own
0715     if (KateApp::self()->mainWindowsCount() > 1) {
0716         return winClosesDocuments() ? queryClose_internal(nullptr, this) : true;
0717     }
0718 
0719     // last one: check if we can close all documents, try run
0720     // and save docs if we really close down !
0721     if (queryClose_internal()) {
0722         KateApp::self()->sessionManager()->saveActiveSession(true);
0723         KateApp::self()->stashManager()->stashDocuments(KateApp::self()->sessionManager()->activeSession()->config(),
0724                                                         KateApp::self()->documentManager()->documentList());
0725         return true;
0726     }
0727 
0728     return false;
0729 }
0730 
0731 KateMainWindow *KateMainWindow::newWindow() const
0732 {
0733     // create new window with current session
0734     // derive size from current one
0735     auto win = KateApp::self()->newMainWindow(KateApp::self()->sessionManager()->activeSession()->config(), {}, true);
0736     win->resize(size());
0737     return win;
0738 }
0739 
0740 void KateMainWindow::slotEditToolbars()
0741 {
0742     KConfigGroup cfg(KSharedConfig::openConfig(), QStringLiteral("MainWindow"));
0743     saveMainWindowSettings(cfg);
0744 
0745     KEditToolBar dlg(factory(), this);
0746 
0747     connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KateMainWindow::slotNewToolbarConfig);
0748     dlg.exec();
0749 }
0750 
0751 void KateMainWindow::reloadXmlGui()
0752 {
0753     for (KTextEditor::Document *doc : KateApp::self()->documentManager()->documentList()) {
0754         doc->reloadXML();
0755         const auto views = doc->views();
0756         for (KTextEditor::View *view : views) {
0757             view->reloadXML();
0758         }
0759     }
0760 }
0761 
0762 void KateMainWindow::slotNewToolbarConfig()
0763 {
0764     applyMainWindowSettings(KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("MainWindow")));
0765 
0766     // we need to reload all View's XML Gui from disk to ensure toolbar
0767     // changes are applied to all views.
0768     reloadXmlGui();
0769 }
0770 
0771 void KateMainWindow::slotFileQuit()
0772 {
0773     KateApp::self()->shutdownKate(this);
0774 }
0775 
0776 void KateMainWindow::slotFileClose()
0777 {
0778     m_viewManager->slotDocumentClose();
0779 }
0780 
0781 void KateMainWindow::slotOpenDocument(const QUrl &url)
0782 {
0783     m_viewManager->openUrl(url, QString(), true);
0784 }
0785 
0786 void KateMainWindow::readOptions()
0787 {
0788     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0789 
0790     const KConfigGroup generalGroup(config, QStringLiteral("General"));
0791     m_modNotification = generalGroup.readEntry("Modified Notification", false);
0792     m_modCloseAfterLast = generalGroup.readEntry("Close After Last", KateApp::isKWrite());
0793     KateApp::self()->documentManager()->setSaveMetaInfos(generalGroup.readEntry("Save Meta Infos", true));
0794     KateApp::self()->documentManager()->setDaysMetaInfos(generalGroup.readEntry("Days Meta Infos", 30));
0795 
0796     KateApp::self()->stashManager()->setStashUnsavedChanges(generalGroup.readEntry("Stash unsaved file changes", false));
0797     KateApp::self()->stashManager()->setStashNewUnsavedFiles(generalGroup.readEntry("Stash new unsaved files", true));
0798 
0799     m_paShowPath->setChecked(generalGroup.readEntry("Show Full Path in Title", false));
0800     m_paShowStatusBar->setChecked(generalGroup.readEntry("Show Status Bar", true));
0801     m_paShowMenuBar->setChecked(generalGroup.readEntry("Show Menu Bar", true));
0802     m_paShowTabBar->setChecked(generalGroup.readEntry("Show Tab Bar", true));
0803     m_paShowUrlNavBar->setChecked(generalGroup.readEntry("Show Url Nav Bar", KateApp::isKate()));
0804 
0805     for (auto a : {m_paShowMenuBar, m_paShowTabBar, m_paShowPath, m_paShowUrlNavBar, m_paShowStatusBar}) {
0806         connect(a, &QAction::toggled, this, &KateMainWindow::saveOptions);
0807     }
0808 
0809     m_mouseButtonBackAction = (MouseBackButtonAction)generalGroup.readEntry("Mouse back button action", 0);
0810     m_mouseButtonForwardAction = (MouseForwardButtonAction)generalGroup.readEntry("Mouse forward button action", 0);
0811 
0812     // emit signal to hide/show statusbars
0813     toggleShowStatusBar();
0814     toggleShowTabBar();
0815     m_viewManager->setShowUrlNavBar(m_paShowUrlNavBar->isChecked());
0816 }
0817 
0818 void KateMainWindow::saveOptions()
0819 {
0820     KSharedConfig::Ptr config = KSharedConfig::openConfig();
0821 
0822     KConfigGroup generalGroup(config, QStringLiteral("General"));
0823 
0824     generalGroup.writeEntry("Save Meta Infos", KateApp::self()->documentManager()->getSaveMetaInfos());
0825 
0826     generalGroup.writeEntry("Days Meta Infos", KateApp::self()->documentManager()->getDaysMetaInfos());
0827 
0828     generalGroup.writeEntry("Show Full Path in Title", m_paShowPath->isChecked());
0829     generalGroup.writeEntry("Show Status Bar", m_paShowStatusBar->isChecked());
0830     generalGroup.writeEntry("Show Menu Bar", m_paShowMenuBar->isChecked());
0831     generalGroup.writeEntry("Show Tab Bar", m_paShowTabBar->isChecked());
0832     generalGroup.writeEntry("Show Url Nav Bar", m_paShowUrlNavBar->isChecked());
0833 }
0834 
0835 void KateMainWindow::toggleShowMenuBar(bool showMessage)
0836 {
0837     if (m_paShowMenuBar->isChecked()) {
0838         menuBar()->show();
0839         if (m_viewManager->activeView() && m_viewManager->activeView()->contextMenu()) {
0840             m_viewManager->activeView()->contextMenu()->removeAction(m_paShowMenuBar);
0841         }
0842     } else {
0843         // we have a hamburger button in the toolbar, we can avoid the message if that is still visible
0844         if (showMessage && toolBar()->isHidden()) {
0845             const QString accel = m_paShowMenuBar->shortcut().toString();
0846             KMessageBox::information(this,
0847                                      i18n("This will hide the menu bar completely."
0848                                           " You can show it again by typing %1.",
0849                                           accel),
0850                                      i18n("Hide menu bar"),
0851                                      QStringLiteral("HideMenuBarWarning"));
0852         }
0853         menuBar()->hide();
0854         if (m_viewManager->activeView() && m_viewManager->activeView()->contextMenu()) {
0855             m_viewManager->activeView()->contextMenu()->addAction(m_paShowMenuBar);
0856         }
0857     }
0858 }
0859 
0860 void KateMainWindow::toggleShowStatusBar()
0861 {
0862     // just hide or show the status bar stack
0863     statusBarStackedWidget()->setVisible(showStatusBar());
0864 }
0865 
0866 bool KateMainWindow::showStatusBar()
0867 {
0868     return m_paShowStatusBar->isChecked();
0869 }
0870 
0871 void KateMainWindow::toggleShowTabBar()
0872 {
0873     Q_EMIT tabBarToggled();
0874 }
0875 
0876 bool KateMainWindow::showTabBar()
0877 {
0878     return m_paShowTabBar->isChecked();
0879 }
0880 
0881 void KateMainWindow::slotWindowActivated()
0882 {
0883     if (m_viewManager->activeView()) {
0884         updateCaption(m_viewManager->activeView()->document());
0885     }
0886 
0887     // update proxy
0888     centralWidget()->setFocusProxy(m_viewManager->activeView());
0889 }
0890 
0891 void KateMainWindow::slotUpdateActionsNeedingUrl()
0892 {
0893     auto &&view = viewManager()->activeView();
0894     const bool hasUrl = view && !view->document()->url().isEmpty();
0895 
0896     action(QStringLiteral("file_copy_filepath"))->setEnabled(hasUrl);
0897     action(QStringLiteral("file_open_containing_folder"))->setEnabled(hasUrl);
0898     action(QStringLiteral("file_rename"))->setEnabled(hasUrl);
0899     action(QStringLiteral("file_delete"))->setEnabled(hasUrl);
0900     action(QStringLiteral("file_properties"))->setEnabled(hasUrl);
0901     documentOpenWith->setEnabled(hasUrl);
0902 }
0903 
0904 void KateMainWindow::dragEnterEvent(QDragEnterEvent *event)
0905 {
0906     if (!event->mimeData()) {
0907         return;
0908     }
0909     const bool accept = event->mimeData()->hasUrls() || event->mimeData()->hasText();
0910     event->setAccepted(accept);
0911 }
0912 
0913 void KateMainWindow::dropEvent(QDropEvent *event)
0914 {
0915     slotDropEvent(event);
0916 }
0917 
0918 void KateMainWindow::slotDropEvent(QDropEvent *event)
0919 {
0920     if (event->mimeData() == nullptr) {
0921         return;
0922     }
0923 
0924     //
0925     // are we dropping files?
0926     //
0927 
0928     if (event->mimeData()->hasUrls()) {
0929         QList<QUrl> textlist = event->mimeData()->urls();
0930 
0931         // Try to get the KTextEditor::View that sent this, and activate it, so that the file opens in the
0932         // view where it was dropped
0933         KTextEditor::View *kVsender = qobject_cast<KTextEditor::View *>(QObject::sender());
0934         if (kVsender != nullptr) {
0935             if (auto parent = kVsender->parent()) {
0936                 KateViewSpace *vs = qobject_cast<KateViewSpace *>(parent->parent());
0937                 if (vs != nullptr) {
0938                     m_viewManager->setActiveSpace(vs);
0939                 }
0940             }
0941         }
0942 
0943         for (const QUrl &url : qAsConst(textlist)) {
0944             // if url has no file component, try and recursively scan dir
0945             KFileItem kitem(url);
0946             kitem.setDelayedMimeTypes(true);
0947             if (kitem.isDir()) {
0948                 if (KMessageBox::questionTwoActions(this,
0949                                                     i18n("You dropped the directory %1 into Kate. "
0950                                                          "Do you want to open all files contained in it?",
0951                                                          url.url()),
0952                                                     i18nc("@title:window", "Open Files Recursively"),
0953                                                     KGuiItem(i18nc("@action:button", "Open All Files"), QStringLiteral("document-open")),
0954                                                     KStandardGuiItem::cancel())
0955                     == KMessageBox::PrimaryAction) {
0956                     KIO::ListJob *list_job = KIO::listRecursive(url, KIO::DefaultFlags, KIO::ListJob::ListFlags{});
0957                     connect(list_job, &KIO::ListJob::entries, this, &KateMainWindow::slotListRecursiveEntries);
0958                 }
0959             } else {
0960                 m_viewManager->openUrl(url);
0961             }
0962         }
0963     }
0964     //
0965     // or are we dropping text?
0966     //
0967     else if (event->mimeData()->hasText()) {
0968         KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc();
0969         doc->setText(event->mimeData()->text());
0970         m_viewManager->activateView(doc);
0971     }
0972 }
0973 
0974 void KateMainWindow::slotListRecursiveEntries(KIO::Job *job, const KIO::UDSEntryList &list)
0975 {
0976     const QUrl dir = static_cast<KIO::SimpleJob *>(job)->url();
0977     for (const KIO::UDSEntry &entry : list) {
0978         if (!entry.isDir()) {
0979             QUrl url(dir);
0980             url = url.adjusted(QUrl::StripTrailingSlash);
0981             url.setPath(url.path() + QLatin1Char('/') + entry.stringValue(KIO::UDSEntry::UDS_NAME));
0982             m_viewManager->openUrl(url);
0983         }
0984     }
0985 }
0986 
0987 void KateMainWindow::editKeys()
0988 {
0989     KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
0990 
0991     const QList<KXMLGUIClient *> clients = guiFactory()->clients();
0992 
0993     for (KXMLGUIClient *client : clients) {
0994         // FIXME there appear to be invalid clients after session switching
0995         //     qCDebug(LOG_KATE)<<"adding client to shortcut editor";
0996         //     qCDebug(LOG_KATE)<<client;
0997         //     qCDebug(LOG_KATE)<<client->actionCollection();
0998         //     qCDebug(LOG_KATE)<<client->componentData().aboutData();
0999         //     qCDebug(LOG_KATE)<<client->componentData().aboutData()->programName();
1000         dlg.addCollection(client->actionCollection(), client->componentName());
1001     }
1002     dlg.configure();
1003 
1004     // reloadXML gui clients, to ensure all clients are up-to-date
1005     reloadXmlGui();
1006 }
1007 
1008 void KateMainWindow::openUrl(const QString &name)
1009 {
1010     m_viewManager->openUrl(QUrl(name));
1011 }
1012 
1013 void KateMainWindow::slotConfigure()
1014 {
1015     showPluginConfigPage(nullptr, 0);
1016 }
1017 
1018 bool KateMainWindow::showPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id)
1019 {
1020     KateConfigDialog *dlg = new KateConfigDialog(this);
1021 
1022     if (configpageinterface) {
1023         dlg->showAppPluginPage(configpageinterface, id);
1024     }
1025 
1026     if (dlg->exec() == QDialog::Accepted) {
1027         m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount());
1028     }
1029     delete dlg;
1030 
1031     m_viewManager->replugActiveView(); // gui (toolbars...) needs to be updated, because
1032     // of possible changes that the configure dialog
1033     // could have done on it, specially for plugins.
1034 
1035     return true;
1036 }
1037 
1038 QUrl KateMainWindow::activeDocumentUrl()
1039 {
1040     // anders: i make this one safe, as it may be called during
1041     // startup (by the file selector)
1042     KTextEditor::View *v = m_viewManager->activeView();
1043     if (v) {
1044         return v->document()->url();
1045     }
1046     return QUrl();
1047 }
1048 
1049 void KateMainWindow::mSlotFixOpenWithMenu()
1050 {
1051     KTextEditor::View *activeView = m_viewManager->activeView();
1052     if (!activeView) {
1053         return;
1054     }
1055 
1056     KTextEditor::Document *doc = activeView->document();
1057     if (!doc) {
1058         return;
1059     }
1060 
1061     KateFileActions::prepareOpenWithMenu(doc->url(), documentOpenWith->menu());
1062 }
1063 
1064 void KateMainWindow::slotOpenWithMenuAction(QAction *a)
1065 {
1066     auto activeView = m_viewManager->activeView();
1067     if (!activeView) {
1068         return;
1069     }
1070 
1071     auto doc = activeView->document();
1072     if (!doc) {
1073         return;
1074     }
1075 
1076     KateFileActions::showOpenWithMenu(this, doc->url(), a);
1077 }
1078 
1079 void KateMainWindow::pluginHelp()
1080 {
1081     KHelpClient::invokeHelp(QString(), QStringLiteral("kate-plugins"));
1082 }
1083 
1084 void KateMainWindow::slotFullScreen(bool t)
1085 {
1086     KToggleFullScreenAction::setFullScreen(this, t);
1087     QMenuBar *mb = menuBar();
1088     if (t) {
1089         QToolButton *b = new QToolButton(mb);
1090         b->setDefaultAction(m_showFullScreenAction);
1091         b->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Ignored));
1092         b->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
1093         mb->setCornerWidget(b, Qt::TopRightCorner);
1094         b->setVisible(true);
1095         b->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
1096     } else {
1097         QWidget *w = mb->cornerWidget(Qt::TopRightCorner);
1098         if (w) {
1099             w->deleteLater();
1100         }
1101     }
1102 }
1103 
1104 bool KateMainWindow::showModOnDiskPrompt(ModOnDiskMode mode)
1105 {
1106     const auto documents = KateApp::self()->documentManager()->documentList();
1107     QList<KTextEditor::Document *> list;
1108     list.reserve(documents.size());
1109     for (auto doc : documents) {
1110         if (KateApp::self()->documentManager()->documentInfo(doc)->modifiedOnDisc && (doc->isModified() || mode == PromptAll)) {
1111             list.append(doc);
1112         }
1113     }
1114 
1115     if (!list.isEmpty() && !m_modignore) {
1116         KateMwModOnHdDialog mhdlg(list, this);
1117         m_modignore = true;
1118         bool res = mhdlg.exec();
1119         m_modignore = false;
1120 
1121         return res;
1122     }
1123     return true;
1124 }
1125 
1126 void KateMainWindow::slotDocumentCreated(KTextEditor::Document *doc)
1127 {
1128     connect(doc, &KTextEditor::Document::modifiedChanged, this, QOverload<KTextEditor::Document *>::of(&KateMainWindow::updateCaption));
1129     connect(doc, &KTextEditor::Document::readWriteChanged, this, QOverload<KTextEditor::Document *>::of(&KateMainWindow::updateCaption));
1130     connect(doc, &KTextEditor::Document::documentNameChanged, this, QOverload<KTextEditor::Document *>::of(&KateMainWindow::updateCaption));
1131     connect(doc, &KTextEditor::Document::documentUrlChanged, this, QOverload<KTextEditor::Document *>::of(&KateMainWindow::updateCaption));
1132     connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl);
1133 
1134     updateCaption(doc);
1135 }
1136 
1137 void KateMainWindow::updateCaption()
1138 {
1139     if (m_viewManager->activeView()) {
1140         updateCaption(m_viewManager->activeView()->document());
1141     }
1142 }
1143 
1144 void KateMainWindow::updateCaption(KTextEditor::Document *doc)
1145 {
1146     if (!m_viewManager->activeView()) {
1147         setCaption(QString(), false);
1148         setWindowFilePath(QString());
1149         return;
1150     }
1151 
1152     // block signals from inactive docs
1153     if (m_viewManager->activeView()->document() != doc) {
1154         return;
1155     }
1156 
1157     QString c;
1158     const auto url = m_viewManager->activeView()->document()->url();
1159     if (m_viewManager->activeView()->document()->url().isEmpty() || (!m_paShowPath || !m_paShowPath->isChecked())) {
1160         c = m_viewManager->activeView()->document()->documentName();
1161     } else {
1162         // we want some filename @ folder output to have chance to keep important stuff even on elide
1163         c = Utils::niceFileNameWithPath(url);
1164     }
1165 
1166     setWindowFilePath(url.toString(QUrl::PreferLocalFile));
1167 
1168     QString sessName = KateApp::self()->sessionManager()->activeSession()->name();
1169     if (!sessName.isEmpty()) {
1170         sessName = QStringLiteral("%1: ").arg(sessName);
1171     }
1172 
1173     QString readOnlyCaption;
1174     if (!m_viewManager->activeView()->document()->isReadWrite()) {
1175         readOnlyCaption = i18n(" [read only]");
1176     }
1177 
1178     setCaption(sessName + c + readOnlyCaption + QStringLiteral(" [*]"), m_viewManager->activeView()->document()->isModified());
1179 }
1180 
1181 void KateMainWindow::saveProperties(KConfigGroup &config, bool includeViewConfig)
1182 {
1183     saveSession(config);
1184 
1185     // store all plugin view states
1186     int id = KateApp::self()->mainWindowID(this);
1187     const auto plugins = KateApp::self()->pluginManager()->pluginList();
1188     for (const KatePluginInfo &item : plugins) {
1189         if (item.plugin && pluginViews().contains(item.plugin)) {
1190             if (auto interface = qobject_cast<KTextEditor::SessionConfigInterface *>(pluginViews().value(item.plugin))) {
1191                 KConfigGroup group(config.config(), QStringLiteral("Plugin:%1:MainWindow:%2").arg(item.saveName()).arg(id));
1192                 interface->writeSessionConfig(group);
1193             }
1194         }
1195     }
1196 
1197     saveOpenRecent(config.config());
1198 
1199     // allow to skip the view manager config, this is needed for KWrite, see bug 463139
1200     if (includeViewConfig) {
1201         m_viewManager->saveViewConfiguration(config);
1202     }
1203 }
1204 
1205 void KateMainWindow::readProperties(const KConfigGroup &config)
1206 {
1207     // KDE5: TODO startRestore should take a const KConfigBase*, or even just a const KConfigGroup&,
1208     // but this propagates down to interfaces/kate/plugin.h so all plugins have to be ported
1209     KConfigBase *configBase = const_cast<KConfig *>(config.config());
1210     startRestore(configBase, config.name());
1211 
1212     // perhaps enable plugin guis
1213     KateApp::self()->pluginManager()->enableAllPluginsGUI(this, configBase);
1214 
1215     finishRestore();
1216 
1217     loadOpenRecent(config.config());
1218     m_viewManager->restoreViewConfiguration(config);
1219 }
1220 
1221 void KateMainWindow::saveOpenRecent(KConfig *config)
1222 {
1223     m_fileOpenRecent->saveEntries(KConfigGroup(config, QStringLiteral("Recent Files")));
1224 }
1225 
1226 void KateMainWindow::loadOpenRecent(const KConfig *config)
1227 {
1228     m_fileOpenRecent->loadEntries(KConfigGroup(config, QStringLiteral("Recent Files")));
1229 }
1230 
1231 void KateMainWindow::saveGlobalProperties(KConfig *sessionConfig)
1232 {
1233     KateApp::self()->documentManager()->saveDocumentList(sessionConfig);
1234 
1235     KConfigGroup cg(sessionConfig, QStringLiteral("General"));
1236     cg.writeEntry("Last Session", KateApp::self()->sessionManager()->activeSession()->name());
1237 
1238     // save plugin config !!
1239     KateApp::self()->pluginManager()->writeConfig(sessionConfig);
1240 
1241     if (m_diagView) {
1242         KConfigGroup cg(sessionConfig, QStringLiteral("Kate Diagnostics"));
1243         m_diagView->writeSessionConfig(cg);
1244     }
1245 }
1246 
1247 void KateMainWindow::saveWindowConfig(const KConfigGroup &_config)
1248 {
1249     KConfigGroup config(_config);
1250     saveMainWindowSettings(config);
1251     config.writeEntry("WindowState", static_cast<int>(windowState()));
1252     config.sync();
1253 }
1254 
1255 void KateMainWindow::restoreWindowConfig(const KConfigGroup &config)
1256 {
1257     setWindowState(Qt::WindowNoState);
1258     applyMainWindowSettings(config);
1259     setWindowState(QFlags<Qt::WindowState>(config.readEntry("WindowState", int(Qt::WindowActive))));
1260 }
1261 
1262 void KateMainWindow::slotUpdateBottomViewBar()
1263 {
1264     // get active view if any, if none => just hide bar
1265     KTextEditor::View *view = m_viewManager->activeView();
1266     if (!view) {
1267         if (auto wid = m_bottomContainerStack->currentWidget()) {
1268             wid->hide();
1269         }
1270         m_bottomViewBarContainer->hide();
1271         return;
1272     }
1273 
1274     // get bar state, we must have a bar widget here, KateView always creates one!
1275     BarState &bs = m_bottomViewBarMapping[view];
1276     Q_ASSERT(bs.bar());
1277 
1278     // extract statusbar if not already done
1279     if (!bs.statusBar()) {
1280         // we search for the status bar by class, this MUST work, we ensure that it is enabled in the view
1281         const auto widgets = bs.bar()->findChildren<QWidget *>(QString(), Qt::FindChildrenRecursively);
1282         for (auto *w : widgets) {
1283             if (w && w->metaObject()->className() == QByteArrayLiteral("KateStatusBar")) {
1284                 bs.setStatusBar(w);
1285             }
1286         }
1287         Q_ASSERT(bs.statusBar());
1288 
1289         // ensure we don't mess up the vertical sizing
1290         bs.statusBar()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
1291 
1292         // add the status bar to our status bar stack, there we will show/hide it
1293         statusBarStackedWidget()->addWidget(bs.statusBar());
1294     }
1295 
1296     // always activate the current statusbar
1297     Q_ASSERT(bs.statusBar()->parent() == statusBarStackedWidget());
1298     statusBarStackedWidget()->setCurrentWidget(bs.statusBar());
1299 
1300     // hide or show the bar
1301     if (bs.state()) {
1302         m_bottomContainerStack->setCurrentWidget(bs.bar());
1303         m_bottomContainerStack->currentWidget()->show();
1304         m_bottomViewBarContainer->show();
1305     } else {
1306         if (auto wid = m_bottomContainerStack->currentWidget()) {
1307             wid->hide();
1308         }
1309         m_bottomViewBarContainer->hide();
1310     }
1311 }
1312 
1313 void KateMainWindow::queueModifiedOnDisc(KTextEditor::Document *doc)
1314 {
1315     if (!m_modNotification) {
1316         return;
1317     }
1318 
1319     KateDocumentInfo *docInfo = KateApp::self()->documentManager()->documentInfo(doc);
1320     if (!docInfo) {
1321         return;
1322     }
1323     bool modOnDisk = static_cast<uint>(docInfo->modifiedOnDisc);
1324 
1325     if (s_modOnHdDialog == nullptr && modOnDisk) {
1326         QList<KTextEditor::Document *> list;
1327         list.append(doc);
1328 
1329         s_modOnHdDialog = new KateMwModOnHdDialog(list, this);
1330         m_modignore = true;
1331         connect(s_modOnHdDialog, &KateMwModOnHdDialog::requestOpenDiffDocument, KateApp::self(), [](const QUrl &url) {
1332             // use open with isTempFile == true
1333             KateApp::self()->openDocUrl(url, QString(), true);
1334         });
1335 
1336         // Someone modified a doc outside and now we are here
1337         // but Kate isn't the active app. Delay the dialog exec
1338         // otherwise it will bring us front interrupting user's
1339         // work.
1340         if (qApp->applicationState() != Qt::ApplicationActive) {
1341             m_modignore = false;
1342             s_modOnHdDialog->setShowOnWindowActivation(true);
1343             // hopefully this shows an alert to the user in task bar
1344             // that something changed in Kate
1345             qApp->alert(this, 3000);
1346             return;
1347         }
1348 
1349         s_modOnHdDialog->exec();
1350         delete s_modOnHdDialog; // s_modOnHdDialog is set to 0 in destructor of KateMwModOnHdDialog (jowenn!!!)
1351         m_modignore = false;
1352     } else if (s_modOnHdDialog != nullptr) {
1353         s_modOnHdDialog->addDocument(doc);
1354     }
1355 }
1356 
1357 bool KateMainWindow::event(QEvent *e)
1358 {
1359     if (e->type() == QEvent::ShortcutOverride) {
1360         QKeyEvent *k = static_cast<QKeyEvent *>(e);
1361         Q_EMIT unhandledShortcutOverride(k);
1362 
1363         if (KateApp::isKate() && k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) {
1364             if (!m_toolViewOutput->isHidden()) {
1365                 hideToolView(m_toolViewOutput);
1366             }
1367         }
1368     }
1369 
1370     return KateMDI::MainWindow::event(e);
1371 }
1372 
1373 QObject *KateMainWindow::pluginView(const QString &name)
1374 {
1375     KTextEditor::Plugin *plugin = KateApp::self()->pluginManager()->plugin(name);
1376     if (!plugin) {
1377         return nullptr;
1378     }
1379 
1380     return m_pluginViews.contains(plugin) ? m_pluginViews.value(plugin) : nullptr;
1381 }
1382 
1383 bool KateMainWindow::addWidget(QWidget *widget)
1384 {
1385     if (!widget) {
1386         qWarning() << Q_FUNC_INFO << "Unexpected null widget!";
1387         return false;
1388     }
1389 
1390     auto vs = m_viewManager->activeViewSpace();
1391     vs->addWidgetAsTab(widget);
1392     Q_EMIT widgetAdded(widget);
1393     m_viewManager->activateView(widget);
1394     return true;
1395 }
1396 
1397 bool KateMainWindow::removeWidget(QWidget *widget)
1398 {
1399     return m_viewManager->removeWidget(widget);
1400 }
1401 
1402 QWidget *KateMainWindow::activeWidget()
1403 {
1404     auto vs = m_viewManager->activeViewSpace();
1405     if (auto w = vs->currentWidget()) {
1406         return w;
1407     }
1408     return activeView();
1409 }
1410 
1411 void KateMainWindow::activateWidget(QWidget *widget)
1412 {
1413     if (!m_viewManager->activateWidget(widget)) {
1414         addWidget(widget);
1415     }
1416 }
1417 
1418 void KateMainWindow::showMessage(const QVariantMap &map)
1419 {
1420     if (!m_outputView) {
1421         return;
1422     }
1423     m_outputView->slotMessage(map);
1424 }
1425 
1426 void KateMainWindow::addPositionToHistory(const QUrl &url, KTextEditor::Cursor c)
1427 {
1428     m_viewManager->addPositionToHistory(url, c);
1429 }
1430 
1431 QWidgetList KateMainWindow::widgets() const
1432 {
1433     return m_viewManager->widgets();
1434 }
1435 
1436 void KateMainWindow::mousePressEvent(QMouseEvent *e)
1437 {
1438     switch (e->button()) {
1439     case Qt::ForwardButton:
1440         handleForwardButtonAction();
1441         break;
1442     case Qt::BackButton:
1443         handleBackButtonAction();
1444         break;
1445     default:;
1446     }
1447 }
1448 
1449 void KateMainWindow::slotFocusPrevTab()
1450 {
1451     if (m_viewManager->activeViewSpace()) {
1452         m_viewManager->activeViewSpace()->focusPrevTab();
1453     }
1454 }
1455 
1456 void KateMainWindow::slotFocusNextTab()
1457 {
1458     if (m_viewManager->activeViewSpace()) {
1459         m_viewManager->activeViewSpace()->focusNextTab();
1460     }
1461 }
1462 
1463 void KateMainWindow::handleBackButtonAction()
1464 {
1465     if (m_viewManager->activeViewSpace()) {
1466         switch (m_mouseButtonBackAction) {
1467         case PreviousTab:
1468             m_viewManager->activeViewSpace()->focusPrevTab();
1469             break;
1470         case HistoryBack:
1471             m_viewManager->activeViewSpace()->goBack();
1472             break;
1473         default:;
1474         }
1475     }
1476 }
1477 
1478 void KateMainWindow::handleForwardButtonAction()
1479 {
1480     if (m_viewManager->activeViewSpace()) {
1481         switch (m_mouseButtonForwardAction) {
1482         case NextTab:
1483             m_viewManager->activeViewSpace()->focusNextTab();
1484             break;
1485         case HistoryForward:
1486             m_viewManager->activeViewSpace()->goForward();
1487             break;
1488         default:;
1489         }
1490     }
1491 }
1492 
1493 void KateMainWindow::slotQuickOpen()
1494 {
1495     /**
1496      * show quick open and pass focus to it
1497      */
1498     KateQuickOpen *quickOpen = new KateQuickOpen(this);
1499     centralWidget()->setFocusProxy(quickOpen);
1500     quickOpen->raise();
1501     quickOpen->show();
1502 }
1503 
1504 QWidget *KateMainWindow::createToolView(KTextEditor::Plugin *plugin,
1505                                         const QString &identifier,
1506                                         KTextEditor::MainWindow::ToolViewPosition pos,
1507                                         const QIcon &icon,
1508                                         const QString &text)
1509 {
1510     return KateMDI::MainWindow::createToolView(plugin, identifier, static_cast<KMultiTabBar::KMultiTabBarPosition>(pos), icon, text);
1511 }
1512 
1513 bool KateMainWindow::moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos)
1514 {
1515     if (!qobject_cast<KateMDI::ToolView *>(widget)) {
1516         return false;
1517     }
1518 
1519     return KateMDI::MainWindow::moveToolView(qobject_cast<KateMDI::ToolView *>(widget), static_cast<KMultiTabBar::KMultiTabBarPosition>(pos));
1520 }
1521 
1522 bool KateMainWindow::showToolView(QWidget *widget)
1523 {
1524     if (!qobject_cast<KateMDI::ToolView *>(widget)) {
1525         return false;
1526     }
1527 
1528     return KateMDI::MainWindow::showToolView(qobject_cast<KateMDI::ToolView *>(widget));
1529 }
1530 
1531 bool KateMainWindow::hideToolView(QWidget *widget)
1532 {
1533     if (!qobject_cast<KateMDI::ToolView *>(widget)) {
1534         return false;
1535     }
1536 
1537     return KateMDI::MainWindow::hideToolView(qobject_cast<KateMDI::ToolView *>(widget));
1538 }
1539 
1540 void KateMainWindow::addRecentOpenedFile(const QUrl &url)
1541 {
1542     // skip non-existing urls for untitled documents
1543     if (url.isEmpty()) {
1544         return;
1545     }
1546     // skip files in /subltmp
1547     if (url.path().startsWith(QDir::tempPath())) {
1548         return;
1549     }
1550 
1551     // to our local list, aka menu
1552     m_fileOpenRecent->addUrl(url);
1553 
1554     /** FIXME Disabled because this can be too slow 100ms/doc sometimes,
1555      renable when it is 0/ms again*/
1556     // to the global "Recent Document Menu", see bug 420504
1557     // KRecentDocument::add(url);
1558 }
1559 
1560 void KateMainWindow::onApplicationStateChanged(Qt::ApplicationState)
1561 {
1562     // After queueModifiedOnDisc, the app wasn't active but now it got
1563     // active. Show the dialog.
1564     if (s_modOnHdDialog && s_modOnHdDialog->showOnWindowActivation() && qApp->applicationState() == Qt::ApplicationActive) {
1565         // Must set this to false, we want only one exec to happen.
1566         s_modOnHdDialog->setShowOnWindowActivation(false);
1567         // do it on next event loop iteration to avoid blocking here
1568         QTimer::singleShot(1, this, [] {
1569             s_modOnHdDialog->exec();
1570             delete s_modOnHdDialog;
1571         });
1572     }
1573 }
1574 
1575 #include "moc_katemainwindow.cpp"