dolphinmainwindow.cpp 103 KB
Newer Older
Peter Penz's avatar
Peter Penz committed
1
/***************************************************************************
2
 *   Copyright (C) 2006 by Peter Penz <peter.penz19@gmail.com>             *
Peter Penz's avatar
Peter Penz committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 *   Copyright (C) 2006 by Stefan Monov <logixoul@gmail.com>               *
 *   Copyright (C) 2006 by Cvetoslav Ludmiloff <ludmiloff@gmail.com>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA            *
 ***************************************************************************/

#include "dolphinmainwindow.h"

24
#include "dolphinmainwindowadaptor.h"
Elvis Angelaccio's avatar
Elvis Angelaccio committed
25
#include "config-terminal.h"
26
#include "global.h"
David Hallas's avatar
David Hallas committed
27
#include "dolphinbookmarkhandler.h"
28
#include "dolphindockwidget.h"
29
#include "dolphincontextmenu.h"
Peter Penz's avatar
Peter Penz committed
30
#include "dolphinnewfilemenu.h"
31
#include "dolphinrecenttabsmenu.h"
32
#include "dolphinviewcontainer.h"
33
#include "dolphintabpage.h"
34
#include "middleclickactioneventfilter.h"
35
#include "panels/folders/folderspanel.h"
36
#include "panels/places/placesitemmodel.h"
37
38
#include "panels/places/placespanel.h"
#include "panels/information/informationpanel.h"
39
#include "panels/terminal/terminalpanel.h"
40
#include "settings/dolphinsettingsdialog.h"
41
#include "statusbar/dolphinstatusbar.h"
42
43
#include "views/dolphinviewactionhandler.h"
#include "views/dolphinremoteencoding.h"
44
#include "views/draganddrophelper.h"
45
#include "views/viewproperties.h"
46
#include "views/dolphinnewfilemenuobserver.h"
Peter Penz's avatar
Peter Penz committed
47
48
#include "dolphin_generalsettings.h"

Peter Penz's avatar
Peter Penz committed
49
50
#include <KActionCollection>
#include <KActionMenu>
51
#include <KAuthorized>
Peter Penz's avatar
Peter Penz committed
52
#include <KConfig>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
53
#include <KDualAction>
Roman Inflianskas's avatar
Roman Inflianskas committed
54
#include <KFileItemListProperties>
Elvis Angelaccio's avatar
Elvis Angelaccio committed
55
#include <KHelpMenu>
56
#include <KIO/CommandLauncherJob>
57
#include <KIO/JobUiDelegate>
58
#include <KIO/OpenFileManagerWindowJob>
Roman Inflianskas's avatar
Roman Inflianskas committed
59
#include <KJobWidgets>
Laurent Montel's avatar
Laurent Montel committed
60
#include <KLocalizedString>
Peter Penz's avatar
Peter Penz committed
61
#include <KMessageBox>
62
#include <KNS3/KMoreToolsMenuFactory>
Roman Inflianskas's avatar
Roman Inflianskas committed
63
64
#include <KProtocolInfo>
#include <KProtocolManager>
Peter Penz's avatar
Peter Penz committed
65
66
67
#include <KRun>
#include <KShell>
#include <KStandardAction>
68
#include <KStartupInfo>
Peter Penz's avatar
Peter Penz committed
69
#include <KToggleAction>
Roman Inflianskas's avatar
Roman Inflianskas committed
70
#include <KToolBar>
71
#include <KToolBarPopupAction>
72
#include <KToolInvocation>
Laurent Montel's avatar
Laurent Montel committed
73
#include <KUrlComboBox>
Roman Inflianskas's avatar
Roman Inflianskas committed
74
#include <KUrlNavigator>
75
#include <KWindowSystem>
Peter Penz's avatar
Peter Penz committed
76

77
#include <QApplication>
78
#include <QClipboard>
Laurent Montel's avatar
Laurent Montel committed
79
#include <QCloseEvent>
80
#include <QDesktopServices>
81
#include <QDialog>
82
#include <QFileInfo>
Roman Inflianskas's avatar
Roman Inflianskas committed
83
84
85
86
87
88
89
90
#include <QLineEdit>
#include <QMenu>
#include <QMenuBar>
#include <QPushButton>
#include <QShowEvent>
#include <QStandardPaths>
#include <QTimer>
#include <QToolButton>
91
#include <QWhatsThisClickedEvent>
Peter Penz's avatar
Peter Penz committed
92

93
94
95
namespace {
    // Used for GeneralSettings::version() to determine whether
    // an updated version of Dolphin is running.
Peter Penz's avatar
Peter Penz committed
96
    const int CurrentDolphinVersion = 200;
97
98
    // The maximum number of entries in the back/forward popup menu
    const int MaxNumberOfNavigationentries = 12;
99
100
    // The maximum number of "Activate Tab" shortcuts
    const int MaxActivateTabShortcuts = 9;
101
}
102

