katemainwindow.cpp 46.2 KB
Newer Older
1
/* This file is part of the KDE project
2
3
4
5
   SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
   SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
   SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
   SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com>
6

7
   SPDX-License-Identifier: LGPL-2.0-only
8
9
*/

10
// BEGIN Includes
11
12
#include "katemainwindow.h"

13
14
#include "kateapp.h"
#include "katecolorschemechooser.h"
Alexander Lohnau's avatar
Alexander Lohnau committed
15
#include "katecommandbar.h"
16
#include "kateconfigdialog.h"
17
18
#include "kateconfigplugindialogpage.h"
#include "katedebug.h"
19
#include "katedocmanager.h"
20
21
#include "katefileactions.h"
#include "katemwmodonhddialog.h"
22
#include "katepluginmanager.h"
23
#include "katequickopen.h"
24
#include "katesavemodifieddialog.h"
25
#include "katesessionmanager.h"
26
#include "katesessionsaction.h"
27
#include "kateupdatedisabler.h"
28
#include "kateviewspace.h"
29

30
31
32
#include <KAboutData>
#include <KActionCollection>
#include <KActionMenu>
Christoph Cullmann's avatar
Christoph Cullmann committed
33
#include <KApplicationTrader>
34
#include <KConfigGroup>
Michal Humpula's avatar
Michal Humpula committed
35
#include <KEditToolBar>
Christoph Cullmann's avatar
Christoph Cullmann committed
36
#include <KHelpClient>
37
#include <KLocalizedString>
Michal Humpula's avatar
Michal Humpula committed
38
39
#include <KMessageBox>
#include <KMultiTabBar>
40
#include <KOpenWithDialog>
41
#include <KRecentDocument>
Michal Humpula's avatar
Michal Humpula committed
42
#include <KRecentFilesAction>
43
44
45
#include <KSharedConfig>
#include <KShortcutsDialog>
#include <KStandardAction>
Michal Humpula's avatar
Michal Humpula committed
46
47
#include <KToggleFullScreenAction>
#include <KToolBar>
Christoph Cullmann's avatar
Christoph Cullmann committed
48
#include <KWindowConfig>
49
#include <KWindowSystem>
50
#include <KXMLGUIFactory>
51

Ahmad Samir's avatar
Ahmad Samir committed
52
53
54
55
56
57
58
59
#include <kio_version.h>
#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
#include <KRun>
#else
#include <KIO/ApplicationLauncherJob>
#include <KIO/JobUiDelegate>
#endif

Michal Humpula's avatar
Michal Humpula committed
60
#include <KFileItem>
61
#include <KIO/Job>
Michal Humpula's avatar
Michal Humpula committed
62

63
#include <QApplication>
64
#include <QDesktopWidget>
65
66
67
#include <QDir>
#include <QFontDatabase>
#include <QList>
Michal Humpula's avatar
Michal Humpula committed
68
#include <QMenu>
69
#include <QMenuBar>
70
71
#include <QMimeData>
#include <QMimeDatabase>
72
#include <QStyle>
73
#include <QTimer>
74
#include <QToolButton>
75

76
#include <ktexteditor/sessionconfiginterface.h>
77

78
// END
79

80
KateMwModOnHdDialog *KateMainWindow::s_modOnHdDialog = nullptr;
81

82
83
KateContainerStackedLayout::KateContainerStackedLayout(QWidget *parent)
    : QStackedLayout(parent)
84
85
{
}
86
87
88

QSize KateContainerStackedLayout::sizeHint() const
{
89
90
91
92
    if (currentWidget()) {
        return currentWidget()->sizeHint();
    }
    return QStackedLayout::sizeHint();
93
94
95
96
}

QSize KateContainerStackedLayout::minimumSize() const
{
97
98
99
100
    if (currentWidget()) {
        return currentWidget()->minimumSize();
    }
    return QStackedLayout::minimumSize();
101
102
}

103
KateMainWindow::KateMainWindow(KConfig *sconfig, const QString &sgroup)
104
    : KateMDI::MainWindow(nullptr)
105
    , m_modignore(false)
106
    , m_wrapper(new KTextEditor::MainWindow(this))
107
{
108
109
110
    /**
     * we don't want any flicker here
     */
111
    KateUpdateDisabler disableUpdates(this);
112

113
114
115
116
117
118
119
    /**
     * get and set config revision
     */
    static const int currentConfigRevision = 10;
    const int readConfigRevision = KConfigGroup(KSharedConfig::openConfig(), "General").readEntry("Config Revision", 0);
    KConfigGroup(KSharedConfig::openConfig(), "General").writeEntry("Config Revision", currentConfigRevision);
    const bool firstStart = readConfigRevision < currentConfigRevision;
120

121
122
    // start session restore if needed
    startRestore(sconfig, sgroup);
123

124
125
    // setup most important actions first, needed by setupMainWindow
    setupImportantActions();
126

127
128
    // setup the most important widgets
    setupMainWindow();
129

130
131
    // setup the actions
    setupActions();
132

133
134
135
    setStandardToolBarMenuEnabled(true);
    setXMLFile(QStringLiteral("kateui.rc"));
    createShellGUI(true);
136

137
    // qCDebug(LOG_KATE) << "****************************************************************************" << sconfig;
138

139
140
    // register mainwindow in app
    KateApp::self()->addMainWindow(this);
141

142
    // enable plugin guis
Christoph Cullmann's avatar
Christoph Cullmann committed
143
    KateApp::self()->pluginManager()->enableAllPluginsGUI(this, sconfig);
144

145
    // caption update
146
147
    const auto documents = KateApp::self()->documentManager()->documentList();
    for (auto doc : documents) {
Christoph Cullmann's avatar
Christoph Cullmann committed
148
        slotDocumentCreated(doc);
149
    }
150

Laurent Montel's avatar
Laurent Montel committed
151
    connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateMainWindow::slotDocumentCreated);
