dolphinview.cpp 62.9 KB
Newer Older
1
2
3
4
5
6
/*
 * SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@gmail.com>
 * SPDX-FileCopyrightText: 2006 Gregor Kališnik <gregor@podnapisi.net>
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */
7
8
9

#include "dolphinview.h"

10
#include "dolphin_detailsmodesettings.h"
11
#include "dolphin_generalsettings.h"
12
#include "dolphinitemlistview.h"
Roman Inflianskas's avatar
Roman Inflianskas committed
13
#include "dolphinnewfilemenuobserver.h"
Peter Penz's avatar
Peter Penz committed
14
#include "draganddrophelper.h"
Roman Inflianskas's avatar
Roman Inflianskas committed
15
16
17
18
19
20
#include "kitemviews/kfileitemlistview.h"
#include "kitemviews/kfileitemmodel.h"
#include "kitemviews/kitemlistcontainer.h"
#include "kitemviews/kitemlistcontroller.h"
#include "kitemviews/kitemlistheader.h"
#include "kitemviews/kitemlistselectionmanager.h"
21
#include "versioncontrol/versioncontrolobserver.h"
22
#include "viewproperties.h"
23
#include "views/tooltips/tooltipmanager.h"
24
#include "zoomlevelinfo.h"
25

Vishesh Handa's avatar
Vishesh Handa committed
26
#ifdef HAVE_BALOO
Roman Inflianskas's avatar
Roman Inflianskas committed
27
#include <Baloo/IndexerConfig>
28
#endif
Roman Inflianskas's avatar
Roman Inflianskas committed
29
30
31
32
#include <KColorScheme>
#include <KDesktopFile>
#include <KDirModel>
#include <KFileItemListProperties>
33
#include <KFormat>
Roman Inflianskas's avatar
Roman Inflianskas committed
34
35
36
37
38
39
40
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/DropJob>
#include <KIO/JobUiDelegate>
#include <KIO/Paste>
#include <KIO/PasteJob>
#include <KIO/PreviewJob>
41
#include <KIO/RenameFileDialog>
Roman Inflianskas's avatar
Roman Inflianskas committed
42
43
44
45
46
47
48
49
50
51
52
#include <KJobWidgets>
#include <KLocalizedString>
#include <KMessageBox>
#include <KProtocolManager>

#include <QAbstractItemView>
#include <QApplication>
#include <QClipboard>
#include <QDropEvent>
#include <QGraphicsSceneDragDropEvent>
#include <QMenu>
Nate Graham's avatar
Nate Graham committed
53
#include <QMimeDatabase>
Roman Inflianskas's avatar
Roman Inflianskas committed
54
55
56
#include <QPixmapCache>
#include <QPointer>
#include <QScrollBar>
57
#include <QSize>
Roman Inflianskas's avatar
Roman Inflianskas committed
58
59
#include <QTimer>
#include <QVBoxLayout>
60

61
DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
62
    QWidget(parent),
63
    m_active(true),
64
    m_tabsForFiles(false),
65
    m_assureVisibleCurrentIndex(false),
66
    m_isFolderWritable(true),
67
    m_dragging(false),
68
    m_url(url),
69
    m_viewPropertiesContext(),
70
    m_mode(DolphinView::IconsView),
71
    m_visibleRoles(),
Kevin Funk's avatar
Kevin Funk committed
72
73
74
75
76
77
    m_topLayout(nullptr),
    m_model(nullptr),
    m_view(nullptr),
    m_container(nullptr),
    m_toolTipManager(nullptr),
    m_selectionChangedTimer(nullptr),
78
    m_currentItemUrl(),
79
    m_scrollToCurrentItem(false),
80
    m_restoredContentsPosition(),
81
    m_selectedUrls(),
82
    m_clearSelectionBeforeSelectingNewItems(false),
83
    m_markFirstNewlySelectedItemAsCurrent(false),
Kevin Funk's avatar
Kevin Funk committed
84
    m_versionControlObserver(nullptr),
85
    m_twoClicksRenamingTimer(nullptr)
86
{
87
    m_topLayout = new QVBoxLayout(this);
88
    m_topLayout->setSpacing(0);
Laurent Montel's avatar
Laurent Montel committed
89
    m_topLayout->setContentsMargins(0, 0, 0, 0);
90

91
92
93
    // When a new item has been created by the "Create New..." menu, the item should
    // get selected and it must be assured that the item will get visible. As the
    // creation is done asynchronously, several signals must be checked:
94
95
    connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::itemCreated,
            this, &DolphinView::observeCreatedItem);