103
DolphinMainWindow::DolphinMainWindow() :
104
    KXmlGuiWindow(nullptr),
Kevin Funk's avatar
Kevin Funk committed
105
    m_newFileMenu(nullptr),
106
    m_helpMenu(nullptr),
Kevin Funk's avatar
Kevin Funk committed
107
108
109
110
    m_tabWidget(nullptr),
    m_activeViewContainer(nullptr),
    m_actionHandler(nullptr),
    m_remoteEncoding(nullptr),
111
    m_settingsDialog(),
David Hallas's avatar
David Hallas committed
112
    m_bookmarkHandler(nullptr),
Kevin Funk's avatar
Kevin Funk committed
113
114
115
116
117
    m_controlButton(nullptr),
    m_updateToolBarTimer(nullptr),
    m_lastHandleUrlStatJob(nullptr),
    m_terminalPanel(nullptr),
    m_placesPanel(nullptr),
118
119
120
    m_tearDownFromPlacesRequested(false),
    m_backAction(nullptr),
    m_forwardAction(nullptr)
Peter Penz's avatar
Peter Penz committed
121
{
Elvis Angelaccio's avatar
Elvis Angelaccio committed
122
    Q_INIT_RESOURCE(dolphin);
123
124
125

    new MainWindowAdaptor(this);

126
127
128
#ifndef Q_OS_WIN
	setWindowFlags(Qt::WindowContextHelpButtonHint);
#endif
129
    setComponentName(QStringLiteral("dolphin"), QGuiApplication::applicationDisplayName());
130
    setObjectName(QStringLiteral("Dolphin#"));
131

132
133
    connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::errorMessage,
            this, &DolphinMainWindow::showErrorMessage);
134

135
    KIO::FileUndoManager* undoManager = KIO::FileUndoManager::self();
136
    undoManager->setUiInterface(new UndoUiInterface());
Peter Penz's avatar
Peter Penz committed
137

138
    connect(undoManager, QOverload<bool>::of(&KIO::FileUndoManager::undoAvailable),
139
140
141
142
143
144
145
            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);
146

147
    GeneralSettings* generalSettings = GeneralSettings::self();
Peter Penz's avatar
Peter Penz committed
148
    const bool firstRun = (generalSettings->version() < 200);
149
150
151
152
153
154
    if (firstRun) {
        generalSettings->setViewPropsTimestamp(QDateTime::currentDateTime());
    }

    setAcceptDrops(true);

155
    m_tabWidget = new DolphinTabWidget(this);
156
    m_tabWidget->setObjectName("tabWidget");
157
158
159
160
161
    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
162
            this, &DolphinMainWindow::updateWindowTitle);
163
164
    setCentralWidget(m_tabWidget);

165
166
167
    setupActions();

    m_actionHandler = new DolphinViewActionHandler(actionCollection(), this);
168
    connect(m_actionHandler, &DolphinViewActionHandler::actionBeingHandled, this, &DolphinMainWindow::clearStatusBar);
169
    connect(m_actionHandler, &DolphinViewActionHandler::createDirectoryTriggered, this, &DolphinMainWindow::createDirectory);
170
171

    m_remoteEncoding = new DolphinRemoteEncoding(this, m_actionHandler);
172
173
    connect(this, &DolphinMainWindow::urlChanged,
            m_remoteEncoding, &DolphinRemoteEncoding::slotAboutToOpenUrl);
174
175
176
177

    setupDockWidgets();

    setupGUI(Keys | Save | Create | ToolBar);
178
    stateChanged(QStringLiteral("new_file"));
179
180

    QClipboard* clipboard = QApplication::clipboard();
181
182
    connect(clipboard, &QClipboard::dataChanged,
            this, &DolphinMainWindow::updatePasteAction);
183

184
    QAction* showFilterBarAction = actionCollection()->action(QStringLiteral("show_filter_bar"));
185
186
187
188
189
190
191
192
193
194
195
196
    showFilterBarAction->setChecked(generalSettings->filterBar());

    if (firstRun) {
        menuBar()->setVisible(false);
        // Assure a proper default size if Dolphin runs the first time
        resize(750, 500);
    }

    const bool showMenu = !menuBar()->isHidden();
    QAction* showMenuBarAction = actionCollection()->action(KStandardAction::name(KStandardAction::ShowMenubar));
    showMenuBarAction->setChecked(showMenu);  // workaround for bug #171080
    if (!showMenu) {
197
        createControlButton();
198
    }
199
200
201
202
203

    // 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);
204
205

    setupWhatsThis();
206
207

    QTimer::singleShot(0, this, &DolphinMainWindow::setupUpdateOpenPreferredSearchToolAction);
Peter Penz's avatar
Peter Penz committed
208
209
210
211
212
213
}

DolphinMainWindow::~DolphinMainWindow()
{
}