152

153
    readOptions();
154

155
156
157
    if (sconfig) {
        m_viewManager->restoreViewConfiguration(KConfigGroup(sconfig, sgroup));
    }
158

159
    finishRestore();
160

161
    m_fileOpenRecent->loadEntries(KConfigGroup(sconfig, "Recent Files"));
162

163
    setAcceptDrops(true);
164

Christoph Cullmann's avatar
Christoph Cullmann committed
165
    connect(KateApp::self()->sessionManager(), SIGNAL(sessionChanged()), this, SLOT(updateCaption()));
166

Laurent Montel's avatar
Laurent Montel committed
167
    connect(this, &KateMDI::MainWindow::sigShowPluginConfigPage, this, &KateMainWindow::showPluginConfigPage);
168
169
170
171

    // prior to this there was (possibly) no view, therefore not context menu.
    // Hence, we have to take care of the menu bar here
    toggleShowMenuBar(false);
172

173
    // on first start: deactivate toolbar
174
    if (firstStart) {
Laurent Montel's avatar
Laurent Montel committed
175
        toolBar(QStringLiteral("mainToolBar"))->hide();
176
    }
177

178
179
180
    // in all cases: avoid that arbitrary plugin toolviews get focus, like terminal, bug 412227
    // we need to delay this a bit due to lazy view creation (and lazy e.g. terminal widget creation)
    QTimer::singleShot(0, centralWidget(), SLOT(setFocus()));
181
182
183
184
}

KateMainWindow::~KateMainWindow()
{
185
186
187
188
189
190
191
192
193
194
195
    // first, save our fallback window size ;)
    KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow");
    KWindowConfig::saveWindowSize(windowHandle(), cfg);

    // save other options ;=)
    saveOptions();

    // unregister mainwindow in app
    KateApp::self()->removeMainWindow(this);

    // disable all plugin guis, delete all pluginViews
Christoph Cullmann's avatar
Christoph Cullmann committed
196
    KateApp::self()->pluginManager()->disableAllPluginsGUI(this);
197
198
199

    // delete the view manager, before KateMainWindow's wrapper is dead
    delete m_viewManager;
200
    m_viewManager = nullptr;
201
202
203

    // kill the wrapper object, now that all views are dead
    delete m_wrapper;
204
    m_wrapper = nullptr;
205
206
}

207
208
209
210
211
212
213
214
QSize KateMainWindow::sizeHint() const
{
    /**
     * have some useful size hint, else we have mini windows per default
     */
    return (QSize(640, 480).expandedTo(minimumSizeHint()));
}

215
void KateMainWindow::setupImportantActions()
216
{
217
218
    m_paShowStatusBar = KStandardAction::showStatusbar(this, SLOT(toggleShowStatusBar()), actionCollection());
    m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar"));
219
    m_paShowMenuBar = KStandardAction::showMenubar(this, SLOT(toggleShowMenuBar()), actionCollection());
220

Christoph Cullmann's avatar
Christoph Cullmann committed
221
    m_paShowTabBar = new KToggleAction(i18n("Show &Tabs"), this);
Christoph Cullmann's avatar
Christoph Cullmann committed
222
    actionCollection()->addAction(QStringLiteral("settings_show_tab_bar"), m_paShowTabBar);
Laurent Montel's avatar
Laurent Montel committed
223
    connect(m_paShowTabBar, &QAction::toggled, this, &KateMainWindow::toggleShowTabBar);
Christoph Cullmann's avatar
Christoph Cullmann committed
224
    m_paShowTabBar->setWhatsThis(i18n("Use this command to show or hide the tabs for the views"));
225

226
227
228
229
    m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this);
    actionCollection()->addAction(QStringLiteral("settings_show_full_path"), m_paShowPath);
    connect(m_paShowPath, SIGNAL(toggled(bool)), this, SLOT(updateCaption()));
    m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption"));
230

231
232
233
    // Load themes
    actionCollection()->addAction(QStringLiteral("colorscheme_menu"), new KateColorSchemeChooser(actionCollection()));

234
    QAction *a = actionCollection()->addAction(KStandardAction::Back, QStringLiteral("view_prev_tab"));
235
236
    a->setText(i18n("&Previous Tab"));
    a->setWhatsThis(i18n("Focus the previous tab."));
237
    actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabPrev());
Laurent Montel's avatar
Laurent Montel committed
238
    connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusPrevTab);
239
240
241
242

    a = actionCollection()->addAction(KStandardAction::Forward, QStringLiteral("view_next_tab"));
    a->setText(i18n("&Next Tab"));
    a->setWhatsThis(i18n("Focus the next tab."));
243
    actionCollection()->setDefaultShortcuts(a, a->shortcuts() << KStandardShortcut::tabNext());
Laurent Montel's avatar
Laurent Montel committed
244
    connect(a, &QAction::triggered, this, &KateMainWindow::slotFocusNextTab);
245