96

97
98
99
    m_selectionChangedTimer = new QTimer(this);
    m_selectionChangedTimer->setSingleShot(true);
    m_selectionChangedTimer->setInterval(300);
100
101
    connect(m_selectionChangedTimer, &QTimer::timeout,
            this, &DolphinView::emitSelectionChangedSignal);
102

103
104
105
    m_model = new KFileItemModel(this);
    m_view = new DolphinItemListView();
    m_view->setEnabledSelectionToggles(GeneralSettings::showSelectionToggle());
106
    m_view->setVisibleRoles({"text"});
107
108
109
110
111
112
    applyModeToView();

    KItemListController* controller = new KItemListController(m_model, m_view, this);
    const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
    controller->setAutoActivationDelay(delay);

113
114
115
116
    // The EnlargeSmallPreviews setting can only be changed after the model
    // has been set in the view by KItemListController.
    m_view->setEnlargeSmallPreviews(GeneralSettings::enlargeSmallPreviews());

117
    m_container = new KItemListContainer(controller, this);
118
    m_container->installEventFilter(this);
Peter Penz's avatar
Peter Penz committed
119
    setFocusProxy(m_container);
120
121
    connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); });
    connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); });
122

123
    controller->setSelectionBehavior(KItemListController::MultiSelection);
124
125
126
127
128
129
130
131
132
133
134
135
    connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated);
    connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated);
    connect(controller, &KItemListController::itemMiddleClicked, this, &DolphinView::slotItemMiddleClicked);
    connect(controller, &KItemListController::itemContextMenuRequested, this, &DolphinView::slotItemContextMenuRequested);
    connect(controller, &KItemListController::viewContextMenuRequested, this, &DolphinView::slotViewContextMenuRequested);
    connect(controller, &KItemListController::headerContextMenuRequested, this, &DolphinView::slotHeaderContextMenuRequested);
    connect(controller, &KItemListController::mouseButtonPressed, this, &DolphinView::slotMouseButtonPressed);
    connect(controller, &KItemListController::itemHovered, this, &DolphinView::slotItemHovered);
    connect(controller, &KItemListController::itemUnhovered, this, &DolphinView::slotItemUnhovered);
    connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent);
    connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading);
    connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged);
136
    connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed);
Steffen Hartleib's avatar
Steffen Hartleib committed
137
138
139
    connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom);
    connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom);
    connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp);
140
141
142
143
144
145
146
147
148
149
150
151
152
153

    connect(m_model, &KFileItemModel::directoryLoadingStarted,       this, &DolphinView::slotDirectoryLoadingStarted);
    connect(m_model, &KFileItemModel::directoryLoadingCompleted,     this, &DolphinView::slotDirectoryLoadingCompleted);
    connect(m_model, &KFileItemModel::directoryLoadingCanceled,      this, &DolphinView::directoryLoadingCanceled);
    connect(m_model, &KFileItemModel::directoryLoadingProgress,   this, &DolphinView::directoryLoadingProgress);
    connect(m_model, &KFileItemModel::directorySortingProgress,   this, &DolphinView::directorySortingProgress);
    connect(m_model, &KFileItemModel::itemsChanged,
            this, &DolphinView::slotItemsChanged);
    connect(m_model, &KFileItemModel::itemsRemoved,    this, &DolphinView::itemCountChanged);
    connect(m_model, &KFileItemModel::itemsInserted,   this, &DolphinView::itemCountChanged);
    connect(m_model, &KFileItemModel::infoMessage,            this, &DolphinView::infoMessage);
    connect(m_model, &KFileItemModel::errorMessage,           this, &DolphinView::errorMessage);
    connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection);
    connect(m_model, &KFileItemModel::urlIsFileError,            this, &DolphinView::urlIsFileError);
154
155

    m_view->installEventFilter(this);
156
157
158
159
160
161
162
163
    connect(m_view, &DolphinItemListView::sortOrderChanged,
            this, &DolphinView::slotSortOrderChangedByHeader);
    connect(m_view, &DolphinItemListView::sortRoleChanged,
            this, &DolphinView::slotSortRoleChangedByHeader);
    connect(m_view, &DolphinItemListView::visibleRolesChanged,
            this, &DolphinView::slotVisibleRolesChangedByHeader);
    connect(m_view, &DolphinItemListView::roleEditingCanceled,
            this, &DolphinView::slotRoleEditingCanceled);