David Hallas's avatar
David Hallas committed
214
215
216
217
218
219
220
221
222
223
QVector<DolphinViewContainer*> DolphinMainWindow::viewContainers() const
{
    QVector<DolphinViewContainer*> viewContainers;
    viewContainers.reserve(m_tabWidget->count());
    for (int i = 0; i < m_tabWidget->count(); ++i) {
        viewContainers << m_tabWidget->tabPageAt(i)->activeViewContainer();
    }
    return viewContainers;
}

224
void DolphinMainWindow::openDirectories(const QList<QUrl>& dirs, bool splitView)
225
{
226
    m_tabWidget->openDirectories(dirs, splitView);
227
228
}

229
230
231
232
233
void DolphinMainWindow::openDirectories(const QStringList& dirs, bool splitView)
{
    openDirectories(QUrl::fromStringList(dirs), splitView);
}

234
void DolphinMainWindow::openFiles(const QList<QUrl>& files, bool splitView)
235
{
236
    m_tabWidget->openFiles(files, splitView);
237
238
}

239
240
241
242
243
244
245
void DolphinMainWindow::openFiles(const QStringList& files, bool splitView)
{
    openFiles(QUrl::fromStringList(files), splitView);
}

void DolphinMainWindow::activateWindow()
{
246
247
    window()->setAttribute(Qt::WA_NativeWindow, true);
    KStartupInfo::setNewStartupId(window()->windowHandle(), KStartupInfo::startupId());
248
249
250
    KWindowSystem::activateWindow(window()->effectiveWinId());
}

251
void DolphinMainWindow::showCommand(CommandType command)
252
{
253
254
255
    DolphinStatusBar* statusBar = m_activeViewContainer->statusBar();
    switch (command) {
    case KIO::FileUndoManager::Copy:
256
        statusBar->setText(i18nc("@info:status", "Successfully copied."));
257
258
        break;
    case KIO::FileUndoManager::Move:
259
        statusBar->setText(i18nc("@info:status", "Successfully moved."));
260
261
        break;
    case KIO::FileUndoManager::Link:
262
        statusBar->setText(i18nc("@info:status", "Successfully linked."));
263
264
        break;
    case KIO::FileUndoManager::Trash:
265
        statusBar->setText(i18nc("@info:status", "Successfully moved to trash."));
266
267
        break;
    case KIO::FileUndoManager::Rename:
268
        statusBar->setText(i18nc("@info:status", "Successfully renamed."));
269
270
271
        break;

    case KIO::FileUndoManager::Mkdir:
272
        statusBar->setText(i18nc("@info:status", "Created folder."));
273
274
275
276
277
        break;

    default:
        break;
    }
278
}
Peter Penz's avatar
Peter Penz committed
279