246
    // the quick open action is used by the KateViewSpace "quick open button"
247
    a = actionCollection()->addAction(QStringLiteral("view_quick_open"));
248
249
    a->setIcon(QIcon::fromTheme(QStringLiteral("quickopen")));
    a->setText(i18n("&Quick Open"));
250
    actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_O));
Laurent Montel's avatar
Laurent Montel committed
251
    connect(a, &QAction::triggered, this, &KateMainWindow::slotQuickOpen);
252
    a->setWhatsThis(i18n("Open a form to quick open documents."));
Waqar Ahmed's avatar
Waqar Ahmed committed
253

254
    // kate command bar
Waqar Ahmed's avatar
Waqar Ahmed committed
255
256
257
    a = actionCollection()->addAction(QStringLiteral("view_commandbar_open"));
    actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_I));
    connect(a, &QAction::triggered, this, &KateMainWindow::slotCommandBarOpen);
258
259
}

260
void KateMainWindow::setupMainWindow()
261
{
262
    setToolViewStyle(KMultiTabBar::KDEV3ICON);
263

264
265
266
267
268
269
    /**
     * create central stacked widget with its children
     */
    m_mainStackedWidget = new QStackedWidget(centralWidget());
    centralWidget()->layout()->addWidget(m_mainStackedWidget);
    (static_cast<QBoxLayout *>(centralWidget()->layout()))->setStretchFactor(m_mainStackedWidget, 100);
270

271
272
    m_viewManager = new KateViewManager(m_mainStackedWidget, this);
    m_mainStackedWidget->addWidget(m_viewManager);
273

274
275
    // make view manager default visible!
    m_mainStackedWidget->setCurrentWidget(m_viewManager);
276

277
278
279
    m_bottomViewBarContainer = new QWidget(centralWidget());
    centralWidget()->layout()->addWidget(m_bottomViewBarContainer);
    m_bottomContainerStack = new KateContainerStackedLayout(m_bottomViewBarContainer);
280
281
282
283
}

void KateMainWindow::setupActions()
{
284
285
    QAction *a;

Alexander Lohnau's avatar
Alexander Lohnau committed
286
287
288
289
290
291
    actionCollection()
        ->addAction(KStandardAction::New, QStringLiteral("file_new"), m_viewManager, SLOT(slotDocumentNew()))
        ->setWhatsThis(i18n("Create a new document"));
    actionCollection()
        ->addAction(KStandardAction::Open, QStringLiteral("file_open"), m_viewManager, SLOT(slotDocumentOpen()))
        ->setWhatsThis(i18n("Open an existing document for editing"));
292
293

    m_fileOpenRecent = KStandardAction::openRecent(m_viewManager, SLOT(openUrl(QUrl)), this);
294
    m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount());
295
296
297
298
299
300
    actionCollection()->addAction(m_fileOpenRecent->objectName(), m_fileOpenRecent);
    m_fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));

    a = actionCollection()->addAction(QStringLiteral("file_save_all"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-all")));
    a->setText(i18n("Save A&ll"));
301
    actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::Key_L));
Laurent Montel's avatar
Laurent Montel committed
302
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::saveAll);
303
304
305
306
    a->setWhatsThis(i18n("Save all open, modified documents to disk."));

    a = actionCollection()->addAction(QStringLiteral("file_reload_all"));
    a->setText(i18n("&Reload All"));
Laurent Montel's avatar
Laurent Montel committed
307
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::reloadAll);
308
309
    a->setWhatsThis(i18n("Reload all open documents."));

310
311
312
    a = actionCollection()->addAction(QStringLiteral("file_copy_filepath"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy")));
    a->setText(i18n("Copy File &Path"));
313
314
315
316
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        auto &&view = viewManager()->activeView();
        KateFileActions::copyFilePathToClipboard(view->document());
    });
317
318
319
320
321
    a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));

    a = actionCollection()->addAction(QStringLiteral("file_open_containing_folder"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-open-folder")));
    a->setText(i18n("&Open Containing Folder"));
322
323
324
325
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        auto &&view = viewManager()->activeView();
        KateFileActions::openContainingFolder(view->document());
    });
326
327
328
329
    a->setWhatsThis(i18n("Copies the file path of the current file to clipboard."));

    a = actionCollection()->addAction(QStringLiteral("file_rename"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename")));
330
    a->setText(i18nc("@action:inmenu", "Rename..."));
331
332
333
334
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        auto &&view = viewManager()->activeView();
        KateFileActions::renameDocumentFile(this, view->document());
    });
335
336
337
    a->setWhatsThis(i18n("Renames the file belonging to the current document."));

    a = actionCollection()->addAction(QStringLiteral("file_delete"));
338
    a->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
339
    a->setText(i18nc("@action:inmenu", "Delete"));
340
341
342
343
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        auto &&view = viewManager()->activeView();
        KateFileActions::deleteDocumentFile(this, view->document());
    });
344
345
346
347
348
    a->setWhatsThis(i18n("Deletes the file belonging to the current document."));

    a = actionCollection()->addAction(QStringLiteral("file_properties"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("dialog-object-properties")));
    a->setText(i18n("Properties"));
349
350
351
352
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        auto &&view = viewManager()->activeView();
        KateFileActions::openFilePropertiesDialog(view->document());
    });
353
354
355
356
    a->setWhatsThis(i18n("Deletes the file belonging to the current document."));

    a = actionCollection()->addAction(QStringLiteral("file_compare"));
    a->setText(i18n("Compare"));