164
165
    connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished,
            this, &DolphinView::slotHeaderColumnWidthChangeFinished);
Peter Penz's avatar
Peter Penz committed
166

167
    KItemListSelectionManager* selectionManager = controller->selectionManager();
168
169
    connect(selectionManager, &KItemListSelectionManager::selectionChanged,
            this, &DolphinView::slotSelectionChanged);
170

171
#ifdef HAVE_BALOO
172
    m_toolTipManager = new ToolTipManager(this);
173
    connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated);
174
#endif
175

176
    m_versionControlObserver = new VersionControlObserver(this);
177
    m_versionControlObserver->setView(this);
178
    m_versionControlObserver->setModel(m_model);
179
180
181
    connect(m_versionControlObserver, &VersionControlObserver::infoMessage, this, &DolphinView::infoMessage);
    connect(m_versionControlObserver, &VersionControlObserver::errorMessage, this, &DolphinView::errorMessage);
    connect(m_versionControlObserver, &VersionControlObserver::operationCompletedMessage, this, &DolphinView::operationCompletedMessage);
182

183
184
185
186
    m_twoClicksRenamingTimer = new QTimer(this);
    m_twoClicksRenamingTimer->setSingleShot(true);
    connect(m_twoClicksRenamingTimer, &QTimer::timeout, this, &DolphinView::slotTwoClicksRenamingTimerTimeout);

187
    applyViewProperties();
188
189
190
    m_topLayout->addWidget(m_container);

    loadDirectory(url);
191
192
193
194
195
196
}

DolphinView::~DolphinView()
{
}

197
QUrl DolphinView::url() const
198
{
199
    return m_url;
200
201
}

202
203
204
205
206
207
208
209
void DolphinView::setActive(bool active)
{
    if (active == m_active) {
        return;
    }

    m_active = active;

210
    updatePalette();
211
212

    if (active) {
Peter Penz's avatar
Peter Penz committed
213
        m_container->setFocus();
214
        emit activated();
215
        emit writeStateChanged(m_isFolderWritable);
216
217
218
    }
}

219
220
bool DolphinView::isActive() const
{
221
    return m_active;
222
223
224
225
}

void DolphinView::setMode(Mode mode)
{
226
    if (mode != m_mode) {
227
        ViewProperties props(viewPropertiesUrl());
228
        props.setViewMode(mode);
229

230
231
232
233
234
        // We pass the new ViewProperties to applyViewProperties, rather than
        // storing them on disk and letting applyViewProperties() read them
        // from there, to prevent that changing the view mode fails if the
        // .directory file is not writable (see bug 318534).
        applyViewProperties(props);
235
    }
236
237
238
239
240
241
242
}

DolphinView::Mode DolphinView::mode() const
{
    return m_mode;
}

243
244
245
246
247
248
void DolphinView::setPreviewsShown(bool show)
{
    if (previewsShown() == show) {
        return;
    }

249
    ViewProperties props(viewPropertiesUrl());
250
251
    props.setPreviewsShown(show);

252
    const int oldZoomLevel = m_view->zoomLevel();
253
    m_view->setPreviewsShown(show);
254
    emit previewsShownChanged(show);
255
256
257
258
259

    const int newZoomLevel = m_view->zoomLevel();
    if (newZoomLevel != oldZoomLevel) {
        emit zoomLevelChanged(newZoomLevel, oldZoomLevel);
    }
260
261
}

262
bool DolphinView::previewsShown() const
263
{
264
    return m_view->previewsShown();
265
266
}

267
268
void DolphinView::setHiddenFilesShown(bool show)
{
269
    if (m_model->showHiddenFiles() == show) {
270
271
272
273
274
        return;
    }

    const KFileItemList itemList = selectedItems();
    m_selectedUrls.clear();
Lukáš Tinkl's avatar
Lukáš Tinkl committed
275
    m_selectedUrls = itemList.urlList();
276

277
    ViewProperties props(viewPropertiesUrl());
278
279
    props.setHiddenFilesShown(show);

280
    m_model->setShowHiddenFiles(show);
281
282
283
    emit hiddenFilesShownChanged(show);
}