280
281
282
283
284
void DolphinMainWindow::pasteIntoFolder()
{
    m_activeViewContainer->view()->pasteIntoFolder();
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
285
void DolphinMainWindow::changeUrl(const QUrl &url)
Peter Penz's avatar
Peter Penz committed
286
{
287
    if (!KProtocolManager::supportsListing(url)) {
288
289
290
291
292
        // The URL navigator only checks for validity, not
        // if the URL can be listed. An error message is
        // shown due to DolphinViewContainer::restoreView().
        return;
    }
293

294
    m_activeViewContainer->setUrl(url);
295
    updateFileAndEditActions();
296
297
298
299
300
    updatePasteAction();
    updateViewActions();
    updateGoActions();

    emit urlChanged(url);
Peter Penz's avatar
Peter Penz committed
301
302
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
303
void DolphinMainWindow::slotTerminalDirectoryChanged(const QUrl& url)
304
{
305
306
307
308
309
    if (m_tearDownFromPlacesRequested && url == QUrl::fromLocalFile(QDir::homePath())) {
        m_placesPanel->proceedWithTearDown();
        m_tearDownFromPlacesRequested = false;
    }

310
311
312
313
314
    m_activeViewContainer->setAutoGrabFocus(false);
    changeUrl(url);
    m_activeViewContainer->setAutoGrabFocus(true);
}

315
316
317
void DolphinMainWindow::slotEditableStateChanged(bool editable)
{
    KToggleAction* editableLocationAction =
318
        static_cast<KToggleAction*>(actionCollection()->action(QStringLiteral("editable_location")));
319
320
321
    editableLocationAction->setChecked(editable);
}

322
void DolphinMainWindow::slotSelectionChanged(const KFileItemList& selection)
Peter Penz's avatar
Peter Penz committed
323
{
324
    updateFileAndEditActions();
Peter Penz's avatar
Peter Penz committed
325

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

328
    QAction* compareFilesAction = actionCollection()->action(QStringLiteral("compare_files"));
329
    if (selectedUrlsCount == 2) {
330
        compareFilesAction->setEnabled(isKompareInstalled());
331
332
333
    } else {
        compareFilesAction->setEnabled(false);
    }
Peter Penz's avatar
Peter Penz committed
334
335
336
337

    emit selectionChanged(selection);
}

338
void DolphinMainWindow::updateHistory()
Peter Penz's avatar
Peter Penz committed
339
{
340
341
342
    const KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    const int index = urlNavigator->historyIndex();

343
    QAction* backAction = actionCollection()->action(KStandardAction::name(KStandardAction::Back));
344
    if (backAction) {
345
        backAction->setToolTip(i18nc("@info", "Go back"));
346
        backAction->setWhatsThis(i18nc("@info:whatsthis go back", "Return to the previously viewed folder."));
347
348
349
        backAction->setEnabled(index < urlNavigator->historySize() - 1);
    }

350
    QAction* forwardAction = actionCollection()->action(KStandardAction::name(KStandardAction::Forward));
351
    if (forwardAction) {
352
        forwardAction->setToolTip(i18nc("@info", "Go forward"));
353
354
        forwardAction->setWhatsThis(xi18nc("@info:whatsthis go forward",
            "This undoes a <interface>Go|Back</interface> action."));
355
356
        forwardAction->setEnabled(index > 0);
    }
Peter Penz's avatar
Peter Penz committed
357
358
359
360
}

void DolphinMainWindow::updateFilterBarAction(bool show)
{
361
    QAction* showFilterBarAction = actionCollection()->action(QStringLiteral("show_filter_bar"));
Peter Penz's avatar
Peter Penz committed
362
363
364
365
366
    showFilterBarAction->setChecked(show);
}

void DolphinMainWindow::openNewMainWindow()
{
367
    Dolphin::openNewWindow({m_activeViewContainer->url()}, this);
Peter Penz's avatar
Peter Penz committed
368
369
}

370
void DolphinMainWindow::openNewActivatedTab()
371
{
372
    m_tabWidget->openNewActivatedTab();
373
374
}

375
376
377
378
379
380
void DolphinMainWindow::addToPlaces()
{
    QUrl url;
    QString name;

    // If nothing is selected, act on the current dir
Laurent Montel's avatar
Laurent Montel committed
381
    if (m_activeViewContainer->view()->selectedItems().isEmpty()) {
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
        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);
    }
}

401
void DolphinMainWindow::openNewTab(const QUrl& url, DolphinTabWidget::TabPlacement tabPlacement)
402
{
403
404
405
406
407
408
409
410
411
412
413
    m_tabWidget->openNewTab(url, QUrl(), tabPlacement);
}

void DolphinMainWindow::openNewTabAfterCurrentTab(const QUrl& url)
{
    m_tabWidget->openNewTab(url, QUrl(), DolphinTabWidget::AfterCurrentTab);
}

void DolphinMainWindow::openNewTabAfterLastTab(const QUrl& url)
{
    m_tabWidget->openNewTab(url, QUrl(), DolphinTabWidget::AfterLastTab);
414
415
}

416
417
void DolphinMainWindow::openInNewTab()
{
418
    const KFileItemList& list = m_activeViewContainer->view()->selectedItems();
419
420
421
422
423
    bool tabCreated = false;

    foreach (const KFileItem& item, list) {
        const QUrl& url = DolphinView::openItemAsFolderUrl(item);
        if (!url.isEmpty()) {
424
            openNewTabAfterCurrentTab(url);
425
            tabCreated = true;
426
        }
427
    }
428
429
430
431

    // if no new tab has been created from the selection
    // open the current directory in a new tab
    if (!tabCreated) {
432
        openNewTabAfterCurrentTab(m_activeViewContainer->url());
433
    }
434
435
436
437
}

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

440
    const KFileItemList list = m_activeViewContainer->view()->selectedItems();
441
442
    if (list.isEmpty()) {
        newWindowUrl = m_activeViewContainer->url();
443
444
445
    } else if (list.count() == 1) {
        const KFileItem& item = list.first();
        newWindowUrl = DolphinView::openItemAsFolderUrl(item);
446
447
448
    }

    if (!newWindowUrl.isEmpty()) {
449
        Dolphin::openNewWindow({newWindowUrl}, this);
450
451
452
    }
}

453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
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);
    }
}

469
470
471
void DolphinMainWindow::showEvent(QShowEvent* event)
{
    KXmlGuiWindow::showEvent(event);
472

473
474
475
476
477
    if (!event->spontaneous()) {
        m_activeViewContainer->view()->setFocus();
    }
}