Alexander Lohnau's avatar
Alexander Lohnau committed
357
358
359
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), [this]() {
        QMessageBox::information(this, i18n("Compare"), i18n("Use the Tabbar context menu to compare two documents"));
    });
360
361
    a->setWhatsThis(i18n("Shows a hint how to compare documents."));

362
363
    a = actionCollection()->addAction(QStringLiteral("file_close_orphaned"));
    a->setText(i18n("Close Orphaned"));
Laurent Montel's avatar
Laurent Montel committed
364
    connect(a, &QAction::triggered, KateApp::self()->documentManager(), &KateDocManager::closeOrphaned);
365
366
    a->setWhatsThis(i18n("Close all documents in the file list that could not be reopened, because they are not accessible anymore."));

367
368
369
    a = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), m_viewManager, SLOT(slotDocumentClose()));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
    a->setWhatsThis(i18n("Close the current document."));
370
371
372
373
374
375
376
377

    a = actionCollection()->addAction(QStringLiteral("file_close_other"));
    a->setText(i18n("Close Other"));
    connect(a, SIGNAL(triggered()), this, SLOT(slotDocumentCloseOther()));
    a->setWhatsThis(i18n("Close other open documents."));

    a = actionCollection()->addAction(QStringLiteral("file_close_all"));
    a->setText(i18n("Clos&e All"));
Laurent Montel's avatar
Laurent Montel committed
378
    connect(a, &QAction::triggered, this, &KateMainWindow::slotDocumentCloseAll);
379
380
381
382
    a->setWhatsThis(i18n("Close all open documents."));

    a = actionCollection()->addAction(KStandardAction::Quit, QStringLiteral("file_quit"));
    // Qt::QueuedConnection: delay real shutdown, as we are inside menu action handling (bug #185708)
Laurent Montel's avatar
Laurent Montel committed
383
    connect(a, &QAction::triggered, this, &KateMainWindow::slotFileQuit, Qt::QueuedConnection);
384
385
386
387
388
    a->setWhatsThis(i18n("Close this window"));

    a = actionCollection()->addAction(QStringLiteral("view_new_view"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
    a->setText(i18n("&New Window"));
Laurent Montel's avatar
Laurent Montel committed
389
    connect(a, &QAction::triggered, this, &KateMainWindow::newWindow);
390
391
    a->setWhatsThis(i18n("Create a new Kate view (a new window with the same document list)."));

392
    m_showFullScreenAction = KStandardAction::fullScreen(nullptr, nullptr, this, this);
393
    actionCollection()->addAction(m_showFullScreenAction->objectName(), m_showFullScreenAction);
Laurent Montel's avatar
Laurent Montel committed
394
    connect(m_showFullScreenAction, &QAction::toggled, this, &KateMainWindow::slotFullScreen);
395
396
397
398

    documentOpenWith = new KActionMenu(i18n("Open W&ith"), this);
    actionCollection()->addAction(QStringLiteral("file_open_with"), documentOpenWith);
    documentOpenWith->setWhatsThis(i18n("Open the current document using another application registered for its file type, or an application of your choice."));
Laurent Montel's avatar
Laurent Montel committed
399
400
    connect(documentOpenWith->menu(), &QMenu::aboutToShow, this, &KateMainWindow::mSlotFixOpenWithMenu);
    connect(documentOpenWith->menu(), &QMenu::triggered, this, &KateMainWindow::slotOpenWithMenuAction);
401
402
403
404
405
406
407
408
409
410

    a = KStandardAction::keyBindings(this, SLOT(editKeys()), actionCollection());
    a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments."));

    a = KStandardAction::configureToolbars(this, SLOT(slotEditToolbars()), actionCollection());
    a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s)."));

    QAction *settingsConfigure = KStandardAction::preferences(this, SLOT(slotConfigure()), actionCollection());
    settingsConfigure->setWhatsThis(i18n("Configure various aspects of this application and the editing component."));

Christoph Cullmann's avatar
Christoph Cullmann committed
411
    if (KateApp::self()->pluginManager()->pluginList().count() > 0) {
412
413
        a = actionCollection()->addAction(QStringLiteral("help_plugins_contents"));
        a->setText(i18n("&Plugins Handbook"));
Laurent Montel's avatar
Laurent Montel committed
414
        connect(a, &QAction::triggered, this, &KateMainWindow::pluginHelp);
415
416
417
        a->setWhatsThis(i18n("This shows help files for various available plugins."));
    }

Laurent Montel's avatar
Laurent Montel committed
418
419
    connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotWindowActivated);
    connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateOpenWith);
420
    connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateActionsNeedingUrl);
Laurent Montel's avatar
Laurent Montel committed
421
    connect(m_viewManager, &KateViewManager::viewChanged, this, &KateMainWindow::slotUpdateBottomViewBar);
422
423

    // re-route signals to our wrapper
Laurent Montel's avatar
Laurent Montel committed
424
425
426
    connect(m_viewManager, &KateViewManager::viewChanged, m_wrapper, &KTextEditor::MainWindow::viewChanged);
    connect(m_viewManager, &KateViewManager::viewCreated, m_wrapper, &KTextEditor::MainWindow::viewCreated);
    connect(this, &KateMainWindow::unhandledShortcutOverride, m_wrapper, &KTextEditor::MainWindow::unhandledShortcutOverride);