284
bool DolphinView::hiddenFilesShown() const
285
{
286
    return m_model->showHiddenFiles();
287
288
}

289
290
291
292
293
294
void DolphinView::setGroupedSorting(bool grouped)
{
    if (grouped == groupedSorting()) {
        return;
    }

295
    ViewProperties props(viewPropertiesUrl());
296
297
298
299
300
301
302
303
304
    props.setGroupedSorting(grouped);
    props.save();

    m_container->controller()->model()->setGroupedSorting(grouped);

    emit groupedSortingChanged(grouped);
}

bool DolphinView::groupedSorting() const
305
{
306
    return m_model->groupedSorting();
307
308
}

309
KFileItemList DolphinView::items() const
310
{
311
    KFileItemList list;
312
    const int itemCount = m_model->count();
313
314
315
    list.reserve(itemCount);

    for (int i = 0; i < itemCount; ++i) {
316
        list.append(m_model->fileItem(i));
317
318
319
320
321
322
323
    }

    return list;
}

int DolphinView::itemsCount() const
{
324
    return m_model->count();
325
326
}

327
KFileItemList DolphinView::selectedItems() const
328
{
329
    const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
330

331
    KFileItemList selectedItems;
332
333
334
    const auto items = selectionManager->selectedItems();
    selectedItems.reserve(items.count());
    for (int index : items) {
335
        selectedItems.append(m_model->fileItem(index));
336
    }
337
    return selectedItems;
338
339
}

340
341
int DolphinView::selectedItemsCount() const
{
342
343
    const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
    return selectionManager->selectedItems().count();
344
345
}

346
void DolphinView::markUrlsAsSelected(const QList<QUrl>& urls)
347
{
348
    m_selectedUrls = urls;
349
350
}

351
void DolphinView::markUrlAsCurrent(const QUrl &url)
352
353
{
    m_currentItemUrl = url;
354
    m_scrollToCurrentItem = true;
355
356
}

357
void DolphinView::selectItems(const QRegularExpression &regexp, bool enabled)
358
{
359
360
361
362
363
    const KItemListSelectionManager::SelectionMode mode = enabled
                                                        ? KItemListSelectionManager::Select
                                                        : KItemListSelectionManager::Deselect;
    KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();

364
365
    for (int index = 0; index < m_model->count(); index++) {
        const KFileItem item = m_model->fileItem(index);
366
        if (regexp.match(item.text()).hasMatch()) {
367
            // An alternative approach would be to store the matching items in a KItemSet and
368
            // select them in one go after the loop, but we'd need a new function
369
            // KItemListSelectionManager::setSelected(KItemSet, SelectionMode mode)
370
371
372
373
            // for that.
            selectionManager->setSelected(index, 1, mode);
        }
    }
374
375
}

376
void DolphinView::setZoomLevel(int level)
377
{
378
    const int oldZoomLevel = zoomLevel();
379
    m_view->setZoomLevel(level);
380
    if (zoomLevel() != oldZoomLevel) {
381
        hideToolTip();
382
        emit zoomLevelChanged(zoomLevel(), oldZoomLevel);
383
    }
384
385
}

386
int DolphinView::zoomLevel() const
387
{
388
    return m_view->zoomLevel();
389
390
}

391
void DolphinView::setSortRole(const QByteArray& role)
392
{
393
394
    if (role != sortRole()) {
        updateSortRole(role);
395
396
397
    }
}

398
QByteArray DolphinView::sortRole() const
399
{
400
401
    const KItemModelBase* model = m_container->controller()->model();
    return model->sortRole();
402
403
404
405
406
}

void DolphinView::setSortOrder(Qt::SortOrder order)
{
    if (sortOrder() != order) {
407
        updateSortOrder(order);
408
409
410
411
412
    }
}

Qt::SortOrder DolphinView::sortOrder() const
{
413
    return m_model->sortOrder();
414
415
}

416
417
418
419
420
421
422
423
424
void DolphinView::setSortFoldersFirst(bool foldersFirst)
{
    if (sortFoldersFirst() != foldersFirst) {
        updateSortFoldersFirst(foldersFirst);
    }
}

bool DolphinView::sortFoldersFirst() const
{
425
    return m_model->sortDirectoriesFirst();
426
427
}