Peter Penz's avatar
Peter Penz committed
478
479
void DolphinMainWindow::closeEvent(QCloseEvent* event)
{
480
481
482
    // Find out if Dolphin is closed directly by the user or
    // by the session manager because the session is closed
    bool closedByUser = true;
483
    if (qApp->isSavingSession()) {
484
485
486
        closedByUser = false;
    }

487
    if (m_tabWidget->count() > 1 && GeneralSettings::confirmClosingMultipleTabs() && closedByUser) {
488
489
        // Ask the user if he really wants to quit and close all tabs.
        // Open a confirmation dialog with 3 buttons:
490
491
492
        // QDialogButtonBox::Yes    -> Quit
        // QDialogButtonBox::No     -> Close only the current tab
        // QDialogButtonBox::Cancel -> do nothing
493
494
        QDialog *dialog = new QDialog(this, Qt::Dialog);
        dialog->setWindowTitle(i18nc("@title:window", "Confirmation"));
495
        dialog->setModal(true);
496
        QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
497
        KGuiItem::assign(buttons->button(QDialogButtonBox::Yes), KGuiItem(i18nc("@action:button 'Quit Dolphin' button", "&Quit %1", QGuiApplication::applicationDisplayName()), QIcon::fromTheme(QStringLiteral("application-exit"))));
498
        KGuiItem::assign(buttons->button(QDialogButtonBox::No), KGuiItem(i18n("C&lose Current Tab"), QIcon::fromTheme(QStringLiteral("tab-close"))));
499
500
        KGuiItem::assign(buttons->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
        buttons->button(QDialogButtonBox::Yes)->setDefault(true);
501
502
503

        bool doNotAskAgainCheckboxResult = false;

504
        const auto result = KMessageBox::createKMessageBox(dialog,
505
            buttons,
506
507
508
509
510
511
512
513
            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) {
514
            GeneralSettings::setConfirmClosingMultipleTabs(false);
515
516
517
        }

        switch (result) {
518
            case QDialogButtonBox::Yes:
519
520
                // Quit
                break;
521
            case QDialogButtonBox::No:
522
                // Close only the current tab
523
                m_tabWidget->closeTab();
Roman Inflianskas's avatar
Roman Inflianskas committed
524
                Q_FALLTHROUGH();
525
526
527
            default:
                event->ignore();
                return;
528
529
530
        }
    }

531
    if (m_terminalPanel && m_terminalPanel->hasProgramRunning() && GeneralSettings::confirmClosingTerminalRunningProgram() && closedByUser) {
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
        // 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),
549
                    KGuiItem(i18n("Show &Terminal Panel"), QIcon::fromTheme(QStringLiteral("dialog-scripts"))));
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
        }
        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;
580
581
582
        }
    }

583
    GeneralSettings::setVersion(CurrentDolphinVersion);
Laurent Montel's avatar
Laurent Montel committed
584
    GeneralSettings::self()->save();
Peter Penz's avatar
Peter Penz committed
585

586
    KXmlGuiWindow::closeEvent(event);
Peter Penz's avatar
Peter Penz committed
587
588
}

Peter Penz's avatar
Peter Penz committed
589
void DolphinMainWindow::saveProperties(KConfigGroup& group)
Peter Penz's avatar
Peter Penz committed
590
{
591
    m_tabWidget->saveProperties(group);
Peter Penz's avatar
Peter Penz committed
592
593
}

Peter Penz's avatar
Peter Penz committed
594
void DolphinMainWindow::readProperties(const KConfigGroup& group)
Peter Penz's avatar
Peter Penz committed
595
{
596
    m_tabWidget->readProperties(group);
Peter Penz's avatar
Peter Penz committed
597
598
599
600
}

void DolphinMainWindow::updateNewMenu()
{
601
    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
Peter Penz's avatar
Peter Penz committed
602
    m_newFileMenu->checkUpToDate();
603
    m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
Peter Penz's avatar
Peter Penz committed
604
605
}

606
607
void DolphinMainWindow::createDirectory()
{
608
    m_newFileMenu->setViewShowsHiddenFiles(activeViewContainer()->view()->hiddenFilesShown());
609
    m_newFileMenu->setPopupFiles(QList<QUrl>() << activeViewContainer()->url());
Peter Penz's avatar
Peter Penz committed
610
    m_newFileMenu->createDirectory();
611
612
}

Peter Penz's avatar
Peter Penz committed
613
614
615
616
617
void DolphinMainWindow::quit()
{
    close();
}

618
void DolphinMainWindow::showErrorMessage(const QString& message)
619
{
620
    m_activeViewContainer->showMessage(message, DolphinViewContainer::Error);
621
622
}

Peter Penz's avatar
Peter Penz committed
623
624
void DolphinMainWindow::slotUndoAvailable(bool available)
{
Bill Egert's avatar
Bill Egert committed
625
    QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo));
626
    if (undoAction) {
Peter Penz's avatar
Peter Penz committed
627
628
629
630
631
632
        undoAction->setEnabled(available);
    }
}

void DolphinMainWindow::slotUndoTextChanged(const QString& text)
{
Bill Egert's avatar
Bill Egert committed
633
    QAction* undoAction = actionCollection()->action(KStandardAction::name(KStandardAction::Undo));
634
    if (undoAction) {
Peter Penz's avatar
Peter Penz committed
635
636
637
638
639
640
641
        undoAction->setText(text);
    }
}

void DolphinMainWindow::undo()
{
    clearStatusBar();
642
643
    KIO::FileUndoManager::self()->uiInterface()->setParentWidget(this);
    KIO::FileUndoManager::self()->undo();
Peter Penz's avatar
Peter Penz committed
644
645
646
647
}