427
428
429
430
431
432

    slotWindowActivated();

    // session actions
    a = actionCollection()->addAction(QStringLiteral("sessions_new"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
Ayushmaan jangid's avatar
Ayushmaan jangid committed
433
    a->setText(i18nc("Menu entry Session->New Session", "&New Session"));
434
    // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008)
Laurent Montel's avatar
Laurent Montel committed
435
    connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionNew, Qt::QueuedConnection);
436
437
438
    a = actionCollection()->addAction(QStringLiteral("sessions_save"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-save")));
    a->setText(i18n("&Save Session"));
Laurent Montel's avatar
Laurent Montel committed
439
    connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSave);
440
441
442
    a = actionCollection()->addAction(QStringLiteral("sessions_save_as"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as")));
    a->setText(i18n("Save Session &As..."));
Laurent Montel's avatar
Laurent Montel committed
443
    connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionSaveAs);
444
445
446
447
    a = actionCollection()->addAction(QStringLiteral("sessions_manage"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("view-choose")));
    a->setText(i18n("&Manage Sessions..."));
    // Qt::QueuedConnection to avoid deletion of code that is executed when reducing the amount of mainwindows. (bug #227008)
Laurent Montel's avatar
Laurent Montel committed
448
    connect(a, &QAction::triggered, KateApp::self()->sessionManager(), &KateSessionManager::sessionManage, Qt::QueuedConnection);
449
450
451
452

    // quick open menu ;)
    a = new KateSessionsAction(i18n("&Quick Open Session"), this);
    actionCollection()->addAction(QStringLiteral("sessions_list"), a);
453
454
455
456
}

void KateMainWindow::slotDocumentCloseAll()
{
Alexander Lohnau's avatar
Alexander Lohnau committed
457
458
459
460
461
462
463
464
    if (!KateApp::self()->documentManager()->documentList().empty()
        && KMessageBox::warningContinueCancel(this,
                                              i18n("This will close all open documents. Are you sure you want to continue?"),
                                              i18n("Close all documents"),
                                              KStandardGuiItem::cont(),
                                              KStandardGuiItem::cancel(),
                                              QStringLiteral("closeAll"))
            != KMessageBox::Cancel) {
465
        if (queryClose_internal()) {
Christoph Cullmann's avatar
Christoph Cullmann committed
466
            KateApp::self()->documentManager()->closeAllDocuments(false);
467
468
        }
    }
469
470
}

471
472
void KateMainWindow::slotDocumentCloseOther(KTextEditor::Document *document)
{
Alexander Lohnau's avatar
Alexander Lohnau committed
473
474
475
476
477
478
479
480
    if (KateApp::self()->documentManager()->documentList().size() > 1
        && KMessageBox::warningContinueCancel(this,
                                              i18n("This will close all open documents beside the current one. Are you sure you want to continue?"),
                                              i18n("Close all documents beside current one"),
                                              KStandardGuiItem::cont(),
                                              KStandardGuiItem::cancel(),
                                              QStringLiteral("closeOther"))
            != KMessageBox::Cancel) {
Christoph Cullmann's avatar
Christoph Cullmann committed
481
482
483
        if (queryClose_internal(document)) {
            KateApp::self()->documentManager()->closeOtherDocuments(document);
        }
484
    }
485
486
}

487
void KateMainWindow::slotDocumentCloseSelected(const QList<KTextEditor::Document *> &docList)
488
{
489
    QList<KTextEditor::Document *> documents;
490
    for (KTextEditor::Document *doc : docList) {
491
492
493
494
495
        if (queryClose_internal(doc)) {
            documents.append(doc);
        }
    }

Christoph Cullmann's avatar
Christoph Cullmann committed
496
    KateApp::self()->documentManager()->closeDocuments(documents);
497
498
}

499
500
void KateMainWindow::slotDocumentCloseOther()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
501
    slotDocumentCloseOther(m_viewManager->activeView()->document());
502
503
}

504
bool KateMainWindow::queryClose_internal(KTextEditor::Document *doc)
505
{
Christoph Cullmann's avatar
Christoph Cullmann committed
506
    int documentCount = KateApp::self()->documentManager()->documentList().size();
507

508
    if (!showModOnDiskPrompt(PromptEdited)) {
509
510
        return false;
    }
511

Christoph Cullmann's avatar
Christoph Cullmann committed
512
    QList<KTextEditor::Document *> modifiedDocuments = KateApp::self()->documentManager()->modifiedDocumentList();
513
514
    modifiedDocuments.removeAll(doc);
    bool shutdown = (modifiedDocuments.count() == 0);
515

516
517
518
    if (!shutdown) {
        shutdown = KateSaveModifiedDialog::queryClose(this, modifiedDocuments);
    }
519

Christoph Cullmann's avatar
Christoph Cullmann committed
520
    if (KateApp::self()->documentManager()->documentList().size() > documentCount) {
521
        KMessageBox::information(this, i18n("New file opened while trying to close Kate, closing aborted."), i18n("Closing Aborted"));
522
523
        shutdown = false;
    }
524

525
    return shutdown;
526
527
528
529
530
531
532
}

/**
 * queryClose(), take care that after the last mainwindow the stuff is closed
 */
bool KateMainWindow::queryClose()
{
533
534
535
536
537
    // session saving, can we close all views ?
    // just test, not close them actually
    if (qApp->isSavingSession()) {
        return queryClose_internal();
    }
538

539
540
541
542
543
    // normal closing of window
    // allow to close all windows until the last without restrictions
    if (KateApp::self()->mainWindowsCount() > 1) {
        return true;
    }
544

545
546
547
548
549
550
551
552
    // last one: check if we can close all documents, try run
    // and save docs if we really close down !
    if (queryClose_internal()) {
        KateApp::self()->sessionManager()->saveActiveSession(true);
        return true;
    }

    return false;
553
554
}

555
void KateMainWindow::newWindow()
556
{
557
    KateApp::self()->newMainWindow(KateApp::self()->sessionManager()->activeSession()->config());
558
559
560
561
}

void KateMainWindow::slotEditToolbars()
{
562
563
    KConfigGroup cfg(KSharedConfig::openConfig(), "MainWindow");
    saveMainWindowSettings(cfg);
564

565
    KEditToolBar dlg(factory());
566

Laurent Montel's avatar
Laurent Montel committed
567
    connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KateMainWindow::slotNewToolbarConfig);
568
    dlg.exec();
569
570
}