428
void DolphinView::setVisibleRoles(const QList<QByteArray>& roles)
429
{
430
    const QList<QByteArray> previousRoles = roles;
431

432
    ViewProperties props(viewPropertiesUrl());
433
    props.setVisibleRoles(roles);
434

435
    m_visibleRoles = roles;
436
    m_view->setVisibleRoles(roles);
437

438
    emit visibleRolesChanged(m_visibleRoles, previousRoles);
439
440
}

441
QList<QByteArray> DolphinView::visibleRoles() const
442
{
443
    return m_visibleRoles;
444
445
}

446
447
void DolphinView::reload()
{
448
449
450
    QByteArray viewState;
    QDataStream saveStream(&viewState, QIODevice::WriteOnly);
    saveState(saveStream);
451

452
    setUrl(url());
453
    loadDirectory(url(), true);
454
455
456

    QDataStream restoreStream(viewState);
    restoreState(restoreStream);
457
458
}

459
void DolphinView::readSettings()
460
{
461
    const int oldZoomLevel = m_view->zoomLevel();
462

Laurent Montel's avatar
Laurent Montel committed
463
    GeneralSettings::self()->load();
464
    m_view->readSettings();
465
    applyViewProperties();
466

467
468
469
470
    const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
    m_container->controller()->setAutoActivationDelay(delay);

    const int newZoomLevel = m_view->zoomLevel();
471
472
473
    if (newZoomLevel != oldZoomLevel) {
        emit zoomLevelChanged(newZoomLevel, oldZoomLevel);
    }
474
475
}

476
477
void DolphinView::writeSettings()
{
Laurent Montel's avatar
Laurent Montel committed
478
    GeneralSettings::self()->save();
479
    m_view->writeSettings();
480
481
}

482
483
void DolphinView::setNameFilter(const QString& nameFilter)
{
484
    m_model->setNameFilter(nameFilter);
485
486
}

487
488
QString DolphinView::nameFilter() const
{
489
    return m_model->nameFilter();
490
491
}

492
493
494
495
496
497
498
499
500
501
void DolphinView::setMimeTypeFilters(const QStringList& filters)
{
    return m_model->setMimeTypeFilters(filters);
}

QStringList DolphinView::mimeTypeFilters() const
{
    return m_model->mimeTypeFilters();
}

502
QString DolphinView::statusBarText() const
503
{
504
505
506
507
    QString summary;
    QString foldersText;
    QString filesText;

508
509
510
    int folderCount = 0;
    int fileCount = 0;
    KIO::filesize_t totalFileSize = 0;
511

512
    if (m_container->controller()->selectionManager()->hasSelection()) {
513
        // Give a summary of the status of the selected files
514
        const KFileItemList list = selectedItems();
515
        foreach (const KFileItem& item, list) {
516
517
518
519
            if (item.isDir()) {
                ++folderCount;
            } else {
                ++fileCount;
520
                totalFileSize += item.size();
521
522
            }
        }
523

524
        if (folderCount + fileCount == 1) {
525
526
            // If only one item is selected, show info about it
            return list.first().getStatusBarInfo();
527
        } else {
528
529
530
            // At least 2 items are selected
            foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount);
            filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount);
531
        }
532
533
    } else {
        calculateItemCount(fileCount, folderCount, totalFileSize);
534
535
        foldersText = i18ncp("@info:status", "1 Folder", "%1 Folders", folderCount);
        filesText = i18ncp("@info:status", "1 File", "%1 Files", fileCount);
536
537
    }

538
539
    if (fileCount > 0 && folderCount > 0) {
        summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)",
540
                        foldersText, filesText,
Laurent Montel's avatar
Laurent Montel committed
541
                        KFormat().formatByteSize(totalFileSize));
542
    } else if (fileCount > 0) {
543
544
        summary = i18nc("@info:status files (size)", "%1 (%2)",
                        filesText,
Laurent Montel's avatar
Laurent Montel committed
545
                        KFormat().formatByteSize(totalFileSize));
546
547
    } else if (folderCount > 0) {
        summary = foldersText;
548
549
    } else {
        summary = i18nc("@info:status", "0 Folders, 0 Files");
550
551
552
    }

    return summary;
553
554
}

555
QList<QAction*> DolphinView::versionControlActions(const KFileItemList& items) const
556
{
557
558
559
    QList<QAction*> actions;

    if (items.isEmpty()) {
560
        const KFileItem item = m_model->rootItem();
561
562
563
        if (!item.isNull()) {
            actions = m_versionControlObserver->actions(KFileItemList() << item);
        }
564
    } else {
565
        actions = m_versionControlObserver->actions(items);
566
567
568
    }

    return actions;
569
570
}