void DolphinMainWindow::cut()
{
648
    m_activeViewContainer->view()->cutSelectedItems();
Peter Penz's avatar
Peter Penz committed
649
650
651
652
}

void DolphinMainWindow::copy()
{
653
    m_activeViewContainer->view()->copySelectedItems();
Peter Penz's avatar
Peter Penz committed
654
655
656
657
}

void DolphinMainWindow::paste()
{
658
    m_activeViewContainer->view()->paste();
Peter Penz's avatar
Peter Penz committed
659
660
}

661
662
663
664
665
void DolphinMainWindow::find()
{
    m_activeViewContainer->setSearchModeEnabled(true);
}

666
667
668
669
670
671
void DolphinMainWindow::updateSearchAction()
{
    QAction* toggleSearchAction = actionCollection()->action(QStringLiteral("toggle_search"));
    toggleSearchAction->setChecked(m_activeViewContainer->isSearchModeEnabled());
}

Peter Penz's avatar
Peter Penz committed
672
673
void DolphinMainWindow::updatePasteAction()
{
Bill Egert's avatar
Bill Egert committed
674
    QAction* pasteAction = actionCollection()->action(KStandardAction::name(KStandardAction::Paste));
675
676
677
    QPair<bool, QString> pasteInfo = m_activeViewContainer->view()->pasteInfo();
    pasteAction->setEnabled(pasteInfo.first);
    pasteAction->setText(pasteInfo.second);
Peter Penz's avatar
Peter Penz committed
678
679
}

680
681
682
683
684
void DolphinMainWindow::slotDirectoryLoadingCompleted()
{
    updatePasteAction();
}

685
686
void DolphinMainWindow::slotToolBarActionMiddleClicked(QAction *action)
{
687
    if (action == actionCollection()->action(KStandardAction::name(KStandardAction::Back))) {
688
        goBackInNewTab();
689
    } else if (action == actionCollection()->action(KStandardAction::name(KStandardAction::Forward))) {
690
691
692
693
694
695
696
697
        goForwardInNewTab();
    } else if (action == actionCollection()->action(QStringLiteral("go_up"))) {
        goUpInNewTab();
    } else if (action == actionCollection()->action(QStringLiteral("go_home"))) {
        goHomeInNewTab();
    }
}

698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
void DolphinMainWindow::slotAboutToShowBackPopupMenu()
{
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    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>();
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    for (int i = gotoIndex - urlNavigator->historyIndex(); i > 0; --i) {
        goBack();
    }
}

void DolphinMainWindow::slotBackForwardActionMiddleClicked(QAction* action)
{
    if (action) {
        KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigator();
        openNewTabAfterCurrentTab(urlNavigator->locationUrl(action->data().value<int>()));
    }
}

void DolphinMainWindow::slotAboutToShowForwardPopupMenu()
{
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    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>();
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    for (int i = urlNavigator->historyIndex() - gotoIndex; i > 0; --i) {
        goForward();
    }
}

Peter Penz's avatar
Peter Penz committed
748
749
750
void DolphinMainWindow::selectAll()
{
    clearStatusBar();
751
752
753
754
755

    // 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
756
    QLineEdit* lineEdit = urlNavigator->editor()->lineEdit();
757
758
759
760
761
762
763
    const bool selectUrl = urlNavigator->isUrlEditable() &&
                           lineEdit->hasFocus();
    if (selectUrl) {
        lineEdit->selectAll();
    } else {
        m_activeViewContainer->view()->selectAll();
    }
Peter Penz's avatar
Peter Penz committed
764
765
766
767
768
}

void DolphinMainWindow::invertSelection()
{
    clearStatusBar();
769
    m_activeViewContainer->view()->invertSelection();
Peter Penz's avatar
Peter Penz committed
770
771
772
773
}

void DolphinMainWindow::toggleSplitView()
{
774
    DolphinTabPage* tabPage = m_tabWidget->currentTabPage();
775
    tabPage->setSplitViewEnabled(!tabPage->splitViewEnabled());
776

777
    updateViewActions();
Peter Penz's avatar
Peter Penz committed
778
779
}

780
781
782
void DolphinMainWindow::toggleSplitStash()
{
    DolphinTabPage* tabPage = m_tabWidget->currentTabPage();
arnav dhamija's avatar
arnav dhamija committed
783
784
    tabPage->setSplitViewEnabled(false);
    tabPage->setSplitViewEnabled(true, QUrl("stash:/"));
785
786
}

Peter Penz's avatar
Peter Penz committed
787
788
789
void DolphinMainWindow::reloadView()
{
    clearStatusBar();
790
    m_activeViewContainer->reload();
791
    m_activeViewContainer->statusBar()->updateSpaceInfo();
Peter Penz's avatar
Peter Penz committed
792
793
794
}

void DolphinMainWindow::stopLoading()
795
{
796
797
798
799
800
    m_activeViewContainer->view()->stopLoading();
}

void DolphinMainWindow::enableStopAction()
{
801
    actionCollection()->action(QStringLiteral("stop"))->setEnabled(true);
802
803
804
805
}

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