571
572
void KateMainWindow::reloadXmlGui()
{
573
    for (KTextEditor::Document *doc : KateApp::self()->documentManager()->documentList()) {
574
        doc->reloadXML();
575
        for (KTextEditor::View *view : doc->views()) {
576
577
578
579
580
            view->reloadXML();
        }
    }
}

581
582
void KateMainWindow::slotNewToolbarConfig()
{
583
    applyMainWindowSettings(KConfigGroup(KSharedConfig::openConfig(), "MainWindow"));
584

Yuri Chornoivan's avatar
Yuri Chornoivan committed
585
    // we need to reload all View's XML Gui from disk to ensure toolbar
586
587
    // changes are applied to all views.
    reloadXmlGui();
588
589
590
591
}

void KateMainWindow::slotFileQuit()
{
592
    KateApp::self()->shutdownKate(this);
593
594
}

Christoph Cullmann's avatar
erlend:    
Christoph Cullmann committed
595
596
void KateMainWindow::slotFileClose()
{
597
    m_viewManager->slotDocumentClose();
Christoph Cullmann's avatar
erlend:    
Christoph Cullmann committed
598
599
}

Laurent Montel's avatar
Laurent Montel committed
600
void KateMainWindow::slotOpenDocument(const QUrl &url)
601
{
602
    m_viewManager->openUrl(url, QString(), true, false);
603
604
}

605
void KateMainWindow::readOptions()
606
{
607
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
608

609
610
    const KConfigGroup generalGroup(config, "General");
    m_modNotification = generalGroup.readEntry("Modified Notification", false);
611
    m_modCloseAfterLast = generalGroup.readEntry("Close After Last", false);
Christoph Cullmann's avatar
Christoph Cullmann committed
612
613
    KateApp::self()->documentManager()->setSaveMetaInfos(generalGroup.readEntry("Save Meta Infos", true));
    KateApp::self()->documentManager()->setDaysMetaInfos(generalGroup.readEntry("Days Meta Infos", 30));
614

615
616
    m_paShowPath->setChecked(generalGroup.readEntry("Show Full Path in Title", false));
    m_paShowStatusBar->setChecked(generalGroup.readEntry("Show Status Bar", true));
617
    m_paShowMenuBar->setChecked(generalGroup.readEntry("Show Menu Bar", true));
Christoph Cullmann's avatar
Christoph Cullmann committed
618
    m_paShowTabBar->setChecked(generalGroup.readEntry("Show Tab Bar", true));
619

620
621
    // emit signal to hide/show statusbars
    toggleShowStatusBar();
Christoph Cullmann's avatar
Christoph Cullmann committed
622
    toggleShowTabBar();
623
624
}

625
void KateMainWindow::saveOptions()
626
{
627
    KSharedConfig::Ptr config = KSharedConfig::openConfig();
628

629
    KConfigGroup generalGroup(config, "General");
630

Christoph Cullmann's avatar
Christoph Cullmann committed
631
    generalGroup.writeEntry("Save Meta Infos", KateApp::self()->documentManager()->getSaveMetaInfos());
632

Christoph Cullmann's avatar
Christoph Cullmann committed
633
    generalGroup.writeEntry("Days Meta Infos", KateApp::self()->documentManager()->getDaysMetaInfos());
634

635
636
    generalGroup.writeEntry("Show Full Path in Title", m_paShowPath->isChecked());
    generalGroup.writeEntry("Show Status Bar", m_paShowStatusBar->isChecked());
637
    generalGroup.writeEntry("Show Menu Bar", m_paShowMenuBar->isChecked());
Christoph Cullmann's avatar
Christoph Cullmann committed
638
    generalGroup.writeEntry("Show Tab Bar", m_paShowTabBar->isChecked());
639
640
}

641
642
643
644
645
646
647
648
void KateMainWindow::toggleShowMenuBar(bool showMessage)
{
    if (m_paShowMenuBar->isChecked()) {
        menuBar()->show();
        removeMenuBarActionFromContextMenu();
    } else {
        if (showMessage) {
            const QString accel = m_paShowMenuBar->shortcut().toString();
649
650
651
652
653
654
            KMessageBox::information(this,
                                     i18n("This will hide the menu bar completely."
                                          " You can show it again by typing %1.",
                                          accel),
                                     i18n("Hide menu bar"),
                                     QStringLiteral("HideMenuBarWarning"));
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
        }
        menuBar()->hide();
        addMenuBarActionToContextMenu();
    }
}