Lukáš Tinkl's avatar
Lukáš Tinkl committed
571
void DolphinView::setUrl(const QUrl& url)
572
{
573
    if (url == m_url) {
574
575
        return;
    }
576

577
578
    clearSelection();

579
    m_url = url;
580

Peter Penz's avatar
Peter Penz committed
581
    hideToolTip();
582

583
584
    disconnect(m_view, &DolphinItemListView::roleEditingFinished,
               this, &DolphinView::slotRoleEditingFinished);
585

586
587
588
589
    // It is important to clear the items from the model before
    // applying the view properties, otherwise expensive operations
    // might be done on the existing items although they get cleared
    // anyhow afterwards by loadDirectory().
590
    m_model->clear();
591
    applyViewProperties();
592
    loadDirectory(url);
593

594
    emit urlChanged(url);
595
596
}

597
598
void DolphinView::selectAll()
{
599
    KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
600
    selectionManager->setSelected(0, m_model->count());
601
602
603
604
}

void DolphinView::invertSelection()
{
605
    KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
606
    selectionManager->setSelected(0, m_model->count(), KItemListSelectionManager::Toggle);
607
608
609
610
}

void DolphinView::clearSelection()
{
611
    m_selectedUrls.clear();
612
    m_container->controller()->selectionManager()->clearSelection();
613
614
}

615
void DolphinView::renameSelectedItems()
616
{
617
    const KFileItemList items = selectedItems();
618
619
620
621
622
623
624
    if (items.isEmpty()) {
        return;
    }

    if (items.count() == 1 && GeneralSettings::renameInline()) {
        const int index = m_model->index(items.first());
        m_view->editRole(index, "text");
625

626
627
        hideToolTip();

628
629
        connect(m_view, &DolphinItemListView::roleEditingFinished,
                this, &DolphinView::slotRoleEditingFinished);
630
    } else {
631
632
633
        KIO::RenameFileDialog* dialog = new KIO::RenameFileDialog(items, this);
        connect(dialog, &KIO::RenameFileDialog::renamingFinished,
                this, &DolphinView::slotRenameDialogRenamingFinished);
Emirald Mateli's avatar
Emirald Mateli committed
634

635
        dialog->open();
636
637
638
639
640
641
    }

    // Assure that the current index remains visible when KFileItemModel
    // will notify the view about changed items (which might result in
    // a changed sorting).
    m_assureVisibleCurrentIndex = true;
642
643
}

644
void DolphinView::trashSelectedItems()
645
{
Lukáš Tinkl's avatar
Lukáš Tinkl committed
646
    const QList<QUrl> list = simplifiedSelectedUrls();
647
648
649
650
    KIO::JobUiDelegate uiDelegate;
    uiDelegate.setWindow(window());
    if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
        KIO::Job* job = KIO::trash(list);
651
        KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job);
652
653
654
655
        KJobWidgets::setWindow(job, this);
        connect(job, &KIO::Job::result,
                this, &DolphinView::slotTrashFileFinished);
    }
656
657
658
659
}

void DolphinView::deleteSelectedItems()
{
Lukáš Tinkl's avatar
Lukáš Tinkl committed
660
    const QList<QUrl> list = simplifiedSelectedUrls();
661

662
663
664
    KIO::JobUiDelegate uiDelegate;
    uiDelegate.setWindow(window());
    if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
665
        KIO::Job* job = KIO::del(list);
666
        KJobWidgets::setWindow(job, this);
667
668
        connect(job, &KIO::Job::result,
                this, &DolphinView::slotDeleteFileFinished);
669
    }
670
}
671

672
void DolphinView::cutSelectedItemsToClipboard()
673
{
674
    QMimeData* mimeData = selectionMimeData();
675
    KIO::setClipboardDataCut(mimeData, true);
676
    QApplication::clipboard()->setMimeData(mimeData);
677
678
}

679
void DolphinView::copySelectedItemsToClipboard()
680
{
681
    QMimeData* mimeData = selectionMimeData();
682
    QApplication::clipboard()->setMimeData(mimeData);
683
684
}

685
void DolphinView::copySelectedItems(const KFileItemList &selection, const QUrl &destinationUrl)
686
687
688
689
690
691
692
693
694
{
    KIO::CopyJob* job = KIO::copy(selection.urlList(), destinationUrl, KIO::DefaultFlags);
    KJobWidgets::setWindow(job, this);

    connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult);
    connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone);
    KIO::FileUndoManager::self()->recordCopyJob(job);
}