809
void DolphinMainWindow::showFilterBar()
Peter Penz's avatar
Peter Penz committed
810
{
811
    m_activeViewContainer->setFilterBarVisible(true);
Peter Penz's avatar
Peter Penz committed
812
813
814
815
816
817
}

void DolphinMainWindow::toggleEditLocation()
{
    clearStatusBar();

818
    QAction* action = actionCollection()->action(QStringLiteral("editable_location"));
819
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
820
    urlNavigator->setUrlEditable(action->isChecked());
Peter Penz's avatar
Peter Penz committed
821
822
}

823
void DolphinMainWindow::replaceLocation()
Peter Penz's avatar
Peter Penz committed
824
{
825
    KUrlNavigator* navigator = m_activeViewContainer->urlNavigator();
Elvis Angelaccio's avatar
Elvis Angelaccio committed
826
    QLineEdit* lineEdit = navigator->editor()->lineEdit();
827
828
829
830
831
832
833
834
835
836
837
838

    // 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
839
840
}

841
842
void DolphinMainWindow::togglePanelLockState()
{
843
    const bool newLockState = !GeneralSettings::lockPanels();
844
845
    foreach (QObject* child, children()) {
        DolphinDockWidget* dock = qobject_cast<DolphinDockWidget*>(child);
846
        if (dock) {
847
848
849
850
            dock->setLocked(newLockState);
        }
    }

851
    GeneralSettings::setLockPanels(newLockState);
852
853
}

854
855
void DolphinMainWindow::slotTerminalPanelVisibilityChanged()
{
856
    if (m_terminalPanel->isHiddenInVisibleWindow() && m_activeViewContainer) {
857
858
859
860
        m_activeViewContainer->view()->setFocus();
    }
}

Peter Penz's avatar
Peter Penz committed
861
862
void DolphinMainWindow::goBack()
{
863
864
    KUrlNavigator* urlNavigator = m_activeViewContainer->urlNavigator();
    urlNavigator->goBack();
865

866
867
868
    if (urlNavigator->locationState().isEmpty()) {
        // An empty location state indicates a redirection URL,
        // which must be skipped too
869
        urlNavigator->goBack();
870
    }
Peter Penz's avatar
Peter Penz committed
871
872
873
874
}

void DolphinMainWindow::goForward()
{
875
    m_activeViewContainer->urlNavigator()->goForward();
Peter Penz's avatar
Peter Penz committed
876
877
878
879
}

void DolphinMainWindow::goUp()
{
880
    m_activeViewContainer->urlNavigator()->goUp();
Peter Penz's avatar
Peter Penz committed
881
882
}

Peter Penz's avatar
Peter Penz committed
883
884
885
886
887
void DolphinMainWindow::goHome()
{
    m_activeViewContainer->urlNavigator()->goHome();
}

888
void DolphinMainWindow::goBackInNewTab()
889
{
890
891
    KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigator();
    const int index = urlNavigator->historyIndex() + 1;
892
    openNewTabAfterCurrentTab(urlNavigator->locationUrl(index));
893
894
}

895
void DolphinMainWindow::goForwardInNewTab()
896
{
897
898
    KUrlNavigator* urlNavigator = activeViewContainer()->urlNavigator();
    const int index = urlNavigator->historyIndex() - 1;
899
    openNewTabAfterCurrentTab(urlNavigator->locationUrl(index));
900
901
}

902
void DolphinMainWindow::goUpInNewTab()
903
{
904
    const QUrl currentUrl = activeViewContainer()->urlNavigator()->locationUrl();
905
    openNewTabAfterCurrentTab(KIO::upUrl(currentUrl));
906
907
}

908
void DolphinMainWindow::goHomeInNewTab()
909
{
910
    openNewTabAfterCurrentTab(Dolphin::homeUrl());
911
912
}

Peter Penz's avatar
Peter Penz committed
913
914
void DolphinMainWindow::compareFiles()
{
915
    const KFileItemList items = m_tabWidget->currentTabPage()->selectedItems();
916
917
918
919
    if (items.count() != 2) {
        // The action is disabled in this case, but it could have been triggered
        // via D-Bus, see https://bugs.kde.org/show_bug.cgi?id=325517
        return;
920
    }
Peter Penz's avatar
Peter Penz committed
921

Lukáš Tinkl's avatar
Lukáš Tinkl committed
922
923
    QUrl urlA = items.at(0).url();
    QUrl urlB = items.at(1).url();
Peter Penz's avatar
Peter Penz committed
924

925
    QString command(QStringLiteral("kompare -c \""));
Lukáš Tinkl's avatar
Lukáš Tinkl committed
926
    command.append(urlA.toDisplayString(QUrl::PreferLocalFile));
Peter Penz's avatar
Peter Penz committed
927
    command.append("\" \"");
Lukáš Tinkl's avatar
Lukáš Tinkl committed
928
    command.append(urlB.toDisplayString(QUrl::PreferLocalFile));
Peter Penz's avatar
Peter Penz committed
929
    command.append('\"');
930
931
932
933

    KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(command, this);
    job->setDesktopName(QStringLiteral("org.kde.kompare"));
    job->start();
Peter Penz's avatar
Peter Penz committed
934
935
}