void KateMainWindow::addMenuBarActionToContextMenu()
{
    if (m_viewManager->activeView()) {
        m_viewManager->activeView()->contextMenu()->addAction(m_paShowMenuBar);
    }
}

void KateMainWindow::removeMenuBarActionFromContextMenu()
{
    if (m_viewManager->activeView()) {
        m_viewManager->activeView()->contextMenu()->removeAction(m_paShowMenuBar);
    }
}

675
void KateMainWindow::toggleShowStatusBar()
676
{
677
    emit statusBarToggled();
678
679
}

680
bool KateMainWindow::showStatusBar()
681
{
682
    return m_paShowStatusBar->isChecked();
Christoph Cullmann's avatar
Christoph Cullmann committed
683
684
685
686
687
688
689
690
691
692
}

void KateMainWindow::toggleShowTabBar()
{
    emit tabBarToggled();
}

bool KateMainWindow::showTabBar()
{
    return m_paShowTabBar->isChecked();
693
694
}

695
void KateMainWindow::slotWindowActivated()
696
{
697
698
699
    if (m_viewManager->activeView()) {
        updateCaption(m_viewManager->activeView()->document());
    }
700

701
702
703
704
    // show view manager in any case
    if (m_mainStackedWidget->currentWidget() != m_viewManager) {
        m_mainStackedWidget->setCurrentWidget(m_viewManager);
    }
705

706
707
    // update proxy
    centralWidget()->setFocusProxy(m_viewManager->activeView());
708
709
710
711
}

void KateMainWindow::slotUpdateOpenWith()
{
712
713
714
715
716
    if (m_viewManager->activeView()) {
        documentOpenWith->setEnabled(!m_viewManager->activeView()->document()->url().isEmpty());
    } else {
        documentOpenWith->setEnabled(false);
    }
717
718
}

719
720
void KateMainWindow::slotUpdateActionsNeedingUrl()
{
721
    auto &&view = viewManager()->activeView();
722
723
724
725
726
727
728
729
730
    const bool hasUrl = view && !view->document()->url().isEmpty();

    action("file_copy_filepath")->setEnabled(hasUrl);
    action("file_open_containing_folder")->setEnabled(hasUrl);
    action("file_rename")->setEnabled(hasUrl);
    action("file_delete")->setEnabled(hasUrl);
    action("file_properties")->setEnabled(hasUrl);
}

731
void KateMainWindow::dragEnterEvent(QDragEnterEvent *event)
732
{
733
734
735
736
737
    if (!event->mimeData()) {
        return;
    }
    const bool accept = event->mimeData()->hasUrls() || event->mimeData()->hasText();
    event->setAccepted(accept);
738
739
}

740
void KateMainWindow::dropEvent(QDropEvent *event)
741
{
742
    slotDropEvent(event);
743
744
}

745
void KateMainWindow::slotDropEvent(QDropEvent *event)
746
{
747
    if (event->mimeData() == nullptr) {
748
        return;
749
    }
750

751
752
753
754
755
756
757
758
759
760
    //
    // are we dropping files?
    //

    if (event->mimeData()->hasUrls()) {
        QList<QUrl> textlist = event->mimeData()->urls();

        // Try to get the KTextEditor::View that sent this, and activate it, so that the file opens in the
        // view where it was dropped
        KTextEditor::View *kVsender = qobject_cast<KTextEditor::View *>(QObject::sender());
761
        if (kVsender != nullptr) {
762
            QWidget *parent = kVsender->parentWidget();
763
            if (parent != nullptr) {
764
                KateViewSpace *vs = qobject_cast<KateViewSpace *>(parent->parentWidget());
765
                if (vs != nullptr) {
766
767
768
769
770
                    m_viewManager->setActiveSpace(vs);
                }
            }
        }

771
        for (const QUrl &url : qAsConst(textlist)) {
772
773
774
775
            // if url has no file component, try and recursively scan dir
            KFileItem kitem(url);
            kitem.setDelayedMimeTypes(true);
            if (kitem.isDir()) {
776
777
                if (KMessageBox::questionYesNo(this,
                                               i18n("You dropped the directory %1 into Kate. "
778
779
                                                    "Do you want to load all files contained in it ?",
                                                    url.url()),
Alexander Lohnau's avatar
Alexander Lohnau committed
780
781
                                               i18n("Load files recursively?"))
                    == KMessageBox::Yes) {
782
                    KIO::ListJob *list_job = KIO::listRecursive(url, KIO::DefaultFlags, false);
783
                    connect(list_job, &KIO::ListJob::entries, this, &KateMainWindow::slotListRecursiveEntries);
784
                }
785
786
787
788
789
790
791
792
793
            } else {
                m_viewManager->openUrl(url);
            }
        }
    }
    //
    // or are we dropping text?
    //
    else if (event->mimeData()->hasText()) {
794
        KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc();
795
796
        doc->setText(event->mimeData()->text());
        m_viewManager->activateView(doc);
797
798
799
800
801
    }
}

void KateMainWindow::slotListRecursiveEntries(KIO::Job *job, const KIO::UDSEntryList &list)
{
802
    const QUrl dir = static_cast<KIO::SimpleJob *>(job)->url();
803
    for (const KIO::UDSEntry &entry : list) {
804
        if (!entry.isDir()) {
805
806
807
808
            QUrl url(dir);
            url = url.adjusted(QUrl::StripTrailingSlash);
            url.setPath(url.path() + QLatin1Char('/') + entry.stringValue(KIO::UDSEntry::UDS_NAME));
            m_viewManager->openUrl(url);
809
810
        }
    }
811
812
813
814
}