695
void DolphinView::moveSelectedItems(const KFileItemList &selection, const QUrl &destinationUrl)
696
697
698
699
700
701
702
703
704
705
{
    KIO::CopyJob* job = KIO::move(selection.urlList(), destinationUrl, KIO::DefaultFlags);
    KJobWidgets::setWindow(job, this);

    connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult);
    connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone);
    KIO::FileUndoManager::self()->recordCopyJob(job);

}

706
void DolphinView::paste()
707
{
708
709
    pasteToUrl(url());
}
710

711
712
713
714
715
void DolphinView::pasteIntoFolder()
{
    const KFileItemList items = selectedItems();
    if ((items.count() == 1) && items.first().isDir()) {
        pasteToUrl(items.first().url());
716
    }
717
}
718

Nate Graham's avatar
Nate Graham committed
719
720
721
722
723
724
725
726
727
728
729
730
731
732
void DolphinView::duplicateSelectedItems()
{
    const KFileItemList itemList = selectedItems();
    if (itemList.isEmpty()) {
        return;
    }

    const QMimeDatabase db;

    // Duplicate all selected items and append "copy" to the end of the file name
    // but before the filename extension, if present
    QList<QUrl> newSelection;
    for (const auto &item : itemList) {
        const QUrl originalURL  = item.url();
733
        const QString originalDirectoryPath = originalURL.adjusted(QUrl::RemoveFilename).path();
Nate Graham's avatar
Nate Graham committed
734
        const QString originalFileName = item.name();
735

Nate Graham's avatar
Nate Graham committed
736
737
738
739
740
741
        QString extension = db.suffixForFileName(originalFileName);

        QUrl duplicateURL = originalURL;

        // No extension; new filename is "<oldfilename> copy"
        if (extension.isEmpty()) {
742
            duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFileName));
Nate Graham's avatar
Nate Graham committed
743
744
745
746
747
748
749
750
        // There's an extension; new filename is "<oldfilename> copy.<extension>"
        } else {
            // Need to add a dot since QMimeDatabase::suffixForFileName() doesn't include it
            extension = QLatin1String(".") + extension;
            const QString originalFilenameWithoutExtension = originalFileName.chopped(extension.size());
            // Preserve file's original filename extension in case the casing differs
            // from what QMimeDatabase::suffixForFileName() returned
            const QString originalExtension = originalFileName.right(extension.size());
751
            duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFilenameWithoutExtension) + originalExtension);
Nate Graham's avatar
Nate Graham committed
752
753
        }

754
        KIO::CopyJob* job = KIO::copyAs(originalURL, duplicateURL);
Nate Graham's avatar
Nate Graham committed
755
756
757
758
759
760
761
        KJobWidgets::setWindow(job, this);

        if (job) {
            newSelection << duplicateURL;
            KIO::FileUndoManager::self()->recordCopyJob(job);
        }
    }
Elvis Angelaccio's avatar
Elvis Angelaccio committed
762
763

    forceUrlsSelection(newSelection.first(), newSelection);
Nate Graham's avatar
Nate Graham committed
764
765
}

766
767
768
769
770
void DolphinView::stopLoading()
{
    m_model->cancelDirectoryLoading();
}

771
772
void DolphinView::updatePalette()
{
773
    QColor color = KColorScheme(isActiveWindow() ? QPalette::Active : QPalette::Inactive, KColorScheme::View).background().color();
774
775
776
777
778
779
780
781
782
783
784
785
786
787
    if (!m_active) {
        color.setAlpha(150);
    }

    QWidget* viewport = m_container->viewport();
    if (viewport) {
        QPalette palette;
        palette.setColor(viewport->backgroundRole(), color);
        viewport->setPalette(palette);
    }

    update();
}

788
789
790
791
792
793
void DolphinView::abortTwoClicksRenaming()
{
    m_twoClicksRenamingItemUrl.clear();
    m_twoClicksRenamingTimer->stop();
}