Peter Penz's avatar
Peter Penz committed
936
937
938
939
void DolphinMainWindow::toggleShowMenuBar()
{
    const bool visible = menuBar()->isVisible();
    menuBar()->setVisible(!visible);
940
    if (visible) {
941
        createControlButton();
942
    } else {
943
        deleteControlButton();
944
    }
Peter Penz's avatar
Peter Penz committed
945
946
}

947
QString DolphinMainWindow::activeContainerLocalPath()
948
{
949
950
951
952
    KIO::StatJob* statJob = KIO::mostLocalUrl(m_activeViewContainer->url());
    KJobWidgets::setWindow(statJob, this);
    statJob->exec();
    QUrl url = statJob->mostLocalUrl();
953
    if (url.isLocalFile()) {
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
        return url.toLocalFile();
    }
    return QDir::homePath();
}

QPointer<QAction> DolphinMainWindow::preferredSearchTool()
{
    m_searchTools.clear();
    KMoreToolsMenuFactory("dolphin/search-tools").fillMenuFromGroupingNames(
        &m_searchTools, { "files-find" }, QUrl::fromLocalFile(activeContainerLocalPath())
    );
    QList<QAction*> actions = m_searchTools.actions();
    if (actions.isEmpty()) {
        return nullptr;
    }
    QAction* action = actions.first();
    if (action->isSeparator()) {
        return nullptr;
    }
    return action;
}

void DolphinMainWindow::setupUpdateOpenPreferredSearchToolAction()
{
    QAction* openPreferredSearchTool = actionCollection()->action(QStringLiteral("open_preferred_search_tool"));
    const QList<QWidget*> widgets = openPreferredSearchTool->associatedWidgets();
    for (QWidget* widget : widgets) {
        QMenu* menu = qobject_cast<QMenu*>(widget);
        if (menu) {
            connect(menu, &QMenu::aboutToShow, this, &DolphinMainWindow::updateOpenPreferredSearchToolAction);
        }
985
986
    }

987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
    // Update the open_preferred_search_tool action *before* the Configure Shortcuts window is shown,
    // since this action is then listed in that window and it should be up-to-date when it is displayed.
    // This update is instantaneous if user made no changes to the search tools in the meantime.
    // Maybe all KStandardActions should defer calls to their slots, so that we could simply connect() to trigger()?
    connect(
        actionCollection()->action(KStandardAction::name(KStandardAction::KeyBindings)), &QAction::hovered,
        this, &DolphinMainWindow::updateOpenPreferredSearchToolAction
    );

    updateOpenPreferredSearchToolAction();
}

void DolphinMainWindow::updateOpenPreferredSearchToolAction()
{
    QAction* openPreferredSearchTool = actionCollection()->action(QStringLiteral("open_preferred_search_tool"));
    if (!openPreferredSearchTool) {
        return;
    }
    QPointer<QAction> tool = preferredSearchTool();
    if (tool) {
        openPreferredSearchTool->setVisible(true);
        openPreferredSearchTool->setText(i18nc("@action:inmenu Tools", "Open %1", tool->text()));
        openPreferredSearchTool->setIcon(tool->icon());
    } else {
        openPreferredSearchTool->setVisible(false);
        // still visible in Shortcuts configuration window
        openPreferredSearchTool->setText(i18nc("@action:inmenu Tools", "Open Preferred Search Tool"));
        openPreferredSearchTool->setIcon(QIcon::fromTheme(QStringLiteral("search")));
    }
}

void DolphinMainWindow::openPreferredSearchTool()
{
    QPointer<QAction> tool = preferredSearchTool();
    if (tool) {
        tool->trigger();
    }
}

void DolphinMainWindow::openTerminal()
{
    KToolInvocation::invokeTerminal(QString(), activeContainerLocalPath());
1029
1030
}

Peter Penz's avatar
Peter Penz committed
1031
1032
void DolphinMainWindow::editSettings()
{
1033
    if (!m_settingsDialog) {
1034
1035
        DolphinViewContainer* container = activeViewContainer();
        container->view()->writeSettings();
1036

Lukáš Tinkl's avatar
Lukáš Tinkl committed
1037
        const QUrl url = container->url();
1038
        DolphinSettingsDialog* settingsDialog = new DolphinSettingsDialog(url, this);
1039
        connect(settingsDialog, &DolphinSettingsDialog::settingsChanged, this, &DolphinMainWindow::refreshViews);
1040
1041
1042
        settingsDialog->setAttribute(Qt::WA_DeleteOnClose);
        settingsDialog->show();
        m_settingsDialog = settingsDialog;
1043
    } else {
1044
        m_settingsDialog.data()->raise();
1045
    }