void KateMainWindow::editKeys()
{
815
    KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
816

817
    const QList<KXMLGUIClient *> clients = guiFactory()->clients();
818

819
    for (KXMLGUIClient *client : clients) {
820
        // FIXME there appear to be invalid clients after session switching
821
822
823
824
825
        //     qCDebug(LOG_KATE)<<"adding client to shortcut editor";
        //     qCDebug(LOG_KATE)<<client;
        //     qCDebug(LOG_KATE)<<client->actionCollection();
        //     qCDebug(LOG_KATE)<<client->componentData().aboutData();
        //     qCDebug(LOG_KATE)<<client->componentData().aboutData()->programName();
826
827
828
        dlg.addCollection(client->actionCollection(), client->componentName());
    }
    dlg.configure();
829

830
831
    // reloadXML gui clients, to ensure all clients are up-to-date
    reloadXmlGui();
832
833
}

834
void KateMainWindow::openUrl(const QString &name)
835
{
836
    m_viewManager->openUrl(QUrl(name));
837
838
839
}

void KateMainWindow::slotConfigure()
840
{
841
    showPluginConfigPage(nullptr, 0);
842
843
}

844
bool KateMainWindow::showPluginConfigPage(KTextEditor::Plugin *configpageinterface, int id)
845
{
846
    if (!m_viewManager->activeView()) {
847
        return false;
848
    }
849

850
    KateConfigDialog *dlg = new KateConfigDialog(this);
851
852
853
    if (configpageinterface) {
        dlg->showAppPluginPage(configpageinterface, id);
    }
854

855
    if (dlg->exec() == QDialog::Accepted) {
856
        m_fileOpenRecent->setMaxItems(KateConfigDialog::recentFilesMaxCount());
857
    }
858

859
    delete dlg;
860

861
862
863
    m_viewManager->reactivateActiveView(); // gui (toolbars...) needs to be updated, because
    // of possible changes that the configure dialog
    // could have done on it, specially for plugins.
864
865

    return true;
866
867
}

Michal Humpula's avatar
Michal Humpula committed
868
QUrl KateMainWindow::activeDocumentUrl()
869
{
870
871
872
873
874
875
876
    // anders: i make this one safe, as it may be called during
    // startup (by the file selector)
    KTextEditor::View *v = m_viewManager->activeView();
    if (v) {
        return v->document()->url();
    }
    return QUrl();
877
878
879
880
}

void KateMainWindow::mSlotFixOpenWithMenu()
{
881
    // dh: in bug #307699, this slot is called when launching the Kate application
Yuri Chornoivan's avatar
Yuri Chornoivan committed
882
    // unfortunately, no one ever could reproduce except users.
883
    KTextEditor::View *activeView = m_viewManager->activeView();
884
    if (!activeView) {
885
886
887
888
889
890
891
892
893
894
        return;
    }

    // cleanup menu
    QMenu *menu = documentOpenWith->menu();
    menu->clear();

    // get a list of appropriate services.
    QMimeDatabase db;
    QMimeType mime = db.mimeTypeForName(activeView->document()->mimeType());
895
    // qCDebug(LOG_KATE) << "mime type: " << mime.name();
896

897
    QAction *a = nullptr;
Nicolas Fella's avatar
Nicolas Fella committed
898
    const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
899
    // add all default open-with-actions except "Kate"
900
    for (const auto &service : offers) {
901
        if (service->name() == QLatin1String("Kate")) {
902
903
904
905
906
907
908
909
            continue;
        }
        a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
        a->setData(service->entryPath());
    }
    // append "Other..." to call the KDE "open with" dialog.
    a = documentOpenWith->menu()->addAction(i18n("&Other..."));
    a->setData(QString());
910
911
}

912
void KateMainWindow::slotOpenWithMenuAction(QAction *a)
913
{
Ahmad Samir's avatar
Ahmad Samir committed
914
915
    const QList<QUrl> list({m_viewManager->activeView()->document()->url()});
#if KIO_VERSION < QT_VERSION_CHECK(5, 71, 0)
916
917
918
919
920
921

    const QString openWith = a->data().toString();
    if (openWith.isEmpty()) {
        // display "open with" dialog
        KOpenWithDialog dlg(list);
        if (dlg.exec()) {
922
            KRun::runService(*dlg.service(), list, this);
923
924
925
926
927
928
        }
        return;
    }

    KService::Ptr app = KService::serviceByDesktopPath(openWith);
    if (app) {
929
        KRun::runService(*app, list, this);
930
931
932
    } else {
        KMessageBox::error(this, i18n("Application '%1' not found.", openWith), i18n("Application not found"));
    }
Ahmad Samir's avatar
Ahmad Samir committed
933
934
935
936
937
938
939
940
#else
    KService::Ptr app = KService::serviceByDesktopPath(a->data().toString());
    // If app is null, ApplicationLauncherJob will invoke the open-with dialog
    auto *job = new KIO::ApplicationLauncherJob(app);
    job->setUrls(list);
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
    job->start();
#endif
941
942
943
944
}

void KateMainWindow::pluginHelp()
{
945
    KHelpClient::invokeHelp(QString(), QStringLiteral("kate-plugins"));