794
bool DolphinView::eventFilter(QObject* watched, QEvent* event)
795
{
796
    switch (event->type()) {
797
798
    case QEvent::PaletteChange:
        updatePalette();
799
        QPixmapCache::clear();
800
801
        break;

802
803
804
805
806
    case QEvent::WindowActivate:
    case QEvent::WindowDeactivate:
        updatePalette();
        break;

807
    case QEvent::KeyPress:
808
        hideToolTip(ToolTipManager::HideBehavior::Instantly);
809
810
811
        if (GeneralSettings::useTabForSwitchingSplitView()) {
            QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
            if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) {
Elvis Angelaccio's avatar
Elvis Angelaccio committed
812
                emit toggleActiveViewRequested();
813
814
815
816
                return true;
            }
        }
        break;
817
818
819
820
821
822
    case QEvent::FocusIn:
        if (watched == m_container) {
            setActive(true);
        }
        break;

823
    case QEvent::GraphicsSceneDragEnter:
824
        if (watched == m_view) {
825
            m_dragging = true;
826
            abortTwoClicksRenaming();
827
828
829
830
        }
        break;

    case QEvent::GraphicsSceneDragLeave:
831
        if (watched == m_view) {
832
833
834
835
836
            m_dragging = false;
        }
        break;

    case QEvent::GraphicsSceneDrop:
837
        if (watched == m_view) {
838
839
            m_dragging = false;
        }
840
841
842
843
844
    default:
        break;
    }

    return QWidget::eventFilter(watched, event);
845
}
846

847
848
849
850
851
852
853
854
855
856
857
858
859
void DolphinView::wheelEvent(QWheelEvent* event)
{
    if (event->modifiers().testFlag(Qt::ControlModifier)) {
        const int numDegrees = event->delta() / 8;
        const int numSteps = numDegrees / 15;

        setZoomLevel(zoomLevel() + numSteps);
        event->accept();
    } else {
        event->ignore();
    }
}

860
861
862
863
864
865
void DolphinView::hideEvent(QHideEvent* event)
{
    hideToolTip();
    QWidget::hideEvent(event);
}

866
867
868
bool DolphinView::event(QEvent* event)
{
    if (event->type() == QEvent::WindowDeactivate) {
869
870
871
872
873
        /* See Bug 297355
         * Dolphin leaves file preview tooltips open even when is not visible.
         *
         * Hide tool-tip when Dolphin loses focus.
         */
874
        hideToolTip();
875
        abortTwoClicksRenaming();
876
877
878
879
880
    }

    return QWidget::event(event);
}

881
void DolphinView::activate()
882
{
883
884
885
    setActive(true);
}

886
887
void DolphinView::slotItemActivated(int index)
{
888
889
    abortTwoClicksRenaming();

890
    const KFileItem item = m_model->fileItem(index);
891
892
893
894
    if (!item.isNull()) {
        emit itemActivated(item);
    }
}
895

896
void DolphinView::slotItemsActivated(const KItemSet& indexes)
897
898
899
{
    Q_ASSERT(indexes.count() >= 2);

900
901
    abortTwoClicksRenaming();

902
903
    if (indexes.count() > 5) {
        QString question = i18np("Are you sure you want to open 1 item?", "Are you sure you want to open %1 items?", indexes.count());
904
905
906
907
908
909
        const int answer = KMessageBox::warningYesNo(this, question);
        if (answer != KMessageBox::Yes) {
            return;
        }
    }

910
911
912
    KFileItemList items;
    items.reserve(indexes.count());

913
    for (int index : indexes) {
914
        KFileItem item = m_model->fileItem(index);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
915
        const QUrl& url = openItemAsFolderUrl(item);
916

917
        if (!url.isEmpty()) { // Open folders in new tabs
918
            emit tabRequested(url, DolphinTabWidget::AfterLastTab);
919
        } else {
920
            items.append(item);
921
922
        }
    }
923
924
925
926
927
928

    if (items.count() == 1) {
        emit itemActivated(items.first());
    } else if (items.count() > 1) {
        emit itemsActivated(items);
    }
929
930
931
}

void DolphinView::slotItemMiddleClicked(int index)
932
{
933
    const KFileItem& item = m_model->fileItem(index);
Lukáš Tinkl's avatar
Lukáš Tinkl committed
934
    const QUrl& url = openItemAsFolderUrl(item);
935
    if (!url.isEmpty()) {
936
        emit tabRequested(url, DolphinTabWidget::AfterCurrentTab);
937
    } else if (isTabsForFilesEnabled()) {
938
        emit tabRequested(item.url(), DolphinTabWidget::AfterCurrentTab);
939
940
    }
}