dolphinmainwindow.cpp 110 KB
Newer Older
1
2
3
4
5
6
7
/*
 * SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz19@gmail.com>
 * SPDX-FileCopyrightText: 2006 Stefan Monov <logixoul@gmail.com>
 * SPDX-FileCopyrightText: 2006 Cvetoslav Ludmiloff <ludmiloff@gmail.com>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
Peter Penz's avatar
Peter Penz committed
8
9
10

#include "dolphinmainwindow.h"

11
#include "dolphinmainwindowadaptor.h"
Elvis Angelaccio's avatar
Elvis Angelaccio committed
12
#include "config-terminal.h"
13
#include "global.h"
David Hallas's avatar
David Hallas committed
14
#include "dolphinbookmarkhandler.h"
15
#include "dolphindockwidget.h"
16
#include "dolphincontextmenu.h"
17
#include "dolphinnavigatorswidgetaction.h"
Peter Penz's avatar
Peter Penz committed
18
#include "dolphinnewfilemenu.h"
19
#include "dolphinrecenttabsmenu.h"
20
#include "dolphinurlnavigatorscontroller.h"
21
#include "dolphinviewcontainer.h"
22
#include "dolphintabpage.h"
23
#include "middleclickactioneventfilter.h"
24
#include "panels/folders/folderspanel.h"
25
#include "panels/places/placesitemmodel.h"
26
27
#include "panels/places/placespanel.h"
#include "panels/information/informationpanel.h"
28
#include "panels/terminal/terminalpanel.h"
29
#include "settings/dolphinsettingsdialog.h"
30
#include "statusbar/dolphinstatusbar.h"
31
32
#include "views/dolphinviewactionhandler.h"
#include "views/dolphinremoteencoding.h"
33
#include "views/draganddrophelper.h"
34
#include "views/viewproperties.h"
35
#include "views/dolphinnewfilemenuobserver.h"
Peter Penz's avatar
Peter Penz committed
36
37
#include "dolphin_generalsettings.h"

Peter Penz's avatar
Peter Penz committed
38
39
#include <KActionCollection>
#include <KActionMenu>
40
#include <KAuthorized>
Peter Penz's avatar
Peter Penz committed
41
#include <KConfig>
42
#include <KConfigGui>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
43
#include <KDualAction>
Roman Inflianskas's avatar
Roman Inflianskas committed
44
#include <KFileItemListProperties>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
45
#include <KHelpMenu>
46
#include <KIO/CommandLauncherJob>
47
#include <KIO/JobUiDelegate>
48
#include <KIO/OpenFileManagerWindowJob>
Ahmad Samir's avatar
Ahmad Samir committed
49
#include <KIO/OpenUrlJob>
Roman Inflianskas's avatar
Roman Inflianskas committed
50
#include <KJobWidgets>
Laurent Montel's avatar
Laurent Montel committed
51
#include <KLocalizedString>
Peter Penz's avatar
Peter Penz committed
52
#include <KMessageBox>
53
#include <KNS3/KMoreToolsMenuFactory>
Roman Inflianskas's avatar
Roman Inflianskas committed
54
55
#include <KProtocolInfo>
#include <KProtocolManager>
Peter Penz's avatar
Peter Penz committed
56
57
#include <KShell>
#include <KStandardAction>
58
#include <KStartupInfo>
59
#include <KSycoca>
Peter Penz's avatar
Peter Penz committed
60
#include <KToggleAction>
Roman Inflianskas's avatar
Roman Inflianskas committed
61
#include <KToolBar>
62
#include <KToolBarPopupAction>
63
#include <KToolInvocation>
Laurent Montel's avatar
Laurent Montel committed
64
#include <KUrlComboBox>
Roman Inflianskas's avatar
Roman Inflianskas committed
65
#include <KUrlNavigator>
66
#include <KWindowSystem>
Peter Penz's avatar
Peter Penz committed
67

68
#include <QApplication>
69
#include <QClipboard>
Laurent Montel's avatar
Laurent Montel committed
70
#include <QCloseEvent>
71
#include <QDesktopServices>
72
#include <QDialog>
73
#include <QFileInfo>
Roman Inflianskas's avatar
Roman Inflianskas committed
74
75
76
77
78
79
80
#include <QLineEdit>
#include <QMenuBar>
#include <QPushButton>
#include <QShowEvent>
#include <QStandardPaths>
#include <QTimer>
#include <QToolButton>
81
#include <QWhatsThisClickedEvent>
Peter Penz's avatar
Peter Penz committed
82

83
84
85
namespace {
    // Used for GeneralSettings::version() to determine whether
    // an updated version of Dolphin is running.
Peter Penz's avatar
Peter Penz committed
86
    const int CurrentDolphinVersion = 200;
87
88
    // The maximum number of entries in the back/forward popup menu
    const int MaxNumberOfNavigationentries = 12;
89
90
    // The maximum number of "Activate Tab" shortcuts
    const int MaxActivateTabShortcuts = 9;
91
}
92

93
DolphinMainWindow::DolphinMainWindow() :
94
    KXmlGuiWindow(nullptr),
Kevin Funk's avatar
Kevin Funk committed
95
    m_newFileMenu(nullptr),
96
    m_helpMenu(nullptr),
Kevin Funk's avatar
Kevin Funk committed
97
98
99
100
    m_tabWidget(nullptr),
    m_activeViewContainer(nullptr),
    m_actionHandler(nullptr),
    m_remoteEncoding(nullptr),
101
    m_settingsDialog(),
David Hallas's avatar
David Hallas committed
102
    m_bookmarkHandler(nullptr),
Kevin Funk's avatar
Kevin Funk committed
103
104
    m_controlButton(nullptr),
    m_updateToolBarTimer(nullptr),
Ahmad Samir's avatar
Ahmad Samir committed
105
    m_lastHandleUrlOpenJob(nullptr),
Kevin Funk's avatar
Kevin Funk committed
106
107
    m_terminalPanel(nullptr),
    m_placesPanel(nullptr),
108
109
    m_tearDownFromPlacesRequested(false),
    m_backAction(nullptr),
110
    m_forwardAction(nullptr)
Peter Penz's avatar
Peter Penz committed
111
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
112
    Q_INIT_RESOURCE(dolphin);
113
114
115

    new MainWindowAdaptor(this);

116
117
118
#ifndef Q_OS_WIN
	setWindowFlags(Qt::WindowContextHelpButtonHint);
#endif
119
    setComponentName(QStringLiteral("dolphin"), QGuiApplication::applicationDisplayName());
120
    setObjectName(QStringLiteral("Dolphin#"));
121

122
123
    connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage,
            this, &DolphinMainWindow::showErrorMessage);
124

125
    KIO::FileUndoManager* undoManager = KIO::FileUndoManager::self();
126
    undoManager->setUiInterface(new UndoUiInterface());
Peter Penz's avatar
Peter Penz committed
127

128
    connect(undoManager, QOverload<bool>::of(&KIO::FileUndoManager::undoAvailable),
129
130
131
132
133
134
135
            this, &DolphinMainWindow::slotUndoAvailable);
    connect(undoManager, &KIO::FileUndoManager::undoTextChanged,
            this, &DolphinMainWindow::slotUndoTextChanged);
    connect(undoManager, &KIO::FileUndoManager::jobRecordingStarted,
            this, &DolphinMainWindow::clearStatusBar);
    connect(undoManager, &KIO::FileUndoManager::jobRecordingFinished,
            this, &DolphinMainWindow::showCommand);
136

137
    const bool firstRun = (GeneralSettings::version() < 200);
138
    if (firstRun) {
139
        GeneralSettings::setViewPropsTimestamp(QDateTime::currentDateTime());
140
141
142
143
    }

    setAcceptDrops(true);

144
145
146
    auto *navigatorsWidgetAction = new DolphinNavigatorsWidgetAction(this);
    actionCollection()->addAction(QStringLiteral("url_navigators"), navigatorsWidgetAction);
    m_tabWidget = new DolphinTabWidget(navigatorsWidgetAction, this);
147
    m_tabWidget->setObjectName("tabWidget");
148
149
150
151
152
    connect(m_tabWidget, &DolphinTabWidget::activeViewChanged,
            this, &DolphinMainWindow::activeViewChanged);
    connect(m_tabWidget, &DolphinTabWidget::tabCountChanged,
            this, &DolphinMainWindow::tabCountChanged);
    connect(m_tabWidget, &DolphinTabWidget::currentUrlChanged,
David Hallas's avatar
David Hallas committed
153
            this, &DolphinMainWindow::updateWindowTitle);
154
155
    setCentralWidget(m_tabWidget);

156
157
158
    setupActions();

    m_actionHandler = new DolphinViewActionHandler(actionCollection(), this);
159
    connect(m_actionHandler, &DolphinViewActionHandler::actionBeingHandled, this, &DolphinMainWindow::clearStatusBar);
160
    connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinMainWindow::createDirectory);
161
162

    m_remoteEncoding = new DolphinRemoteEncoding(this, m_actionHandler);
163
164
    connect(this, &DolphinMainWindow::urlChanged,
            m_remoteEncoding, &DolphinRemoteEncoding::slotAboutToOpenUrl);
165
166
167
168

    setupDockWidgets();

    setupGUI(Keys | Save | Create | ToolBar);
169
    stateChanged(QStringLiteral("new_file"));
170
171

    QClipboard* clipboard = QApplication::clipboard();
172
173
    connect(clipboard, &QClipboard::dataChanged,
            this, &DolphinMainWindow::updatePasteAction);
174

175
    QAction* toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter"));
176
    toggleFilterBarAction->setChecked(GeneralSettings::filterBar());
177
178
179
180

    if (firstRun) {
        menuBar()->setVisible(false);
        // Assure a proper default size if Dolphin runs the first time
Nate Graham's avatar
Nate Graham committed
181
        resize(760, 550);
182
183
184
185
186
187
    }

    const bool showMenu = !menuBar()->isHidden();
    QAction* showMenuBarAction = actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar));
    showMenuBarAction->setChecked(showMenu);  // workaround for bug #171080
    if (!showMenu) {
188
        createControlButton();
189
    }
190

191
192
    updateAllowedToolbarAreas();

193
194
195
196
    // enable middle-click on back/forward/up to open in a new tab
    auto *middleClickEventFilter = new MiddleClickActionEventFilter(this);
    connect(middleClickEventFilter, &MiddleClickActionEventFilter::actionMiddleClicked, this, &DolphinMainWindow::slotToolBarActionMiddleClicked);
    toolBar()->installEventFilter(middleClickEventFilter);
197
198

    setupWhatsThis();
199

200
201
202
    connect(KSycoca::self(), QOverload<>::of(&KSycoca::databaseChanged), this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);

    QTimer::singleShot(0, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);
Peter Penz's avatar
Peter Penz committed
203
204
205
206
207
208
}

DolphinMainWindow::~DolphinMainWindow()
{
}

209
QVector<DolphinViewContainer*> DolphinMainWindow::viewContainers() const
David Hallas's avatar
David Hallas committed
210
211
{
    QVector<DolphinViewContainer*> viewContainers;
212

David Hallas's avatar
David Hallas committed
213
    for (int i = 0; i < m_tabWidget->count(); ++i) {
214
215
216
217
218
219
        DolphinTabPage *tabPage = m_tabWidget->tabPageAt(i);

        viewContainers << tabPage->primaryViewContainer();
        if (tabPage->splitViewEnabled()) {
            viewContainers << tabPage->secondaryViewContainer();
        }
David Hallas's avatar
David Hallas committed
220
221
222
223
    }
    return viewContainers;
}

224
225
226
227
228
229
230
231
232
233
234
235
236
237
void DolphinMainWindow::setViewsWithInvalidPathsToHome()
{
    const QVector<DolphinViewContainer*> theViewContainers = viewContainers();
    for (DolphinViewContainer *viewContainer : theViewContainers) {

        // Only consider local dirs, not remote locations and abstract protocols
        if (viewContainer->url().isLocalFile()) {
            if (!QFileInfo::exists(viewContainer->url().toLocalFile())) {
                viewContainer->setUrl(QUrl::fromLocalFile(QDir::homePath()));
            }
        }
    }
}

238
void DolphinMainWindow::openDirectories(const QList<QUrl>& dirs, bool splitView)
239
{
240
    m_tabWidget->openDirectories(dirs, splitView);
241
242
}

243
244
245
246
247
void DolphinMainWindow::openDirectories(const QStringList& dirs, bool splitView)
{
    openDirectories(QUrl::fromStringList(dirs), splitView);
}

248
void DolphinMainWindow::openFiles(const QList<QUrl>& files, bool splitView)
249
{
250
    m_tabWidget->openFiles(files, splitView);
251
252
}

253
254
255
256
257
258
259
bool DolphinMainWindow::isFoldersPanelEnabled() const
{
    return actionCollection()->action(QStringLiteral("show_folders_panel"))->isChecked();
}

bool DolphinMainWindow::isInformationPanelEnabled() const
{
260
#ifdef HAVE_BALOO
261
    return actionCollection()->action(QStringLiteral("show_information_panel"))->isChecked();
262
263
264
#else
    return false;
#endif
265
266
}

267
268
269
270
271
272
273
void DolphinMainWindow::openFiles(const QStringList& files, bool splitView)
{
    openFiles(QUrl::fromStringList(files), splitView);
}

void DolphinMainWindow::activateWindow()
{
274
275
    window()->setAttribute(Qt::WA_NativeWindow, true);
    KStartupInfo::setNewStartupId(window()->windowHandle(), KStartupInfo::startupId());
276
277
278
    KWindowSystem::activateWindow(window()->effectiveWinId());
}

279
void DolphinMainWindow::showCommand(CommandType command)
280
{
281
282
283
    DolphinStatusBar* statusBar = m_activeViewContainer->statusBar();
    switch (command) {
    case KIO::FileUndoManager::Copy:
284
        statusBar->setText(i18nc("@info:status", "Successfully copied."));
285
286
        break;
    case KIO::FileUndoManager::Move:
287
        statusBar->setText(i18nc("@info:status", "Successfully moved."));
288
289
        break;
    case KIO::FileUndoManager::Link:
290
        statusBar->setText(i18nc("@info:status", "Successfully linked."));
291
292
        break;
    case KIO::FileUndoManager::Trash:
293
        statusBar->setText(i18nc("@info:status", "Successfully moved to trash."));
294
295
        break;
    case KIO::FileUndoManager::Rename:
296
        statusBar->setText(i18nc("@info:status", "Successfully renamed."));
297
298
299
        break;

    case KIO::FileUndoManager::Mkdir:
300
        statusBar->setText(i18nc("@info:status", "Created folder."));
301
302
303
304
305
        break;

    default:
        break;
    }
306
}
Peter Penz's avatar
Peter Penz committed
307

308
309
310
311
312
void DolphinMainWindow::pasteIntoFolder()
{
    m_activeViewContainer->view()->pasteIntoFolder();
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
313
void DolphinMainWindow::changeUrl(const QUrl &url)
Peter Penz's avatar
Peter Penz committed
314
{
315
    if (!KProtocolManager::supportsListing(url)) {
316
317
318
319
320
        // The URL navigator only checks for validity, not
        // if the URL can be listed. An error message is
        // shown due to DolphinViewContainer::restoreView().
        return;
    }
321

322
    m_activeViewContainer->setUrl(url);
323
    updateFileAndEditActions();
324
325
326
327
    updatePasteAction();
    updateViewActions();
    updateGoActions();

Alexander Lohnau's avatar
Alexander Lohnau committed
328
    Q_EMIT urlChanged(url);
Peter Penz's avatar
Peter Penz committed
329
330
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
331
void DolphinMainWindow::slotTerminalDirectoryChanged(const QUrl& url)
332
{
333
334
335
336
337
    if (m_tearDownFromPlacesRequested && url == QUrl::fromLocalFile(QDir::homePath())) {
        m_placesPanel->proceedWithTearDown();
        m_tearDownFromPlacesRequested = false;
    }

338
339
340
341
342
    m_activeViewContainer->setAutoGrabFocus(false);
    changeUrl(url);
    m_activeViewContainer->setAutoGrabFocus(true);
}

343
344
345
void DolphinMainWindow::slotEditableStateChanged(bool editable)
{
    KToggleAction* editableLocationAction =
346
        static_cast<KToggleAction*>(actionCollection()->action(QStringLiteral("editable_location")));
347
348
349
    editableLocationAction->setChecked(editable);
}

350
void DolphinMainWindow::slotSelectionChanged(const KFileItemList& selection)
Peter Penz's avatar
Peter Penz committed
351
{
352
    updateFileAndEditActions();
Peter Penz's avatar
Peter Penz committed
353

354
    const int selectedUrlsCount = m_tabWidget->currentTabPage()->selectedItemsCount();
Peter Penz's avatar
Peter Penz committed
355

356
    QAction* compareFilesAction = actionCollection()->action(QStringLiteral("compare_files"));
357
    if (selectedUrlsCount == 2) {
358
        compareFilesAction->setEnabled(isKompareInstalled());
359
360
361
    } else {
        compareFilesAction->setEnabled(false);
    }
Peter Penz's avatar
Peter Penz committed
362

Alexander Lohnau's avatar
Alexander Lohnau committed
363
    Q_EMIT selectionChanged(selection);
Peter Penz's avatar
Peter Penz committed
364
365
}

366
void DolphinMainWindow::updateHistory()
Peter Penz's avatar
Peter Penz committed
367
{
368
    const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
369
370
    const int index = urlNavigator->historyIndex();

371
    QAction* backAction = actionCollection()->action(KStandardAction::name(KStandardAction::Back));
372
    if (backAction) {
373
        backAction->setToolTip(i18nc("@info", "Go back"));
374
        backAction->setWhatsThis(i18nc("@info:whatsthis go back", "Return to the previously viewed folder."));
375
376
377
        backAction->setEnabled(index < urlNavigator->historySize() - 1);
    }

378
    QAction* forwardAction = actionCollection()->action(KStandardAction::name(KStandardAction::Forward));
379
    if (forwardAction) {
380
        forwardAction->setToolTip(i18nc("@info", "Go forward"));
381
382
        forwardAction->setWhatsThis(xi18nc("@info:whatsthis go forward",
            "This undoes a <interface>Go|Back</interface> action."));
383
384
        forwardAction->setEnabled(index > 0);
    }
Peter Penz's avatar
Peter Penz committed
385
386
387
388
}

void DolphinMainWindow::updateFilterBarAction(bool show)
{
389
390
    QAction* toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter"));
    toggleFilterBarAction->setChecked(show);
Peter Penz's avatar
Peter Penz committed
391
392
393
394
}

void DolphinMainWindow::openNewMainWindow()
{
395
    Dolphin::openNewWindow({m_activeViewContainer->url()}, this);
Peter Penz's avatar
Peter Penz committed
396
397
}

398
void DolphinMainWindow::openNewActivatedTab()
399
{
400
401
402
    // keep browsers compatibility, new tab is always after last one
    auto openNewTabAfterLastTabConfigured = GeneralSettings::openNewTabAfterLastTab();
    GeneralSettings::setOpenNewTabAfterLastTab(true);
403
    m_tabWidget->openNewActivatedTab();
404
    GeneralSettings::setOpenNewTabAfterLastTab(openNewTabAfterLastTabConfigured);
405
406
}

407
408
409
410
411
412
void DolphinMainWindow::addToPlaces()
{
    QUrl url;
    QString name;

    // If nothing is selected, act on the current dir
Laurent Montel's avatar
Laurent Montel committed
413
    if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
        url = m_activeViewContainer->url();
        name = m_activeViewContainer->placesText();
    } else {
        const auto dirToAdd = m_activeViewContainer->view()->selectedItems().first();
        url = dirToAdd.url();
        name = dirToAdd.name();
    }
    if (url.isValid()) {
        PlacesItemModel model;
        QString icon;
        if (m_activeViewContainer->isSearchModeEnabled()) {
            icon = QStringLiteral("folder-saved-search-symbolic");
        } else {
            icon = KIO::iconNameForUrl(url);
        }
        model.createPlacesItem(name, url, icon);
    }
}

433
void DolphinMainWindow::openNewTab(const QUrl& url)
434
{
435
    m_tabWidget->openNewTab(url, QUrl());
436
437
}

438
439
void DolphinMainWindow::openInNewTab()
{
440
    const KFileItemList& list = m_activeViewContainer->view()->selectedItems();
441
442
    bool tabCreated = false;

Alexander Lohnau's avatar
Alexander Lohnau committed
443
    for (const KFileItem& item : list) {
444
445
        const QUrl& url = DolphinView::openItemAsFolderUrl(item);
        if (!url.isEmpty()) {
446
            openNewTab(url);
447
            tabCreated = true;
448
        }
449
    }
450
451
452
453

    // if no new tab has been created from the selection
    // open the current directory in a new tab
    if (!tabCreated) {
454
        openNewTab(m_activeViewContainer->url());
455
    }
456
457
458
459
}

void DolphinMainWindow::openInNewWindow()
{
Lukáš Tinkl's avatar
Lukáš Tinkl committed
460
    QUrl newWindowUrl;
461

462
    const KFileItemList list = m_activeViewContainer->view()->selectedItems();
463
464
    if (list.isEmpty()) {
        newWindowUrl = m_activeViewContainer->url();
465
466
467
    } else if (list.count() == 1) {
        const KFileItem& item = list.first();
        newWindowUrl = DolphinView::openItemAsFolderUrl(item);
468
469
470
    }

    if (!newWindowUrl.isEmpty()) {
471
        Dolphin::openNewWindow({newWindowUrl}, this);
472
473
474
    }
}

475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
void DolphinMainWindow::showTarget()
{
    const auto link = m_activeViewContainer->view()->selectedItems().at(0);
    const auto linkLocationDir = QFileInfo(link.localPath()).absoluteDir();
    auto linkDestination = link.linkDest();
    if (QFileInfo(linkDestination).isRelative()) {
        linkDestination = linkLocationDir.filePath(linkDestination);
    }
    if (QFileInfo::exists(linkDestination)) {
        KIO::highlightInFileManager({QUrl::fromLocalFile(linkDestination).adjusted(QUrl::StripTrailingSlash)});
    } else {
        m_activeViewContainer->showMessage(xi18nc("@info", "Could not access <filename>%1</filename>.", linkDestination),
                                           DolphinViewContainer::Warning);
    }
}

491
492
493
void DolphinMainWindow::showEvent(QShowEvent* event)
{
    KXmlGuiWindow::showEvent(event);
494

495
496
497
498
499
    if (!event->spontaneous()) {
        m_activeViewContainer->view()->setFocus();
    }
}

Peter Penz's avatar
Peter Penz committed
500
501
void DolphinMainWindow::closeEvent(QCloseEvent* event)
{
502
503
504
    // Find out if Dolphin is closed directly by the user or
    // by the session manager because the session is closed
    bool closedByUser = true;
505
    if (qApp->isSavingSession()) {
506
507
508
        closedByUser = false;
    }

509
510
511
512
    if (m_tabWidget->count() > 1
        && GeneralSettings::confirmClosingMultipleTabs()
        && !GeneralSettings::rememberOpenedTabs()
        && closedByUser) {
513
514
        // Ask the user if he really wants to quit and close all tabs.
        // Open a confirmation dialog with 3 buttons:
515
516
517
        // QDialogButtonBox::Yes    -> Quit
        // QDialogButtonBox::No     -> Close only the current tab
        // QDialogButtonBox::Cancel -> do nothing
518
519
        QDialog *dialog = new QDialog(this, Qt::Dialog);
        dialog->setWindowTitle(i18nc("@title:window", "Confirmation"));
520
        dialog->setModal(true);
521
        QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
522
        KGuiItem::assign(buttons->button(QDialogButtonBox::Yes), KGuiItem(i18nc("@action:button 'Quit Dolphin' button", "&Quit %1", QGuiApplication::applicationDisplayName()), QIcon::fromTheme(QStringLiteral("application-exit"))));
523
        KGuiItem::assign(buttons->button(QDialogButtonBox::No), KGuiItem(i18n("C&lose Current Tab"), QIcon::fromTheme(QStringLiteral("tab-close"))));
524
525
        KGuiItem::assign(buttons->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
        buttons->button(QDialogButtonBox::Yes)->setDefault(true);
526
527
528

        bool doNotAskAgainCheckboxResult = false;

529
        const auto result = KMessageBox::createKMessageBox(dialog,
530
            buttons,
531
532
533
534
535
536
537
538
            QMessageBox::Warning,
            i18n("You have multiple tabs open in this window, are you sure you want to quit?"),
            QStringList(),
            i18n("Do not ask again"),
            &doNotAskAgainCheckboxResult,
            KMessageBox::Notify);

        if (doNotAskAgainCheckboxResult) {
539
            GeneralSettings::setConfirmClosingMultipleTabs(false);
540
541
542
        }

        switch (result) {
543
            case QDialogButtonBox::Yes:
544
545
                // Quit
                break;
546
            case QDialogButtonBox::No:
547
                // Close only the current tab
548
                m_tabWidget->closeTab();
Roman Inflianskas's avatar
Roman Inflianskas committed
549
                Q_FALLTHROUGH();
550
551
552
            default:
                event->ignore();
                return;
553
554
555
        }
    }

556
    if (m_terminalPanel && m_terminalPanel->hasProgramRunning() && GeneralSettings::confirmClosingTerminalRunningProgram() && closedByUser) {
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
        // Ask if the user really wants to quit Dolphin with a program that is still running in the Terminal panel
        // Open a confirmation dialog with 3 buttons:
        // QDialogButtonBox::Yes    -> Quit
        // QDialogButtonBox::No     -> Show Terminal Panel
        // QDialogButtonBox::Cancel -> do nothing
        QDialog *dialog = new QDialog(this, Qt::Dialog);
        dialog->setWindowTitle(i18nc("@title:window", "Confirmation"));
        dialog->setModal(true);
        auto standardButtons = QDialogButtonBox::Yes | QDialogButtonBox::Cancel;
        if (!m_terminalPanel->isVisible()) {
            standardButtons |= QDialogButtonBox::No;
        }
        QDialogButtonBox *buttons = new QDialogButtonBox(standardButtons);
        KGuiItem::assign(buttons->button(QDialogButtonBox::Yes), KStandardGuiItem::quit());
        if (!m_terminalPanel->isVisible()) {
            KGuiItem::assign(
                    buttons->button(QDialogButtonBox::No),
574
                    KGuiItem(i18n("Show &Terminal Panel"), QIcon::fromTheme(QStringLiteral("dialog-scripts"))));
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
        }
        KGuiItem::assign(buttons->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());

        bool doNotAskAgainCheckboxResult = false;

        const auto result = KMessageBox::createKMessageBox(
                dialog,
                buttons,
                QMessageBox::Warning,
                i18n("The program '%1' is still running in the Terminal panel. Are you sure you want to quit?", m_terminalPanel->runningProgramName()),
                QStringList(),
                i18n("Do not ask again"),
                &doNotAskAgainCheckboxResult,
                KMessageBox::Dangerous);

        if (doNotAskAgainCheckboxResult) {
            GeneralSettings::setConfirmClosingTerminalRunningProgram(false);
        }

        switch (result) {
            case QDialogButtonBox::Yes:
                // Quit
                break;
            case QDialogButtonBox::No:
                actionCollection()->action("show_terminal_panel")->trigger();
                // Do not quit, ignore quit event
                Q_FALLTHROUGH();
            default:
                event->ignore();
                return;
605
606
607
        }
    }

608
609
610
611
612
613
614
615
    if (GeneralSettings::rememberOpenedTabs())  {
        KConfigGui::setSessionConfig(QStringLiteral("dolphin"), QStringLiteral("dolphin"));
        KConfig *config = KConfigGui::sessionConfig();
        saveGlobalProperties(config);
        savePropertiesInternal(config, 1);
        config->sync();
    }

616
    GeneralSettings::setVersion(CurrentDolphinVersion);
Laurent Montel's avatar
Laurent Montel committed
617
    GeneralSettings::self()->save();
Peter Penz's avatar
Peter Penz committed
618

619
    KXmlGuiWindow::closeEvent(event);
Peter Penz's avatar
Peter Penz committed
620
621
}

Peter Penz's avatar
Peter Penz committed
622
void DolphinMainWindow::saveProperties(KConfigGroup& group)
Peter Penz's avatar
Peter Penz committed
623
{
624
    m_tabWidget->saveProperties(group);
Peter Penz's avatar
Peter Penz committed
625
626
}

Peter Penz's avatar
Peter Penz committed
627
void DolphinMainWindow::readProperties(const KConfigGroup& group)
Peter Penz's avatar
Peter Penz committed
628
{
629
    m_tabWidget->readProperties(group);
Peter Penz's avatar
Peter Penz committed
630
631
632
633
}

void DolphinMainWindow::updateNewMenu()
{
634
    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
Peter Penz's avatar
Peter Penz committed
635
    m_newFileMenu->checkUpToDate();
636
    m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
Peter Penz's avatar
Peter Penz committed
637
638
}

639
640
void DolphinMainWindow::createDirectory()
{
641
    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
642
    m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
Peter Penz's avatar
Peter Penz committed
643
    m_newFileMenu->createDirectory();
644
645
}

Peter Penz's avatar
Peter Penz committed
646
647
648
649
650
void DolphinMainWindow::quit()
{
    close();
}

651
void DolphinMainWindow::showErrorMessage(const QString& message)
652
{
653
    m_activeViewContainer->showMessage(message, DolphinViewContainer::Error);
654
655
}

Peter Penz's avatar
Peter Penz committed
656
657
void DolphinMainWindow::slotUndoAvailable(bool available)
{
Bill Egert's avatar
Bill Egert committed
658
    QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo));
659
    if (undoAction) {
Peter Penz's avatar
Peter Penz committed
660
661
662
663
664
665
        undoAction->setEnabled(available);
    }
}

void DolphinMainWindow::slotUndoTextChanged(const QString& text)
{
Bill Egert's avatar
Bill Egert committed
666
    QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo));
667
    if (undoAction) {
Peter Penz's avatar
Peter Penz committed
668
669
670
671
672
673
674
        undoAction->setText(text);
    }
}

void DolphinMainWindow::undo()
{
    clearStatusBar();
675
676
    KIO::FileUndoManager::self()->uiInterface()->setParentWidget(this);
    KIO::FileUndoManager::self()->undo();
Peter Penz's avatar
Peter Penz committed
677
678
679
680
}

void DolphinMainWindow::cut()
{
681
    m_activeViewContainer->view()->cutSelectedItemsToClipboard();
Peter Penz's avatar
Peter Penz committed
682
683
684
685
}

void DolphinMainWindow::copy()
{
686
    m_activeViewContainer->view()->copySelectedItemsToClipboard();
Peter Penz's avatar
Peter Penz committed
687
688
689
690
}

void DolphinMainWindow::paste()
{
691
    m_activeViewContainer->view()->paste();
Peter Penz's avatar
Peter Penz committed
692
693
}

694
695
696
697
698
void DolphinMainWindow::find()
{
    m_activeViewContainer->setSearchModeEnabled(true);
}

699
700
701
702
703
704
void DolphinMainWindow::updateSearchAction()
{
    QAction* toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search"));
    toggleSearchAction->setChecked(m_activeViewContainer->isSearchModeEnabled());
}

Peter Penz's avatar
Peter Penz committed
705
706
void DolphinMainWindow::updatePasteAction()
{
Bill Egert's avatar
Bill Egert committed
707
    QAction* pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
708
709
710
    QPair<bool, QString> pasteInfo = m_activeViewContainer->view()->pasteInfo();
    pasteAction->setEnabled(pasteInfo.first);
    pasteAction->setText(pasteInfo.second);
Peter Penz's avatar
Peter Penz committed
711
712
}

713
714
715
716
717
void DolphinMainWindow::slotDirectoryLoadingCompleted()
{
    updatePasteAction();
}

718
719
void DolphinMainWindow::slotToolBarActionMiddleClicked(QAction *action)
{
720
    if (action == actionCollection()->action(KStandardAction::name(KStandardAction::Back))) {
721
        goBackInNewTab();
722
    } else if (action == actionCollection()->action(KStandardAction::name(KStandardAction::Forward))) {
723
724
725
726
727
728
729
730
        goForwardInNewTab();
    } else if (action == actionCollection()->action(QStringLiteral("go_up"))) {
        goUpInNewTab();
    } else if (action == actionCollection()->action(QStringLiteral("go_home"))) {
        goHomeInNewTab();
    }
}

731
732
void DolphinMainWindow::slotAboutToShowBackPopupMenu()
{
733
    const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
734
735
736
737
738
739
740
741
742
743
744
745
    int entries = 0;
    m_backAction->menu()->clear();
    for (int i = urlNavigator->historyIndex() + 1; i < urlNavigator->historySize() && entries < MaxNumberOfNavigationentries; ++i, ++entries) {
        QAction* action = new QAction(urlNavigator->locationUrl(i).toString(QUrl::PreferLocalFile), m_backAction->menu());
        action->setData(i);
        m_backAction->menu()->addAction(action);
    }
}

void DolphinMainWindow::slotGoBack(QAction* action)
{
    int gotoIndex = action->data().value<int>();
746
    const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
747
748
749
750
751
752
753
754
    for (int i = gotoIndex - urlNavigator->historyIndex(); i > 0; --i) {
        goBack();
    }
}

void DolphinMainWindow::slotBackForwardActionMiddleClicked(QAction* action)
{
    if (action) {
755
        const KUrlNavigator *urlNavigator = activeViewContainer()->urlNavigatorInternalWithHistory();
756
        openNewTab(urlNavigator->locationUrl(action->data().value<int>()));
757
758
759
760
761
    }
}

void DolphinMainWindow::slotAboutToShowForwardPopupMenu()
{
762
    const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
763
764
765
766
767
768
769
770
771
772
773
774
    int entries = 0;
    m_forwardAction->menu()->clear();
    for (int i = urlNavigator->historyIndex() - 1; i >= 0 && entries < MaxNumberOfNavigationentries; --i, ++entries) {
        QAction* action = new QAction(urlNavigator->locationUrl(i).toString(QUrl::PreferLocalFile), m_forwardAction->menu());
        action->setData(i);
        m_forwardAction->menu()->addAction(action);
    }
}

void DolphinMainWindow::slotGoForward(QAction* action)
{
    int gotoIndex = action->data().value<int>();
775
    const KUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
776
777
778
779
780
    for (int i = urlNavigator->historyIndex() - gotoIndex; i > 0; --i) {
        goForward();
    }
}

Peter Penz's avatar
Peter Penz committed
781
782
783
void DolphinMainWindow::selectAll()
{
    clearStatusBar();
784
785
786
787
788

    // if the URL navigator is editable and focused, select the whole
    // URL instead of all items of the view

    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
789
    QLineEdit* lineEdit = urlNavigator->editor()->lineEdit();
790
791
792
793
794
795
796
    const bool selectUrl = urlNavigator->isUrlEditable() &&
                           lineEdit->hasFocus();
    if (selectUrl) {
        lineEdit->selectAll();
    } else {
        m_activeViewContainer->view()->selectAll();
    }
Peter Penz's avatar
Peter Penz committed
797
798
799
800
801
}

void DolphinMainWindow::invertSelection()
{
    clearStatusBar();
802
    m_activeViewContainer->view()->invertSelection();
Peter Penz's avatar
Peter Penz committed
803
804
805
806
}

void DolphinMainWindow::toggleSplitView()
{
807
    DolphinTabPage* tabPage = m_tabWidget->currentTabPage();
Felix Ernst's avatar
Felix Ernst committed
808
    tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled(), WithAnimation);
809

810
    updateViewActions();
Peter Penz's avatar
Peter Penz committed
811
812
}

813
814
815
void DolphinMainWindow::toggleSplitStash()
{
    DolphinTabPage* tabPage = m_tabWidget->currentTabPage();
Felix Ernst's avatar
Felix Ernst committed
816
817
    tabPage->setSplitViewEnabled(false, WithAnimation);
    tabPage->setSplitViewEnabled(true, WithAnimation, QUrl("stash:/"));
818
819
}

Peter Penz's avatar
Peter Penz committed
820
821
822
void DolphinMainWindow::reloadView()
{
    clearStatusBar();
823
    m_activeViewContainer->reload();
824
    m_activeViewContainer->statusBar()->updateSpaceInfo();
Peter Penz's avatar
Peter Penz committed
825
826
827
}

void DolphinMainWindow::stopLoading()
828
{
829
830
831
832
833
    m_activeViewContainer->view()->stopLoading();
}

void DolphinMainWindow::enableStopAction()
{
834
    actionCollection()->action(QStringLiteral("stop"))->setEnabled(true);
835
836
837
838
}

void DolphinMainWindow::disableStopAction()
{
839
    actionCollection()->action(QStringLiteral("stop"))->setEnabled(false);
840
}
Peter Penz's avatar
Peter Penz committed
841

842
void DolphinMainWindow::showFilterBar()
Peter Penz's avatar
Peter Penz committed
843
{
844
    m_activeViewContainer->setFilterBarVisible(true);
Peter Penz's avatar
Peter Penz committed
845
846
}

847
848
void DolphinMainWindow::toggleFilterBar()
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
849
    const bool checked = !m_activeViewContainer->isFilterBarVisible();
850
851
852
853
854
855
    m_activeViewContainer->setFilterBarVisible(checked);

    QAction* toggleFilterBarAction = actionCollection()->action(QStringLiteral("toggle_filter"));
    toggleFilterBarAction->setChecked(checked);
}

Peter Penz's avatar
Peter Penz committed
856
857
858
859
void DolphinMainWindow::toggleEditLocation()
{
    clearStatusBar();

860
    QAction* action = actionCollection()->action(QStringLiteral("editable_location"));
861
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
862
    urlNavigator->setUrlEditable(action->isChecked());
Peter Penz's avatar
Peter Penz committed
863
864
}

865
void DolphinMainWindow::replaceLocation()
Peter Penz's avatar
Peter Penz committed
866
{
867
    KUrlNavigator* navigator = m_activeViewContainer->urlNavigator();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
868
    QLineEdit* lineEdit = navigator->editor()->lineEdit();
869
870
871
872
873
874
875
876
877
878
879
880

    // If the text field currently has focus and everything is selected,
    // pressing the keyboard shortcut returns the whole thing to breadcrumb mode
    if (navigator->isUrlEditable()
        && lineEdit->hasFocus()
        && lineEdit->selectedText() == lineEdit->text() ) {
        navigator->setUrlEditable(false);
    } else {
        navigator->setUrlEditable(true);
        navigator->setFocus();
        lineEdit->selectAll();
    }
Peter Penz's avatar
Peter Penz committed
881
882
}

883
884
void DolphinMainWindow::togglePanelLockState()
{
885
    const bool newLockState = !GeneralSettings::lockPanels();
Alexander Lohnau's avatar
Alexander Lohnau committed
886
887
    const auto childrenObjects = children();
    for (QObject* child : childrenObjects) {
888
        DolphinDockWidget* dock = qobject_cast<DolphinDockWidget*>(child);
889
        if (dock) {
890
891
892
893
            dock->setLocked(newLockState);
        }
    }

894
    GeneralSettings::setLockPanels(newLockState);
895
896
}

897
898
void DolphinMainWindow::slotTerminalPanelVisibilityChanged()
{
899
    if (m_terminalPanel->isHiddenInVisibleWindow() && m_activeViewContainer) {
900
901
902
903
        m_activeViewContainer->view()->setFocus();
    }
}

Peter Penz's avatar
Peter Penz committed
904
905
void DolphinMainWindow::goBack()
{
906
    DolphinUrlNavigator *urlNavigator = m_activeViewContainer->urlNavigatorInternalWithHistory();
907
    urlNavigator->goBack();
908

909
910
911
    if (urlNavigator->locationState().isEmpty()) {
        // An empty location state indicates a redirection URL,
        // which must be skipped too
912
        urlNavigator->goBack();
913
    }
Peter Penz's avatar
Peter Penz committed
914
915
916
917
}

void DolphinMainWindow::goForward()
{
Felix Ernst's avatar
Felix Ernst committed
918
    m_activeViewContainer->urlNavigatorInternalWithHistory()->goForward();
Peter Penz's avatar
Peter Penz committed
919
920
921
922
}

void DolphinMainWindow::goUp()
{
Felix Ernst's avatar
Felix Ernst committed
923
    m_activeViewContainer->urlNavigatorInternalWithHistory()->goUp();
Peter Penz's avatar
Peter Penz committed
924
925
}

Peter Penz's avatar
Peter Penz committed
926
927
void DolphinMainWindow::goHome()
{
Felix Ernst's avatar
Felix Ernst committed
928
    m_activeViewContainer->urlNavigatorInternalWithHistory()->goHome();
Peter Penz's avatar
Peter Penz committed
929
930
}

931
void DolphinMainWindow::goBackInNewTab()
932
{
933
    const KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigatorInternalWithHistory();
934
    const int index = urlNavigator->historyIndex() + 1;
935
    openNewTab(urlNavigator->locationUrl(index));
936
937
}

938
void DolphinMainWindow::goForwardInNewTab()
939
{
940
    const KUrlNavigator* urlNavigator = activeView