]> cloud.milkyroute.net Git - dolphin.git/blob - src/views/dolphinview.cpp
dolphinview: Fix right click broken on placeholderLabel
[dolphin.git] / src / views / dolphinview.cpp
1 /*
2 * SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@gmail.com>
3 * SPDX-FileCopyrightText: 2006 Gregor Kališnik <gregor@podnapisi.net>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8 #include "dolphinview.h"
9
10 #include "dolphin_generalsettings.h"
11 #include "dolphin_detailsmodesettings.h"
12 #include "dolphinitemlistview.h"
13 #include "dolphinnewfilemenuobserver.h"
14 #include "draganddrophelper.h"
15 #include "kitemviews/kfileitemlistview.h"
16 #include "kitemviews/kfileitemmodel.h"
17 #include "kitemviews/kitemlistcontainer.h"
18 #include "kitemviews/kitemlistcontroller.h"
19 #include "kitemviews/kitemlistheader.h"
20 #include "kitemviews/kitemlistselectionmanager.h"
21 #include "kitemviews/private/kitemlistroleeditor.h"
22 #include "settings/viewmodes/viewmodesettings.h"
23 #include "selectionmode/singleclickselectionproxystyle.h"
24 #include "versioncontrol/versioncontrolobserver.h"
25 #include "viewproperties.h"
26 #include "views/tooltips/tooltipmanager.h"
27 #include "zoomlevelinfo.h"
28
29 #if HAVE_BALOO
30 #include <Baloo/IndexerConfig>
31 #endif
32 #include <KColorScheme>
33 #include <KDesktopFile>
34 #include <KDirModel>
35 #include <KFileItemListProperties>
36 #include <KFormat>
37 #include <KIO/CopyJob>
38 #include <KIO/DeleteJob>
39 #include <KIO/DropJob>
40 #include <KIO/JobUiDelegate>
41 #include <KIO/Paste>
42 #include <KIO/PasteJob>
43 #include <KIO/RenameFileDialog>
44 #include <KJobWidgets>
45 #include <KLocalizedString>
46 #include <KMessageBox>
47 #include <KProtocolManager>
48 #include <KUrlMimeData>
49
50 #include <kwidgetsaddons_version.h>
51
52 #include <kio_version.h>
53 #if KIO_VERSION >= QT_VERSION_CHECK(5, 100, 0)
54 #include <KIO/DeleteOrTrashJob>
55 #endif
56
57 #include <QAbstractItemView>
58 #include <QActionGroup>
59 #include <QApplication>
60 #include <QClipboard>
61 #include <QDropEvent>
62 #include <QGraphicsOpacityEffect>
63 #include <QGraphicsSceneDragDropEvent>
64 #include <QLabel>
65 #include <QMenu>
66 #include <QMimeDatabase>
67 #include <QPixmapCache>
68 #include <QScrollBar>
69 #include <QSize>
70 #include <QTimer>
71 #include <QToolTip>
72 #include <QVBoxLayout>
73
74 DolphinView::DolphinView(const QUrl& url, QWidget* parent) :
75 QWidget(parent),
76 m_active(true),
77 m_tabsForFiles(false),
78 m_assureVisibleCurrentIndex(false),
79 m_isFolderWritable(true),
80 m_dragging(false),
81 m_url(url),
82 m_viewPropertiesContext(),
83 m_mode(DolphinView::IconsView),
84 m_visibleRoles(),
85 m_topLayout(nullptr),
86 m_model(nullptr),
87 m_view(nullptr),
88 m_container(nullptr),
89 m_toolTipManager(nullptr),
90 m_selectionChangedTimer(nullptr),
91 m_currentItemUrl(),
92 m_scrollToCurrentItem(false),
93 m_restoredContentsPosition(),
94 m_selectedUrls(),
95 m_clearSelectionBeforeSelectingNewItems(false),
96 m_markFirstNewlySelectedItemAsCurrent(false),
97 m_versionControlObserver(nullptr),
98 m_twoClicksRenamingTimer(nullptr),
99 m_placeholderLabel(nullptr),
100 m_showLoadingPlaceholderTimer(nullptr)
101 {
102 m_topLayout = new QVBoxLayout(this);
103 m_topLayout->setSpacing(0);
104 m_topLayout->setContentsMargins(0, 0, 0, 0);
105
106 // When a new item has been created by the "Create New..." menu, the item should
107 // get selected and it must be assured that the item will get visible. As the
108 // creation is done asynchronously, several signals must be checked:
109 connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::itemCreated,
110 this, &DolphinView::observeCreatedItem);
111
112 m_selectionChangedTimer = new QTimer(this);
113 m_selectionChangedTimer->setSingleShot(true);
114 m_selectionChangedTimer->setInterval(300);
115 connect(m_selectionChangedTimer, &QTimer::timeout,
116 this, &DolphinView::emitSelectionChangedSignal);
117
118 m_model = new KFileItemModel(this);
119 m_view = new DolphinItemListView();
120 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::FollowSetting);
121 m_view->setVisibleRoles({"text"});
122 applyModeToView();
123
124 KItemListController* controller = new KItemListController(m_model, m_view, this);
125 const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
126 controller->setAutoActivationDelay(delay);
127
128 // The EnlargeSmallPreviews setting can only be changed after the model
129 // has been set in the view by KItemListController.
130 m_view->setEnlargeSmallPreviews(GeneralSettings::enlargeSmallPreviews());
131
132 m_container = new KItemListContainer(controller, this);
133 m_container->installEventFilter(this);
134 setFocusProxy(m_container);
135 connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); });
136 connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] { hideToolTip(); });
137
138 m_showLoadingPlaceholderTimer = new QTimer(this);
139 m_showLoadingPlaceholderTimer->setInterval(500);
140 m_showLoadingPlaceholderTimer->setSingleShot(true);
141 connect(m_showLoadingPlaceholderTimer, &QTimer::timeout, this, &DolphinView::showLoadingPlaceholder);
142
143 // Show some placeholder text for empty folders
144 // This is made using a heavily-modified QLabel rather than a KTitleWidget
145 // because KTitleWidget can't be told to turn off mouse-selectable text
146 m_placeholderLabel = new QLabel(this);
147 QFont placeholderLabelFont;
148 // To match the size of a level 2 Heading/KTitleWidget
149 placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
150 m_placeholderLabel->setFont(placeholderLabelFont);
151 m_placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
152 m_placeholderLabel->setWordWrap(true);
153 m_placeholderLabel->setAlignment(Qt::AlignCenter);
154 // Match opacity of QML placeholder label component
155 auto *effect = new QGraphicsOpacityEffect(m_placeholderLabel);
156 effect->setOpacity(0.5);
157 m_placeholderLabel->setGraphicsEffect(effect);
158 // Set initial text and visibility
159 updatePlaceholderLabel();
160
161 auto *centeringLayout = new QVBoxLayout(m_container);
162 centeringLayout->addWidget(m_placeholderLabel);
163 centeringLayout->setAlignment(m_placeholderLabel, Qt::AlignCenter);
164 m_placeholderLabel->setContextMenuPolicy(Qt::CustomContextMenu);
165 connect(m_placeholderLabel, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos){
166 slotViewContextMenuRequested(m_placeholderLabel->mapToGlobal(pos));
167 });
168
169 controller->setSelectionBehavior(KItemListController::MultiSelection);
170 connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated);
171 connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated);
172 connect(controller, &KItemListController::itemMiddleClicked, this, &DolphinView::slotItemMiddleClicked);
173 connect(controller, &KItemListController::itemContextMenuRequested, this, &DolphinView::slotItemContextMenuRequested);
174 connect(controller, &KItemListController::viewContextMenuRequested, this, &DolphinView::slotViewContextMenuRequested);
175 connect(controller, &KItemListController::headerContextMenuRequested, this, &DolphinView::slotHeaderContextMenuRequested);
176 connect(controller, &KItemListController::mouseButtonPressed, this, &DolphinView::slotMouseButtonPressed);
177 connect(controller, &KItemListController::itemHovered, this, &DolphinView::slotItemHovered);
178 connect(controller, &KItemListController::itemUnhovered, this, &DolphinView::slotItemUnhovered);
179 connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent);
180 connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading);
181 connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged);
182 connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed);
183 connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom);
184 connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom);
185 connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp);
186 connect(controller, &KItemListController::selectionModeChangeRequested, this, &DolphinView::selectionModeChangeRequested);
187
188 connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted);
189 connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted);
190 connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::slotDirectoryLoadingCanceled);
191 connect(m_model, &KFileItemModel::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress);
192 connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress);
193 connect(m_model, &KFileItemModel::itemsChanged,
194 this, &DolphinView::slotItemsChanged);
195 connect(m_model, &KFileItemModel::itemsRemoved, this, &DolphinView::itemCountChanged);
196 connect(m_model, &KFileItemModel::itemsInserted, this, &DolphinView::itemCountChanged);
197 connect(m_model, &KFileItemModel::infoMessage, this, &DolphinView::infoMessage);
198 connect(m_model, &KFileItemModel::errorMessage, this, &DolphinView::errorMessage);
199 connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection);
200 connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError);
201 connect(m_model, &KFileItemModel::fileItemsChanged, this, &DolphinView::fileItemsChanged);
202 connect(m_model, &KFileItemModel::currentDirectoryRemoved, this, &DolphinView::currentDirectoryRemoved);
203
204 connect(this, &DolphinView::itemCountChanged,
205 this, &DolphinView::updatePlaceholderLabel);
206
207 m_view->installEventFilter(this);
208 connect(m_view, &DolphinItemListView::sortOrderChanged,
209 this, &DolphinView::slotSortOrderChangedByHeader);
210 connect(m_view, &DolphinItemListView::sortRoleChanged,
211 this, &DolphinView::slotSortRoleChangedByHeader);
212 connect(m_view, &DolphinItemListView::visibleRolesChanged,
213 this, &DolphinView::slotVisibleRolesChangedByHeader);
214 connect(m_view, &DolphinItemListView::roleEditingCanceled,
215 this, &DolphinView::slotRoleEditingCanceled);
216 connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished,
217 this, &DolphinView::slotHeaderColumnWidthChangeFinished);
218 connect(m_view->header(), &KItemListHeader::sidePaddingChanged,
219 this, &DolphinView::slotSidePaddingWidthChanged);
220
221 KItemListSelectionManager* selectionManager = controller->selectionManager();
222 connect(selectionManager, &KItemListSelectionManager::selectionChanged,
223 this, &DolphinView::slotSelectionChanged);
224
225 #if HAVE_BALOO
226 m_toolTipManager = new ToolTipManager(this);
227 connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated);
228 #endif
229
230 m_versionControlObserver = new VersionControlObserver(this);
231 m_versionControlObserver->setView(this);
232 m_versionControlObserver->setModel(m_model);
233 connect(m_versionControlObserver, &VersionControlObserver::infoMessage, this, &DolphinView::infoMessage);
234 connect(m_versionControlObserver, &VersionControlObserver::errorMessage, this, &DolphinView::errorMessage);
235 connect(m_versionControlObserver, &VersionControlObserver::operationCompletedMessage, this, &DolphinView::operationCompletedMessage);
236
237 m_twoClicksRenamingTimer = new QTimer(this);
238 m_twoClicksRenamingTimer->setSingleShot(true);
239 connect(m_twoClicksRenamingTimer, &QTimer::timeout, this, &DolphinView::slotTwoClicksRenamingTimerTimeout);
240
241 applyViewProperties();
242 m_topLayout->addWidget(m_container);
243
244 loadDirectory(url);
245 }
246
247 DolphinView::~DolphinView()
248 {
249 disconnect(m_container->controller(), &KItemListController::modelChanged, this, &DolphinView::slotModelChanged);
250 }
251
252 QUrl DolphinView::url() const
253 {
254 return m_url;
255 }
256
257 void DolphinView::setActive(bool active)
258 {
259 if (active == m_active) {
260 return;
261 }
262
263 m_active = active;
264
265 updatePalette();
266
267 if (active) {
268 m_container->setFocus();
269 Q_EMIT activated();
270 Q_EMIT writeStateChanged(m_isFolderWritable);
271 }
272 }
273
274 bool DolphinView::isActive() const
275 {
276 return m_active;
277 }
278
279 void DolphinView::setViewMode(Mode mode)
280 {
281 if (mode != m_mode) {
282 ViewProperties props(viewPropertiesUrl());
283 props.setViewMode(mode);
284
285 // We pass the new ViewProperties to applyViewProperties, rather than
286 // storing them on disk and letting applyViewProperties() read them
287 // from there, to prevent that changing the view mode fails if the
288 // .directory file is not writable (see bug 318534).
289 applyViewProperties(props);
290 }
291 }
292
293 DolphinView::Mode DolphinView::viewMode() const
294 {
295 return m_mode;
296 }
297
298 void DolphinView::setSelectionModeEnabled(const bool enabled)
299 {
300 if (enabled) {
301 m_proxyStyle = std::make_unique<SelectionMode::SingleClickSelectionProxyStyle>();
302 setStyle(m_proxyStyle.get());
303 m_view->setStyle(m_proxyStyle.get());
304 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::False);
305 } else {
306 setStyle(QApplication::style());
307 m_view->setStyle(QApplication::style());
308 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::FollowSetting);
309 }
310 m_container->controller()->setSelectionModeEnabled(enabled);
311 }
312
313 bool DolphinView::selectionMode() const
314 {
315 return m_container->controller()->selectionMode();
316 }
317
318 void DolphinView::setPreviewsShown(bool show)
319 {
320 if (previewsShown() == show) {
321 return;
322 }
323
324 ViewProperties props(viewPropertiesUrl());
325 props.setPreviewsShown(show);
326
327 const int oldZoomLevel = m_view->zoomLevel();
328 m_view->setPreviewsShown(show);
329 Q_EMIT previewsShownChanged(show);
330
331 const int newZoomLevel = m_view->zoomLevel();
332 if (newZoomLevel != oldZoomLevel) {
333 Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel);
334 }
335 }
336
337 bool DolphinView::previewsShown() const
338 {
339 return m_view->previewsShown();
340 }
341
342 void DolphinView::setHiddenFilesShown(bool show)
343 {
344 if (m_model->showHiddenFiles() == show) {
345 return;
346 }
347
348 const KFileItemList itemList = selectedItems();
349 m_selectedUrls.clear();
350 m_selectedUrls = itemList.urlList();
351
352 ViewProperties props(viewPropertiesUrl());
353 props.setHiddenFilesShown(show);
354
355 m_model->setShowHiddenFiles(show);
356 Q_EMIT hiddenFilesShownChanged(show);
357 }
358
359 bool DolphinView::hiddenFilesShown() const
360 {
361 return m_model->showHiddenFiles();
362 }
363
364 void DolphinView::setGroupedSorting(bool grouped)
365 {
366 if (grouped == groupedSorting()) {
367 return;
368 }
369
370 ViewProperties props(viewPropertiesUrl());
371 props.setGroupedSorting(grouped);
372 props.save();
373
374 m_container->controller()->model()->setGroupedSorting(grouped);
375
376 Q_EMIT groupedSortingChanged(grouped);
377 }
378
379 bool DolphinView::groupedSorting() const
380 {
381 return m_model->groupedSorting();
382 }
383
384 KFileItemList DolphinView::items() const
385 {
386 KFileItemList list;
387 const int itemCount = m_model->count();
388 list.reserve(itemCount);
389
390 for (int i = 0; i < itemCount; ++i) {
391 list.append(m_model->fileItem(i));
392 }
393
394 return list;
395 }
396
397 int DolphinView::itemsCount() const
398 {
399 return m_model->count();
400 }
401
402 KFileItemList DolphinView::selectedItems() const
403 {
404 const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
405
406 KFileItemList selectedItems;
407 const auto items = selectionManager->selectedItems();
408 selectedItems.reserve(items.count());
409 for (int index : items) {
410 selectedItems.append(m_model->fileItem(index));
411 }
412 return selectedItems;
413 }
414
415 int DolphinView::selectedItemsCount() const
416 {
417 const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
418 return selectionManager->selectedItems().count();
419 }
420
421 void DolphinView::markUrlsAsSelected(const QList<QUrl>& urls)
422 {
423 m_selectedUrls = urls;
424 }
425
426 void DolphinView::markUrlAsCurrent(const QUrl &url)
427 {
428 m_currentItemUrl = url;
429 m_scrollToCurrentItem = true;
430 }
431
432 void DolphinView::selectItems(const QRegularExpression &regexp, bool enabled)
433 {
434 const KItemListSelectionManager::SelectionMode mode = enabled
435 ? KItemListSelectionManager::Select
436 : KItemListSelectionManager::Deselect;
437 KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
438
439 for (int index = 0; index < m_model->count(); index++) {
440 const KFileItem item = m_model->fileItem(index);
441 if (regexp.match(item.text()).hasMatch()) {
442 // An alternative approach would be to store the matching items in a KItemSet and
443 // select them in one go after the loop, but we'd need a new function
444 // KItemListSelectionManager::setSelected(KItemSet, SelectionMode mode)
445 // for that.
446 selectionManager->setSelected(index, 1, mode);
447 }
448 }
449 }
450
451 void DolphinView::setZoomLevel(int level)
452 {
453 const int oldZoomLevel = zoomLevel();
454 m_view->setZoomLevel(level);
455 if (zoomLevel() != oldZoomLevel) {
456 hideToolTip();
457 Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel);
458 }
459 }
460
461 int DolphinView::zoomLevel() const
462 {
463 return m_view->zoomLevel();
464 }
465
466 void DolphinView::setSortRole(const QByteArray& role)
467 {
468 if (role != sortRole()) {
469 updateSortRole(role);
470 }
471 }
472
473 QByteArray DolphinView::sortRole() const
474 {
475 const KItemModelBase* model = m_container->controller()->model();
476 return model->sortRole();
477 }
478
479 void DolphinView::setSortOrder(Qt::SortOrder order)
480 {
481 if (sortOrder() != order) {
482 updateSortOrder(order);
483 }
484 }
485
486 Qt::SortOrder DolphinView::sortOrder() const
487 {
488 return m_model->sortOrder();
489 }
490
491 void DolphinView::setSortFoldersFirst(bool foldersFirst)
492 {
493 if (sortFoldersFirst() != foldersFirst) {
494 updateSortFoldersFirst(foldersFirst);
495 }
496 }
497
498 bool DolphinView::sortFoldersFirst() const
499 {
500 return m_model->sortDirectoriesFirst();
501 }
502
503 void DolphinView::setSortHiddenLast(bool hiddenLast)
504 {
505 if (sortHiddenLast() != hiddenLast) {
506 updateSortHiddenLast(hiddenLast);
507 }
508 }
509
510 bool DolphinView::sortHiddenLast() const
511 {
512 return m_model->sortHiddenLast();
513 }
514
515 void DolphinView::setVisibleRoles(const QList<QByteArray>& roles)
516 {
517 const QList<QByteArray> previousRoles = roles;
518
519 ViewProperties props(viewPropertiesUrl());
520 props.setVisibleRoles(roles);
521
522 m_visibleRoles = roles;
523 m_view->setVisibleRoles(roles);
524
525 Q_EMIT visibleRolesChanged(m_visibleRoles, previousRoles);
526 }
527
528 QList<QByteArray> DolphinView::visibleRoles() const
529 {
530 return m_visibleRoles;
531 }
532
533 void DolphinView::reload()
534 {
535 QByteArray viewState;
536 QDataStream saveStream(&viewState, QIODevice::WriteOnly);
537 saveState(saveStream);
538
539 setUrl(url());
540 loadDirectory(url(), true);
541
542 QDataStream restoreStream(viewState);
543 restoreState(restoreStream);
544 }
545
546 void DolphinView::readSettings()
547 {
548 const int oldZoomLevel = m_view->zoomLevel();
549
550 GeneralSettings::self()->load();
551 m_view->readSettings();
552 applyViewProperties();
553
554 const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1;
555 m_container->controller()->setAutoActivationDelay(delay);
556
557 const int newZoomLevel = m_view->zoomLevel();
558 if (newZoomLevel != oldZoomLevel) {
559 Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel);
560 }
561 }
562
563 void DolphinView::writeSettings()
564 {
565 GeneralSettings::self()->save();
566 m_view->writeSettings();
567 }
568
569 void DolphinView::setNameFilter(const QString& nameFilter)
570 {
571 m_model->setNameFilter(nameFilter);
572 }
573
574 QString DolphinView::nameFilter() const
575 {
576 return m_model->nameFilter();
577 }
578
579 void DolphinView::setMimeTypeFilters(const QStringList& filters)
580 {
581 return m_model->setMimeTypeFilters(filters);
582 }
583
584 QStringList DolphinView::mimeTypeFilters() const
585 {
586 return m_model->mimeTypeFilters();
587 }
588
589 void DolphinView::requestStatusBarText()
590 {
591 if (m_statJobForStatusBarText) {
592 // Kill the pending request.
593 m_statJobForStatusBarText->kill();
594 }
595
596 if (m_container->controller()->selectionManager()->hasSelection()) {
597 int folderCount = 0;
598 int fileCount = 0;
599 KIO::filesize_t totalFileSize = 0;
600
601 // Give a summary of the status of the selected files
602 const KFileItemList list = selectedItems();
603 for (const KFileItem& item : list) {
604 if (item.isDir()) {
605 ++folderCount;
606 } else {
607 ++fileCount;
608 totalFileSize += item.size();
609 }
610 }
611
612 if (folderCount + fileCount == 1) {
613 // If only one item is selected, show info about it
614 Q_EMIT statusBarTextChanged(list.first().getStatusBarInfo());
615 } else {
616 // At least 2 items are selected
617 emitStatusBarText(folderCount, fileCount, totalFileSize, HasSelection);
618 }
619 } else { // has no selection
620 if (!m_model->rootItem().url().isValid()) {
621 return;
622 }
623
624 m_statJobForStatusBarText = KIO::statDetails(m_model->rootItem().url(),
625 KIO::StatJob::SourceSide, KIO::StatRecursiveSize, KIO::HideProgressInfo);
626 connect(m_statJobForStatusBarText, &KJob::result,
627 this, &DolphinView::slotStatJobResult);
628 m_statJobForStatusBarText->start();
629 }
630 }
631
632 void DolphinView::emitStatusBarText(const int folderCount, const int fileCount,
633 KIO::filesize_t totalFileSize, const Selection selection)
634 {
635 QString foldersText;
636 QString filesText;
637 QString summary;
638
639 if (selection == HasSelection) {
640 // At least 2 items are selected because the case of 1 selected item is handled in
641 // DolphinView::requestStatusBarText().
642 foldersText = i18ncp("@info:status", "1 Folder selected", "%1 Folders selected", folderCount);
643 filesText = i18ncp("@info:status", "1 File selected", "%1 Files selected", fileCount);
644 } else {
645 foldersText = i18ncp("@info:status", "1 Folder", "%1 Folders", folderCount);
646 filesText = i18ncp("@info:status", "1 File", "%1 Files", fileCount);
647 }
648
649 if (fileCount > 0 && folderCount > 0) {
650 summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)",
651 foldersText, filesText,
652 KFormat().formatByteSize(totalFileSize));
653 } else if (fileCount > 0) {
654 summary = i18nc("@info:status files (size)", "%1 (%2)",
655 filesText,
656 KFormat().formatByteSize(totalFileSize));
657 } else if (folderCount > 0) {
658 summary = foldersText;
659 } else {
660 summary = i18nc("@info:status", "0 Folders, 0 Files");
661 }
662 Q_EMIT statusBarTextChanged(summary);
663 }
664
665 QList<QAction*> DolphinView::versionControlActions(const KFileItemList& items) const
666 {
667 QList<QAction*> actions;
668
669 if (items.isEmpty()) {
670 const KFileItem item = m_model->rootItem();
671 if (!item.isNull()) {
672 actions = m_versionControlObserver->actions(KFileItemList() << item);
673 }
674 } else {
675 actions = m_versionControlObserver->actions(items);
676 }
677
678 return actions;
679 }
680
681 void DolphinView::setUrl(const QUrl& url)
682 {
683 if (url == m_url) {
684 return;
685 }
686
687 clearSelection();
688
689 m_url = url;
690
691 hideToolTip();
692
693 disconnect(m_view, &DolphinItemListView::roleEditingFinished,
694 this, &DolphinView::slotRoleEditingFinished);
695
696 // It is important to clear the items from the model before
697 // applying the view properties, otherwise expensive operations
698 // might be done on the existing items although they get cleared
699 // anyhow afterwards by loadDirectory().
700 m_model->clear();
701 applyViewProperties();
702 loadDirectory(url);
703
704 Q_EMIT urlChanged(url);
705 }
706
707 void DolphinView::selectAll()
708 {
709 KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
710 selectionManager->setSelected(0, m_model->count());
711 }
712
713 void DolphinView::invertSelection()
714 {
715 KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
716 selectionManager->setSelected(0, m_model->count(), KItemListSelectionManager::Toggle);
717 }
718
719 void DolphinView::clearSelection()
720 {
721 m_selectedUrls.clear();
722 m_container->controller()->selectionManager()->clearSelection();
723 }
724
725 void DolphinView::renameSelectedItems()
726 {
727 const KFileItemList items = selectedItems();
728 if (items.isEmpty()) {
729 return;
730 }
731
732 if (items.count() == 1 && GeneralSettings::renameInline()) {
733 const int index = m_model->index(items.first());
734
735 QMetaObject::Connection * const connection = new QMetaObject::Connection;
736 *connection = connect(m_view, &KItemListView::scrollingStopped, this, [=](){
737 QObject::disconnect(*connection);
738 delete connection;
739
740 m_view->editRole(index, "text");
741
742 hideToolTip();
743
744 connect(m_view, &DolphinItemListView::roleEditingFinished,
745 this, &DolphinView::slotRoleEditingFinished);
746 });
747 m_view->scrollToItem(index);
748
749 } else {
750 KIO::RenameFileDialog* dialog = new KIO::RenameFileDialog(items, this);
751 connect(dialog, &KIO::RenameFileDialog::renamingFinished,
752 this, &DolphinView::slotRenameDialogRenamingFinished);
753
754 dialog->open();
755 }
756
757 // Assure that the current index remains visible when KFileItemModel
758 // will notify the view about changed items (which might result in
759 // a changed sorting).
760 m_assureVisibleCurrentIndex = true;
761 }
762
763 void DolphinView::trashSelectedItems()
764 {
765 const QList<QUrl> list = simplifiedSelectedUrls();
766
767 #if KIO_VERSION >= QT_VERSION_CHECK(5, 100, 0)
768 using Iface = KIO::AskUserActionInterface;
769 auto *trashJob = new KIO::DeleteOrTrashJob(list, Iface::Trash, Iface::DefaultConfirmation, this);
770 connect(trashJob, &KJob::result, this, &DolphinView::slotTrashFileFinished);
771 trashJob->start();
772 #else
773 KIO::JobUiDelegate uiDelegate;
774 uiDelegate.setWindow(window());
775 if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) {
776 KIO::Job* job = KIO::trash(list);
777 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, list, QUrl(QStringLiteral("trash:/")), job);
778 KJobWidgets::setWindow(job, this);
779 connect(job, &KIO::Job::result,
780 this, &DolphinView::slotTrashFileFinished);
781 }
782 #endif
783 }
784
785 void DolphinView::deleteSelectedItems()
786 {
787 const QList<QUrl> list = simplifiedSelectedUrls();
788
789 #if KIO_VERSION >= QT_VERSION_CHECK(5, 100, 0)
790 using Iface = KIO::AskUserActionInterface;
791 auto *trashJob = new KIO::DeleteOrTrashJob(list, Iface::Delete, Iface::DefaultConfirmation, this);
792 connect(trashJob, &KJob::result, this, &DolphinView::slotTrashFileFinished);
793 trashJob->start();
794 #else
795 KIO::JobUiDelegate uiDelegate;
796 uiDelegate.setWindow(window());
797 if (uiDelegate.askDeleteConfirmation(list, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) {
798 KIO::Job* job = KIO::del(list);
799 KJobWidgets::setWindow(job, this);
800 connect(job, &KIO::Job::result,
801 this, &DolphinView::slotDeleteFileFinished);
802 }
803 #endif
804 }
805
806 void DolphinView::cutSelectedItemsToClipboard()
807 {
808 QMimeData* mimeData = selectionMimeData();
809 KIO::setClipboardDataCut(mimeData, true);
810 KUrlMimeData::exportUrlsToPortal(mimeData);
811 QApplication::clipboard()->setMimeData(mimeData);
812 }
813
814 void DolphinView::copySelectedItemsToClipboard()
815 {
816 QMimeData *mimeData = selectionMimeData();
817 KUrlMimeData::exportUrlsToPortal(mimeData);
818 QApplication::clipboard()->setMimeData(mimeData);
819 }
820
821 void DolphinView::copySelectedItems(const KFileItemList &selection, const QUrl &destinationUrl)
822 {
823 KIO::CopyJob* job = KIO::copy(selection.urlList(), destinationUrl, KIO::DefaultFlags);
824 KJobWidgets::setWindow(job, this);
825
826 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult);
827 connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone);
828 KIO::FileUndoManager::self()->recordCopyJob(job);
829 }
830
831 void DolphinView::moveSelectedItems(const KFileItemList &selection, const QUrl &destinationUrl)
832 {
833 KIO::CopyJob* job = KIO::move(selection.urlList(), destinationUrl, KIO::DefaultFlags);
834 KJobWidgets::setWindow(job, this);
835
836 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult);
837 connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotCopyingDone);
838 KIO::FileUndoManager::self()->recordCopyJob(job);
839
840 }
841
842 void DolphinView::paste()
843 {
844 pasteToUrl(url());
845 }
846
847 void DolphinView::pasteIntoFolder()
848 {
849 const KFileItemList items = selectedItems();
850 if ((items.count() == 1) && items.first().isDir()) {
851 pasteToUrl(items.first().url());
852 }
853 }
854
855 void DolphinView::duplicateSelectedItems()
856 {
857 const KFileItemList itemList = selectedItems();
858 if (itemList.isEmpty()) {
859 return;
860 }
861
862 const QMimeDatabase db;
863
864 // Duplicate all selected items and append "copy" to the end of the file name
865 // but before the filename extension, if present
866 QList<QUrl> newSelection;
867 for (const auto &item : itemList) {
868 const QUrl originalURL = item.url();
869 const QString originalDirectoryPath = originalURL.adjusted(QUrl::RemoveFilename).path();
870 const QString originalFileName = item.name();
871
872 QString extension = db.suffixForFileName(originalFileName);
873
874 QUrl duplicateURL = originalURL;
875
876 // No extension; new filename is "<oldfilename> copy"
877 if (extension.isEmpty()) {
878 duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFileName));
879 // There's an extension; new filename is "<oldfilename> copy.<extension>"
880 } else {
881 // Need to add a dot since QMimeDatabase::suffixForFileName() doesn't include it
882 extension = QLatin1String(".") + extension;
883 const QString originalFilenameWithoutExtension = originalFileName.chopped(extension.size());
884 // Preserve file's original filename extension in case the casing differs
885 // from what QMimeDatabase::suffixForFileName() returned
886 const QString originalExtension = originalFileName.right(extension.size());
887 duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFilenameWithoutExtension) + originalExtension);
888 }
889
890 KIO::CopyJob* job = KIO::copyAs(originalURL, duplicateURL);
891 KJobWidgets::setWindow(job, this);
892
893 if (job) {
894 newSelection << duplicateURL;
895 KIO::FileUndoManager::self()->recordCopyJob(job);
896 }
897 }
898
899 forceUrlsSelection(newSelection.first(), newSelection);
900 }
901
902 void DolphinView::stopLoading()
903 {
904 m_model->cancelDirectoryLoading();
905 }
906
907 void DolphinView::updatePalette()
908 {
909 QColor color = KColorScheme(isActiveWindow() ? QPalette::Active : QPalette::Inactive, KColorScheme::View).background().color();
910 if (!m_active) {
911 color.setAlpha(150);
912 }
913
914 QWidget* viewport = m_container->viewport();
915 if (viewport) {
916 QPalette palette;
917 palette.setColor(viewport->backgroundRole(), color);
918 viewport->setPalette(palette);
919 }
920
921 update();
922 }
923
924 void DolphinView::abortTwoClicksRenaming()
925 {
926 m_twoClicksRenamingItemUrl.clear();
927 m_twoClicksRenamingTimer->stop();
928 }
929
930 bool DolphinView::eventFilter(QObject* watched, QEvent* event)
931 {
932 switch (event->type()) {
933 case QEvent::PaletteChange:
934 updatePalette();
935 QPixmapCache::clear();
936 break;
937
938 case QEvent::WindowActivate:
939 case QEvent::WindowDeactivate:
940 updatePalette();
941 break;
942
943 case QEvent::KeyPress:
944 hideToolTip(ToolTipManager::HideBehavior::Instantly);
945 if (GeneralSettings::useTabForSwitchingSplitView()) {
946 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
947 if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) {
948 Q_EMIT toggleActiveViewRequested();
949 return true;
950 }
951 }
952 break;
953 case QEvent::FocusIn:
954 if (watched == m_container) {
955 setActive(true);
956 }
957 break;
958
959 case QEvent::GraphicsSceneDragEnter:
960 if (watched == m_view) {
961 m_dragging = true;
962 abortTwoClicksRenaming();
963 }
964 break;
965
966 case QEvent::GraphicsSceneDragLeave:
967 if (watched == m_view) {
968 m_dragging = false;
969 }
970 break;
971
972 case QEvent::GraphicsSceneDrop:
973 if (watched == m_view) {
974 m_dragging = false;
975 }
976 break;
977
978 case QEvent::ToolTip:
979 tryShowNameToolTip(static_cast<QHelpEvent*>(event));
980
981 default:
982 break;
983 }
984
985 return QWidget::eventFilter(watched, event);
986 }
987
988 void DolphinView::wheelEvent(QWheelEvent* event)
989 {
990 if (event->modifiers().testFlag(Qt::ControlModifier)) {
991 const QPoint numDegrees = event->angleDelta() / 8;
992 const QPoint numSteps = numDegrees / 15;
993
994 setZoomLevel(zoomLevel() + numSteps.y());
995 event->accept();
996 } else {
997 event->ignore();
998 }
999 }
1000
1001 void DolphinView::hideEvent(QHideEvent* event)
1002 {
1003 hideToolTip();
1004 QWidget::hideEvent(event);
1005 }
1006
1007 bool DolphinView::event(QEvent* event)
1008 {
1009 if (event->type() == QEvent::WindowDeactivate) {
1010 /* See Bug 297355
1011 * Dolphin leaves file preview tooltips open even when is not visible.
1012 *
1013 * Hide tool-tip when Dolphin loses focus.
1014 */
1015 hideToolTip();
1016 abortTwoClicksRenaming();
1017 }
1018
1019 return QWidget::event(event);
1020 }
1021
1022 void DolphinView::activate()
1023 {
1024 setActive(true);
1025 }
1026
1027 void DolphinView::slotItemActivated(int index)
1028 {
1029 abortTwoClicksRenaming();
1030
1031 const KFileItem item = m_model->fileItem(index);
1032 if (!item.isNull()) {
1033 Q_EMIT itemActivated(item);
1034 }
1035 }
1036
1037 void DolphinView::slotItemsActivated(const KItemSet &indexes)
1038 {
1039 Q_ASSERT(indexes.count() >= 2);
1040
1041 abortTwoClicksRenaming();
1042
1043 const auto modifiers = QGuiApplication::keyboardModifiers();
1044
1045 if (indexes.count() > 5) {
1046 QString question = i18np("Are you sure you want to open 1 item?", "Are you sure you want to open %1 items?", indexes.count());
1047 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1048 const int answer = KMessageBox::warningTwoActions(this, question, {},
1049 #else
1050 const int answer = KMessageBox::warningYesNo(this, question, {},
1051 #endif
1052 KGuiItem(i18ncp("@action:button", "Open %1 Item", "Open %1 Items", indexes.count()),
1053 QStringLiteral("document-open")),
1054 KStandardGuiItem::cancel());
1055 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1056 if (answer != KMessageBox::PrimaryAction) {
1057 #else
1058 if (answer != KMessageBox::Yes) {
1059 #endif
1060 return;
1061 }
1062 }
1063
1064 KFileItemList items;
1065 items.reserve(indexes.count());
1066
1067 for (int index : indexes) {
1068 KFileItem item = m_model->fileItem(index);
1069 const QUrl& url = openItemAsFolderUrl(item);
1070
1071 if (!url.isEmpty()) {
1072 // Open folders in new tabs or in new windows depending on the modifier
1073 // The ctrl+shift behavior is ignored because we are handling multiple items
1074 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
1075 if (modifiers & Qt::ShiftModifier && !(modifiers & Qt::ControlModifier)) {
1076 Q_EMIT windowRequested(url);
1077 } else {
1078 Q_EMIT tabRequested(url);
1079 }
1080 } else {
1081 items.append(item);
1082 }
1083 }
1084
1085 if (items.count() == 1) {
1086 Q_EMIT itemActivated(items.first());
1087 } else if (items.count() > 1) {
1088 Q_EMIT itemsActivated(items);
1089 }
1090 }
1091
1092 void DolphinView::slotItemMiddleClicked(int index)
1093 {
1094 const KFileItem& item = m_model->fileItem(index);
1095 const QUrl& url = openItemAsFolderUrl(item);
1096 const auto modifiers = QGuiApplication::keyboardModifiers();
1097 if (!url.isEmpty()) {
1098 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
1099 if (modifiers & Qt::ShiftModifier) {
1100 Q_EMIT activeTabRequested(url);
1101 } else {
1102 Q_EMIT tabRequested(url);
1103 }
1104 } else if (isTabsForFilesEnabled()) {
1105 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
1106 if (modifiers & Qt::ShiftModifier) {
1107 Q_EMIT activeTabRequested(item.url());
1108 } else {
1109 Q_EMIT tabRequested(item.url());
1110 }
1111 }
1112 }
1113
1114 void DolphinView::slotItemContextMenuRequested(int index, const QPointF& pos)
1115 {
1116 // Force emit of a selection changed signal before we request the
1117 // context menu, to update the edit-actions first. (See Bug 294013)
1118 if (m_selectionChangedTimer->isActive()) {
1119 emitSelectionChangedSignal();
1120 }
1121
1122 const KFileItem item = m_model->fileItem(index);
1123 Q_EMIT requestContextMenu(pos.toPoint(), item, selectedItems(), url());
1124 }
1125
1126 void DolphinView::slotViewContextMenuRequested(const QPointF& pos)
1127 {
1128 Q_EMIT requestContextMenu(pos.toPoint(), KFileItem(), selectedItems(), url());
1129 }
1130
1131 void DolphinView::slotHeaderContextMenuRequested(const QPointF& pos)
1132 {
1133 ViewProperties props(viewPropertiesUrl());
1134
1135 QPointer<QMenu> menu = new QMenu(QApplication::activeWindow());
1136
1137 KItemListView* view = m_container->controller()->view();
1138 const QList<QByteArray> visibleRolesSet = view->visibleRoles();
1139
1140 bool indexingEnabled = false;
1141 #if HAVE_BALOO
1142 Baloo::IndexerConfig config;
1143 indexingEnabled = config.fileIndexingEnabled();
1144 #endif
1145
1146 QString groupName;
1147 QMenu* groupMenu = nullptr;
1148
1149 // Add all roles to the menu that can be shown or hidden by the user
1150 const QList<KFileItemModel::RoleInfo> rolesInfo = KFileItemModel::rolesInformation();
1151 for (const KFileItemModel::RoleInfo& info : rolesInfo) {
1152 if (info.role == "text") {
1153 // It should not be possible to hide the "text" role
1154 continue;
1155 }
1156
1157 const QString text = m_model->roleDescription(info.role);
1158 QAction* action = nullptr;
1159 if (info.group.isEmpty()) {
1160 action = menu->addAction(text);
1161 } else {
1162 if (!groupMenu || info.group != groupName) {
1163 groupName = info.group;
1164 groupMenu = menu->addMenu(groupName);
1165 }
1166
1167 action = groupMenu->addAction(text);
1168 }
1169
1170 action->setCheckable(true);
1171 action->setChecked(visibleRolesSet.contains(info.role));
1172 action->setData(info.role);
1173
1174 const bool enable = (!info.requiresBaloo && !info.requiresIndexer) ||
1175 (info.requiresBaloo) ||
1176 (info.requiresIndexer && indexingEnabled);
1177 action->setEnabled(enable);
1178 }
1179
1180 menu->addSeparator();
1181
1182 QActionGroup* widthsGroup = new QActionGroup(menu);
1183 const bool autoColumnWidths = props.headerColumnWidths().isEmpty();
1184
1185 QAction* toggleSidePaddingAction = menu->addAction(i18nc("@action:inmenu", "Side Padding"));
1186 toggleSidePaddingAction->setCheckable(true);
1187 toggleSidePaddingAction->setChecked(view->header()->sidePadding() > 0);
1188
1189 QAction* autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths"));
1190 autoAdjustWidthsAction->setCheckable(true);
1191 autoAdjustWidthsAction->setChecked(autoColumnWidths);
1192 autoAdjustWidthsAction->setActionGroup(widthsGroup);
1193
1194 QAction* customWidthsAction = menu->addAction(i18nc("@action:inmenu", "Custom Column Widths"));
1195 customWidthsAction->setCheckable(true);
1196 customWidthsAction->setChecked(!autoColumnWidths);
1197 customWidthsAction->setActionGroup(widthsGroup);
1198
1199 QAction* action = menu->exec(pos.toPoint());
1200 if (menu && action) {
1201 KItemListHeader* header = view->header();
1202
1203 if (action == autoAdjustWidthsAction) {
1204 // Clear the column-widths from the viewproperties and turn on
1205 // the automatic resizing of the columns
1206 props.setHeaderColumnWidths(QList<int>());
1207 header->setAutomaticColumnResizing(true);
1208 } else if (action == customWidthsAction) {
1209 // Apply the current column-widths as custom column-widths and turn
1210 // off the automatic resizing of the columns
1211 QList<int> columnWidths;
1212 const auto visibleRoles = view->visibleRoles();
1213 columnWidths.reserve(visibleRoles.count());
1214 for (const QByteArray& role : visibleRoles) {
1215 columnWidths.append(header->columnWidth(role));
1216 }
1217 props.setHeaderColumnWidths(columnWidths);
1218 header->setAutomaticColumnResizing(false);
1219 } else if (action == toggleSidePaddingAction) {
1220 header->setSidePadding(toggleSidePaddingAction->isChecked() ? 20 : 0);
1221 } else {
1222 // Show or hide the selected role
1223 const QByteArray selectedRole = action->data().toByteArray();
1224
1225 QList<QByteArray> visibleRoles = view->visibleRoles();
1226 if (action->isChecked()) {
1227 visibleRoles.append(selectedRole);
1228 } else {
1229 visibleRoles.removeOne(selectedRole);
1230 }
1231
1232 view->setVisibleRoles(visibleRoles);
1233 props.setVisibleRoles(visibleRoles);
1234
1235 QList<int> columnWidths;
1236 if (!header->automaticColumnResizing()) {
1237 const auto visibleRoles = view->visibleRoles();
1238 columnWidths.reserve(visibleRoles.count());
1239 for (const QByteArray& role : visibleRoles) {
1240 columnWidths.append(header->columnWidth(role));
1241 }
1242 }
1243 props.setHeaderColumnWidths(columnWidths);
1244 }
1245 }
1246
1247 delete menu;
1248 }
1249
1250 void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray& role, qreal current)
1251 {
1252 const QList<QByteArray> visibleRoles = m_view->visibleRoles();
1253
1254 ViewProperties props(viewPropertiesUrl());
1255 QList<int> columnWidths = props.headerColumnWidths();
1256 if (columnWidths.count() != visibleRoles.count()) {
1257 columnWidths.clear();
1258 columnWidths.reserve(visibleRoles.count());
1259 const KItemListHeader* header = m_view->header();
1260 for (const QByteArray& role : visibleRoles) {
1261 const int width = header->columnWidth(role);
1262 columnWidths.append(width);
1263 }
1264 }
1265
1266 const int roleIndex = visibleRoles.indexOf(role);
1267 Q_ASSERT(roleIndex >= 0 && roleIndex < columnWidths.count());
1268 columnWidths[roleIndex] = current;
1269
1270 props.setHeaderColumnWidths(columnWidths);
1271 }
1272
1273 void DolphinView::slotSidePaddingWidthChanged(qreal width)
1274 {
1275 ViewProperties props(viewPropertiesUrl());
1276 DetailsModeSettings::setSidePadding(int(width));
1277 m_view->writeSettings();
1278 }
1279
1280 void DolphinView::slotItemHovered(int index)
1281 {
1282 const KFileItem item = m_model->fileItem(index);
1283
1284 if (GeneralSettings::showToolTips() && !m_dragging) {
1285 QRectF itemRect = m_container->controller()->view()->itemContextRect(index);
1286 const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint());
1287 itemRect.moveTo(pos);
1288
1289 #if HAVE_BALOO
1290 auto nativeParent = nativeParentWidget();
1291 if (nativeParent) {
1292 m_toolTipManager->showToolTip(item, itemRect, nativeParent->windowHandle());
1293 }
1294 #endif
1295 }
1296
1297 Q_EMIT requestItemInfo(item);
1298 }
1299
1300 void DolphinView::slotItemUnhovered(int index)
1301 {
1302 Q_UNUSED(index)
1303 hideToolTip();
1304 Q_EMIT requestItemInfo(KFileItem());
1305 }
1306
1307 void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent* event)
1308 {
1309 QUrl destUrl;
1310 KFileItem destItem = m_model->fileItem(index);
1311 if (destItem.isNull() || (!destItem.isDir() && !destItem.isDesktopFile())) {
1312 // Use the URL of the view as drop target if the item is no directory
1313 // or desktop-file
1314 destItem = m_model->rootItem();
1315 destUrl = url();
1316 } else {
1317 // The item represents a directory or desktop-file
1318 destUrl = destItem.mostLocalUrl();
1319 }
1320
1321 QDropEvent dropEvent(event->pos().toPoint(),
1322 event->possibleActions(),
1323 event->mimeData(),
1324 event->buttons(),
1325 event->modifiers());
1326 dropUrls(destUrl, &dropEvent, this);
1327
1328 setActive(true);
1329 }
1330
1331 void DolphinView::dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget)
1332 {
1333 KIO::DropJob* job = DragAndDropHelper::dropUrls(destUrl, dropEvent, dropWidget);
1334
1335 if (job) {
1336 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult);
1337
1338 if (destUrl == url()) {
1339 // Mark the dropped urls as selected.
1340 m_clearSelectionBeforeSelectingNewItems = true;
1341 m_markFirstNewlySelectedItemAsCurrent = true;
1342 connect(job, &KIO::DropJob::itemCreated, this, &DolphinView::slotItemCreated);
1343 }
1344 }
1345 }
1346
1347 void DolphinView::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
1348 {
1349 if (previous != nullptr) {
1350 Q_ASSERT(qobject_cast<KFileItemModel*>(previous));
1351 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(previous);
1352 disconnect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted);
1353 m_versionControlObserver->setModel(nullptr);
1354 }
1355
1356 if (current) {
1357 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
1358 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(current);
1359 connect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted);
1360 m_versionControlObserver->setModel(fileItemModel);
1361 }
1362 }
1363
1364 void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons)
1365 {
1366 Q_UNUSED(itemIndex)
1367
1368 hideToolTip();
1369
1370 if (buttons & Qt::BackButton) {
1371 Q_EMIT goBackRequested();
1372 } else if (buttons & Qt::ForwardButton) {
1373 Q_EMIT goForwardRequested();
1374 }
1375 }
1376
1377 void DolphinView::slotSelectedItemTextPressed(int index)
1378 {
1379 if (GeneralSettings::renameInline() && !m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) {
1380 const KFileItem item = m_model->fileItem(index);
1381 const KFileItemListProperties capabilities(KFileItemList() << item);
1382 if (capabilities.supportsMoving()) {
1383 m_twoClicksRenamingItemUrl = item.url();
1384 m_twoClicksRenamingTimer->start(QApplication::doubleClickInterval());
1385 }
1386 }
1387 }
1388
1389 void DolphinView::slotCopyingDone(KIO::Job *, const QUrl &, const QUrl &to)
1390 {
1391 slotItemCreated(to);
1392 }
1393
1394 void DolphinView::slotItemCreated(const QUrl& url)
1395 {
1396 if (m_markFirstNewlySelectedItemAsCurrent) {
1397 markUrlAsCurrent(url);
1398 m_markFirstNewlySelectedItemAsCurrent = false;
1399 }
1400 m_selectedUrls << url;
1401 }
1402
1403 void DolphinView::slotJobResult(KJob *job)
1404 {
1405 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
1406 Q_EMIT errorMessage(job->errorString());
1407 }
1408 if (!m_selectedUrls.isEmpty()) {
1409 m_selectedUrls = KDirModel::simplifiedUrlList(m_selectedUrls);
1410 }
1411 }
1412
1413 void DolphinView::slotSelectionChanged(const KItemSet& current, const KItemSet& previous)
1414 {
1415 const int currentCount = current.count();
1416 const int previousCount = previous.count();
1417 const bool selectionStateChanged = (currentCount == 0 && previousCount > 0) ||
1418 (currentCount > 0 && previousCount == 0);
1419
1420 // If nothing has been selected before and something got selected (or if something
1421 // was selected before and now nothing is selected) the selectionChangedSignal must
1422 // be emitted asynchronously as fast as possible to update the edit-actions.
1423 m_selectionChangedTimer->setInterval(selectionStateChanged ? 0 : 300);
1424 m_selectionChangedTimer->start();
1425 }
1426
1427 void DolphinView::emitSelectionChangedSignal()
1428 {
1429 m_selectionChangedTimer->stop();
1430 Q_EMIT selectionChanged(selectedItems());
1431 }
1432
1433 void DolphinView::slotStatJobResult(KJob *job)
1434 {
1435 int folderCount = 0;
1436 int fileCount = 0;
1437 KIO::filesize_t totalFileSize = 0;
1438 bool countFileSize = true;
1439
1440 const auto entry = static_cast<KIO::StatJob *>(job)->statResult();
1441 if (entry.contains(KIO::UDSEntry::UDS_RECURSIVE_SIZE)) {
1442 // We have a precomputed value.
1443 totalFileSize = static_cast<KIO::filesize_t>(
1444 entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE));
1445 countFileSize = false;
1446 }
1447
1448 const int itemCount = m_model->count();
1449 for (int i = 0; i < itemCount; ++i) {
1450 const KFileItem item = m_model->fileItem(i);
1451 if (item.isDir()) {
1452 ++folderCount;
1453 } else {
1454 ++fileCount;
1455 if (countFileSize) {
1456 totalFileSize += item.size();
1457 }
1458 }
1459 }
1460 emitStatusBarText(folderCount, fileCount, totalFileSize, NoSelection);
1461 }
1462
1463 void DolphinView::updateSortRole(const QByteArray& role)
1464 {
1465 ViewProperties props(viewPropertiesUrl());
1466 props.setSortRole(role);
1467
1468 KItemModelBase* model = m_container->controller()->model();
1469 model->setSortRole(role);
1470
1471 Q_EMIT sortRoleChanged(role);
1472 }
1473
1474 void DolphinView::updateSortOrder(Qt::SortOrder order)
1475 {
1476 ViewProperties props(viewPropertiesUrl());
1477 props.setSortOrder(order);
1478
1479 m_model->setSortOrder(order);
1480
1481 Q_EMIT sortOrderChanged(order);
1482 }
1483
1484 void DolphinView::updateSortFoldersFirst(bool foldersFirst)
1485 {
1486 ViewProperties props(viewPropertiesUrl());
1487 props.setSortFoldersFirst(foldersFirst);
1488
1489 m_model->setSortDirectoriesFirst(foldersFirst);
1490
1491 Q_EMIT sortFoldersFirstChanged(foldersFirst);
1492 }
1493
1494 void DolphinView::updateSortHiddenLast(bool hiddenLast)
1495 {
1496 ViewProperties props(viewPropertiesUrl());
1497 props.setSortHiddenLast(hiddenLast);
1498
1499 m_model->setSortHiddenLast(hiddenLast);
1500
1501 Q_EMIT sortHiddenLastChanged(hiddenLast);
1502 }
1503
1504
1505 QPair<bool, QString> DolphinView::pasteInfo() const
1506 {
1507 const QMimeData *mimeData = QApplication::clipboard()->mimeData();
1508 QPair<bool, QString> info;
1509 info.second = KIO::pasteActionText(mimeData, &info.first, rootItem());
1510 return info;
1511 }
1512
1513 void DolphinView::setTabsForFilesEnabled(bool tabsForFiles)
1514 {
1515 m_tabsForFiles = tabsForFiles;
1516 }
1517
1518 bool DolphinView::isTabsForFilesEnabled() const
1519 {
1520 return m_tabsForFiles;
1521 }
1522
1523 bool DolphinView::itemsExpandable() const
1524 {
1525 return m_mode == DetailsView;
1526 }
1527
1528 bool DolphinView::isExpanded(const KFileItem& item) const
1529 {
1530 Q_ASSERT(item.isDir());
1531 Q_ASSERT(items().contains(item));
1532 if (!itemsExpandable()) {
1533 return false;
1534 }
1535 return m_model->isExpanded(m_model->index(item));
1536 }
1537
1538 void DolphinView::restoreState(QDataStream& stream)
1539 {
1540 // Read the version number of the view state and check if the version is supported.
1541 quint32 version = 0;
1542 stream >> version;
1543 if (version != 1) {
1544 // The version of the view state isn't supported, we can't restore it.
1545 return;
1546 }
1547
1548 // Restore the current item that had the keyboard focus
1549 stream >> m_currentItemUrl;
1550
1551 // Restore the previously selected items
1552 stream >> m_selectedUrls;
1553
1554 // Restore the view position
1555 stream >> m_restoredContentsPosition;
1556
1557 // Restore expanded folders (only relevant for the details view - will be ignored by the view in other view modes)
1558 QSet<QUrl> urls;
1559 stream >> urls;
1560 m_model->restoreExpandedDirectories(urls);
1561 }
1562
1563 void DolphinView::saveState(QDataStream& stream)
1564 {
1565 stream << quint32(1); // View state version
1566
1567 // Save the current item that has the keyboard focus
1568 const int currentIndex = m_container->controller()->selectionManager()->currentItem();
1569 if (currentIndex != -1) {
1570 KFileItem item = m_model->fileItem(currentIndex);
1571 Q_ASSERT(!item.isNull()); // If the current index is valid a item must exist
1572 QUrl currentItemUrl = item.url();
1573 stream << currentItemUrl;
1574 } else {
1575 stream << QUrl();
1576 }
1577
1578 // Save the selected urls
1579 stream << selectedItems().urlList();
1580
1581 // Save view position
1582 const qreal x = m_container->horizontalScrollBar()->value();
1583 const qreal y = m_container->verticalScrollBar()->value();
1584 stream << QPoint(x, y);
1585
1586 // Save expanded folders (only relevant for the details view - the set will be empty in other view modes)
1587 stream << m_model->expandedDirectories();
1588 }
1589
1590 KFileItem DolphinView::rootItem() const
1591 {
1592 return m_model->rootItem();
1593 }
1594
1595 void DolphinView::setViewPropertiesContext(const QString& context)
1596 {
1597 m_viewPropertiesContext = context;
1598 }
1599
1600 QString DolphinView::viewPropertiesContext() const
1601 {
1602 return m_viewPropertiesContext;
1603 }
1604
1605 QUrl DolphinView::openItemAsFolderUrl(const KFileItem& item, const bool browseThroughArchives)
1606 {
1607 if (item.isNull()) {
1608 return QUrl();
1609 }
1610
1611 QUrl url = item.targetUrl();
1612
1613 if (item.isDir()) {
1614 return url;
1615 }
1616
1617 if (item.isMimeTypeKnown()) {
1618 const QString& mimetype = item.mimetype();
1619
1620 if (browseThroughArchives && item.isFile() && url.isLocalFile()) {
1621 // Generic mechanism for redirecting to tar:/<path>/ when clicking on a tar file,
1622 // zip:/<path>/ when clicking on a zip file, etc.
1623 // The .protocol file specifies the mimetype that the kioslave handles.
1624 // Note that we don't use mimetype inheritance since we don't want to
1625 // open OpenDocument files as zip folders...
1626 const QString& protocol = KProtocolManager::protocolForArchiveMimetype(mimetype);
1627 if (!protocol.isEmpty()) {
1628 url.setScheme(protocol);
1629 return url;
1630 }
1631 }
1632
1633 if (mimetype == QLatin1String("application/x-desktop")) {
1634 // Redirect to the URL in Type=Link desktop files, unless it is a http(s) URL.
1635 KDesktopFile desktopFile(url.toLocalFile());
1636 if (desktopFile.hasLinkType()) {
1637 const QString linkUrl = desktopFile.readUrl();
1638 if (!linkUrl.startsWith(QLatin1String("http"))) {
1639 return QUrl::fromUserInput(linkUrl);
1640 }
1641 }
1642 }
1643 }
1644
1645 return QUrl();
1646 }
1647
1648 void DolphinView::resetZoomLevel()
1649 {
1650 ViewModeSettings settings{m_mode};
1651 settings.useDefaults(true);
1652 const int defaultIconSize = settings.iconSize();
1653 settings.useDefaults(false);
1654
1655 setZoomLevel(ZoomLevelInfo::zoomLevelForIconSize(QSize(defaultIconSize, defaultIconSize)));
1656 }
1657
1658 void DolphinView::observeCreatedItem(const QUrl& url)
1659 {
1660 if (m_active) {
1661 forceUrlsSelection(url, {url});
1662 }
1663 }
1664
1665 void DolphinView::slotDirectoryRedirection(const QUrl& oldUrl, const QUrl& newUrl)
1666 {
1667 if (oldUrl.matches(url(), QUrl::StripTrailingSlash)) {
1668 Q_EMIT redirection(oldUrl, newUrl);
1669 m_url = newUrl; // #186947
1670 }
1671 }
1672
1673 void DolphinView::updateViewState()
1674 {
1675 if (m_currentItemUrl != QUrl()) {
1676 KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
1677
1678 // if there is a selection already, leave it that way
1679 if (!selectionManager->hasSelection()) {
1680 const int currentIndex = m_model->index(m_currentItemUrl);
1681 if (currentIndex != -1) {
1682 selectionManager->setCurrentItem(currentIndex);
1683
1684 // scroll to current item and reset the state
1685 if (m_scrollToCurrentItem) {
1686 m_view->scrollToItem(currentIndex);
1687 m_scrollToCurrentItem = false;
1688 }
1689 m_currentItemUrl = QUrl();
1690 } else {
1691 selectionManager->setCurrentItem(0);
1692 }
1693 } else {
1694 m_currentItemUrl = QUrl();
1695 }
1696 }
1697
1698 if (!m_restoredContentsPosition.isNull()) {
1699 const int x = m_restoredContentsPosition.x();
1700 const int y = m_restoredContentsPosition.y();
1701 m_restoredContentsPosition = QPoint();
1702
1703 m_container->horizontalScrollBar()->setValue(x);
1704 m_container->verticalScrollBar()->setValue(y);
1705 }
1706
1707 if (!m_selectedUrls.isEmpty()) {
1708 KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
1709
1710 // if there is a selection already, leave it that way
1711 if (!selectionManager->hasSelection()) {
1712 if (m_clearSelectionBeforeSelectingNewItems) {
1713 selectionManager->clearSelection();
1714 m_clearSelectionBeforeSelectingNewItems = false;
1715 }
1716
1717 KItemSet selectedItems = selectionManager->selectedItems();
1718
1719 QList<QUrl>::iterator it = m_selectedUrls.begin();
1720 while (it != m_selectedUrls.end()) {
1721 const int index = m_model->index(*it);
1722 if (index >= 0) {
1723 selectedItems.insert(index);
1724 it = m_selectedUrls.erase(it);
1725 } else {
1726 ++it;
1727 }
1728 }
1729
1730 if (!selectedItems.isEmpty()) {
1731 selectionManager->beginAnchoredSelection(selectionManager->currentItem());
1732 selectionManager->setSelectedItems(selectedItems);
1733 }
1734 }
1735 }
1736 }
1737
1738 void DolphinView::hideToolTip(const ToolTipManager::HideBehavior behavior)
1739 {
1740 if (GeneralSettings::showToolTips()) {
1741 #if HAVE_BALOO
1742 m_toolTipManager->hideToolTip(behavior);
1743 #else
1744 Q_UNUSED(behavior)
1745 #endif
1746 } else if (m_mode == DolphinView::IconsView) {
1747 QToolTip::hideText();
1748 }
1749 }
1750
1751 void DolphinView::slotTwoClicksRenamingTimerTimeout()
1752 {
1753 const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
1754
1755 // verify that only one item is selected
1756 if (selectionManager->selectedItems().count() == 1) {
1757 const int index = selectionManager->currentItem();
1758 const QUrl fileItemUrl = m_model->fileItem(index).url();
1759
1760 // check if the selected item was the same item that started the twoClicksRenaming
1761 if (fileItemUrl.isValid() && m_twoClicksRenamingItemUrl == fileItemUrl) {
1762 renameSelectedItems();
1763 }
1764 }
1765 }
1766
1767 void DolphinView::slotTrashFileFinished(KJob* job)
1768 {
1769 if (job->error() == 0) {
1770 Q_EMIT operationCompletedMessage(i18nc("@info:status", "Trash operation completed."));
1771 } else if (job->error() != KIO::ERR_USER_CANCELED) {
1772 Q_EMIT errorMessage(job->errorString());
1773 }
1774 }
1775
1776 void DolphinView::slotDeleteFileFinished(KJob* job)
1777 {
1778 if (job->error() == 0) {
1779 Q_EMIT operationCompletedMessage(i18nc("@info:status", "Delete operation completed."));
1780 } else if (job->error() != KIO::ERR_USER_CANCELED) {
1781 Q_EMIT errorMessage(job->errorString());
1782 }
1783 }
1784
1785 void DolphinView::slotRenamingResult(KJob* job)
1786 {
1787 if (job->error()) {
1788 KIO::CopyJob *copyJob = qobject_cast<KIO::CopyJob *>(job);
1789 Q_ASSERT(copyJob);
1790 const QUrl newUrl = copyJob->destUrl();
1791 const int index = m_model->index(newUrl);
1792 if (index >= 0) {
1793 QHash<QByteArray, QVariant> data;
1794 const QUrl oldUrl = copyJob->srcUrls().at(0);
1795 data.insert("text", oldUrl.fileName());
1796 m_model->setData(index, data);
1797 }
1798 }
1799 }
1800
1801 void DolphinView::slotDirectoryLoadingStarted()
1802 {
1803 m_loadingState = LoadingState::Loading;
1804 updatePlaceholderLabel();
1805
1806 // Disable the writestate temporary until it can be determined in a fast way
1807 // in DolphinView::slotDirectoryLoadingCompleted()
1808 if (m_isFolderWritable) {
1809 m_isFolderWritable = false;
1810 Q_EMIT writeStateChanged(m_isFolderWritable);
1811 }
1812
1813 Q_EMIT directoryLoadingStarted();
1814 }
1815
1816 void DolphinView::slotDirectoryLoadingCompleted()
1817 {
1818 m_loadingState = LoadingState::Completed;
1819
1820 // Update the view-state. This has to be done asynchronously
1821 // because the view might not be in its final state yet.
1822 QTimer::singleShot(0, this, &DolphinView::updateViewState);
1823
1824 // Update the placeholder label in case we found that the folder was empty
1825 // after loading it
1826
1827 Q_EMIT directoryLoadingCompleted();
1828
1829 updatePlaceholderLabel();
1830 updateWritableState();
1831 }
1832
1833 void DolphinView::slotDirectoryLoadingCanceled()
1834 {
1835 m_loadingState = LoadingState::Canceled;
1836
1837 updatePlaceholderLabel();
1838
1839 Q_EMIT directoryLoadingCanceled();
1840 }
1841
1842 void DolphinView::slotItemsChanged()
1843 {
1844 m_assureVisibleCurrentIndex = false;
1845 }
1846
1847 void DolphinView::slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous)
1848 {
1849 Q_UNUSED(previous)
1850 Q_ASSERT(m_model->sortOrder() == current);
1851
1852 ViewProperties props(viewPropertiesUrl());
1853 props.setSortOrder(current);
1854
1855 Q_EMIT sortOrderChanged(current);
1856 }
1857
1858 void DolphinView::slotSortRoleChangedByHeader(const QByteArray& current, const QByteArray& previous)
1859 {
1860 Q_UNUSED(previous)
1861 Q_ASSERT(m_model->sortRole() == current);
1862
1863 ViewProperties props(viewPropertiesUrl());
1864 props.setSortRole(current);
1865
1866 Q_EMIT sortRoleChanged(current);
1867 }
1868
1869 void DolphinView::slotVisibleRolesChangedByHeader(const QList<QByteArray>& current,
1870 const QList<QByteArray>& previous)
1871 {
1872 Q_UNUSED(previous)
1873 Q_ASSERT(m_container->controller()->view()->visibleRoles() == current);
1874
1875 const QList<QByteArray> previousVisibleRoles = m_visibleRoles;
1876
1877 m_visibleRoles = current;
1878
1879 ViewProperties props(viewPropertiesUrl());
1880 props.setVisibleRoles(m_visibleRoles);
1881
1882 Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles);
1883 }
1884
1885 void DolphinView::slotRoleEditingCanceled()
1886 {
1887 disconnect(m_view, &DolphinItemListView::roleEditingFinished,
1888 this, &DolphinView::slotRoleEditingFinished);
1889 }
1890
1891 void DolphinView::slotRoleEditingFinished(int index, const QByteArray& role, const QVariant& value)
1892 {
1893 disconnect(m_view, &DolphinItemListView::roleEditingFinished,
1894 this, &DolphinView::slotRoleEditingFinished);
1895
1896 const KFileItemList items = selectedItems();
1897 if (items.count() != 1) {
1898 return;
1899 }
1900
1901 if (role == "text") {
1902 const KFileItem oldItem = items.first();
1903 const EditResult retVal = value.value<EditResult>();
1904 const QString newName = retVal.newName;
1905 if (!newName.isEmpty() && newName != oldItem.text() && newName != QLatin1Char('.') && newName != QLatin1String("..")) {
1906 const QUrl oldUrl = oldItem.url();
1907
1908 QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
1909 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
1910
1911 #ifndef Q_OS_WIN
1912 //Confirm hiding file/directory by renaming inline
1913 if (!hiddenFilesShown() && newName.startsWith(QLatin1Char('.')) && !oldItem.name().startsWith(QLatin1Char('.'))) {
1914 KGuiItem yesGuiItem(i18nc("@action:button", "Rename and Hide"), QStringLiteral("view-hidden"));
1915
1916 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1917 const auto code = KMessageBox::questionTwoActions(this,
1918 #else
1919 const auto code = KMessageBox::questionYesNo(this,
1920 #endif
1921 oldItem.isFile() ? i18n("Adding a dot to the beginning of this file's name will hide it from view.\n"
1922 "Do you still want to rename it?")
1923 : i18n("Adding a dot to the beginning of this folder's name will hide it from view.\n"
1924 "Do you still want to rename it?"),
1925 oldItem.isFile() ? i18n("Hide this File?") : i18n("Hide this Folder?"),
1926 yesGuiItem,
1927 KStandardGuiItem::cancel(),
1928 QStringLiteral("ConfirmHide")
1929 );
1930
1931 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0)
1932 if (code == KMessageBox::SecondaryAction) {
1933 #else
1934 if (code == KMessageBox::No) {
1935 #endif
1936 return;
1937 }
1938 }
1939 #endif
1940
1941 const bool newNameExistsAlready = (m_model->index(newUrl) >= 0);
1942 if (!newNameExistsAlready && m_model->index(oldUrl) == index) {
1943 // Only change the data in the model if no item with the new name
1944 // is in the model yet. If there is an item with the new name
1945 // already, calling KIO::CopyJob will open a dialog
1946 // asking for a new name, and KFileItemModel will update the
1947 // data when the dir lister signals that the file name has changed.
1948 QHash<QByteArray, QVariant> data;
1949 data.insert(role, retVal.newName);
1950 m_model->setData(index, data);
1951 }
1952
1953 KIO::Job * job = KIO::moveAs(oldUrl, newUrl);
1954 KJobWidgets::setWindow(job, this);
1955 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job);
1956 job->uiDelegate()->setAutoErrorHandlingEnabled(true);
1957
1958 forceUrlsSelection(newUrl, {newUrl});
1959
1960 if (!newNameExistsAlready) {
1961 // Only connect the result signal if there is no item with the new name
1962 // in the model yet, see bug 328262.
1963 connect(job, &KJob::result, this, &DolphinView::slotRenamingResult);
1964 }
1965 }
1966 if (retVal.direction != EditDone) {
1967 const short indexShift = retVal.direction == EditNext ? 1 : -1;
1968 m_container->controller()->selectionManager()->setSelected(index, 1, KItemListSelectionManager::Deselect);
1969 m_container->controller()->selectionManager()->setSelected(index + indexShift, 1,
1970 KItemListSelectionManager::Select);
1971 renameSelectedItems();
1972 }
1973 }
1974 }
1975
1976 void DolphinView::loadDirectory(const QUrl& url, bool reload)
1977 {
1978 if (!url.isValid()) {
1979 const QString location(url.toDisplayString(QUrl::PreferLocalFile));
1980 if (location.isEmpty()) {
1981 Q_EMIT errorMessage(i18nc("@info:status", "The location is empty."));
1982 } else {
1983 Q_EMIT errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location));
1984 }
1985 return;
1986 }
1987
1988 if (reload) {
1989 m_model->refreshDirectory(url);
1990 } else {
1991 m_model->loadDirectory(url);
1992 }
1993 }
1994
1995 void DolphinView::applyViewProperties()
1996 {
1997 const ViewProperties props(viewPropertiesUrl());
1998 applyViewProperties(props);
1999 }
2000
2001 void DolphinView::applyViewProperties(const ViewProperties& props)
2002 {
2003 m_view->beginTransaction();
2004
2005 const Mode mode = props.viewMode();
2006 if (m_mode != mode) {
2007 const Mode previousMode = m_mode;
2008 m_mode = mode;
2009
2010 // Changing the mode might result in changing
2011 // the zoom level. Remember the old zoom level so
2012 // that zoomLevelChanged() can get emitted.
2013 const int oldZoomLevel = m_view->zoomLevel();
2014 applyModeToView();
2015
2016 Q_EMIT modeChanged(m_mode, previousMode);
2017
2018 if (m_view->zoomLevel() != oldZoomLevel) {
2019 Q_EMIT zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel);
2020 }
2021 }
2022
2023 const bool hiddenFilesShown = props.hiddenFilesShown();
2024 if (hiddenFilesShown != m_model->showHiddenFiles()) {
2025 m_model->setShowHiddenFiles(hiddenFilesShown);
2026 Q_EMIT hiddenFilesShownChanged(hiddenFilesShown);
2027 }
2028
2029 const bool groupedSorting = props.groupedSorting();
2030 if (groupedSorting != m_model->groupedSorting()) {
2031 m_model->setGroupedSorting(groupedSorting);
2032 Q_EMIT groupedSortingChanged(groupedSorting);
2033 }
2034
2035 const QByteArray sortRole = props.sortRole();
2036 if (sortRole != m_model->sortRole()) {
2037 m_model->setSortRole(sortRole);
2038 Q_EMIT sortRoleChanged(sortRole);
2039 }
2040
2041 const Qt::SortOrder sortOrder = props.sortOrder();
2042 if (sortOrder != m_model->sortOrder()) {
2043 m_model->setSortOrder(sortOrder);
2044 Q_EMIT sortOrderChanged(sortOrder);
2045 }
2046
2047 const bool sortFoldersFirst = props.sortFoldersFirst();
2048 if (sortFoldersFirst != m_model->sortDirectoriesFirst()) {
2049 m_model->setSortDirectoriesFirst(sortFoldersFirst);
2050 Q_EMIT sortFoldersFirstChanged(sortFoldersFirst);
2051 }
2052
2053 const bool sortHiddenLast = props.sortHiddenLast();
2054 if (sortHiddenLast != m_model->sortHiddenLast()) {
2055 m_model->setSortHiddenLast(sortHiddenLast);
2056 Q_EMIT sortHiddenLastChanged(sortHiddenLast);
2057 }
2058
2059 const QList<QByteArray> visibleRoles = props.visibleRoles();
2060 if (visibleRoles != m_visibleRoles) {
2061 const QList<QByteArray> previousVisibleRoles = m_visibleRoles;
2062 m_visibleRoles = visibleRoles;
2063 m_view->setVisibleRoles(visibleRoles);
2064 Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles);
2065 }
2066
2067 const bool previewsShown = props.previewsShown();
2068 if (previewsShown != m_view->previewsShown()) {
2069 const int oldZoomLevel = zoomLevel();
2070
2071 m_view->setPreviewsShown(previewsShown);
2072 Q_EMIT previewsShownChanged(previewsShown);
2073
2074 // Changing the preview-state might result in a changed zoom-level
2075 if (oldZoomLevel != zoomLevel()) {
2076 Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel);
2077 }
2078 }
2079
2080 KItemListView* itemListView = m_container->controller()->view();
2081 if (itemListView->isHeaderVisible()) {
2082 KItemListHeader* header = itemListView->header();
2083 const QList<int> headerColumnWidths = props.headerColumnWidths();
2084 const int rolesCount = m_visibleRoles.count();
2085 if (headerColumnWidths.count() == rolesCount) {
2086 header->setAutomaticColumnResizing(false);
2087
2088 QHash<QByteArray, qreal> columnWidths;
2089 for (int i = 0; i < rolesCount; ++i) {
2090 columnWidths.insert(m_visibleRoles[i], headerColumnWidths[i]);
2091 }
2092 header->setColumnWidths(columnWidths);
2093 } else {
2094 header->setAutomaticColumnResizing(true);
2095 }
2096 header->setSidePadding(DetailsModeSettings::sidePadding());
2097 }
2098
2099 m_view->endTransaction();
2100 }
2101
2102 void DolphinView::applyModeToView()
2103 {
2104 switch (m_mode) {
2105 case IconsView: m_view->setItemLayout(KFileItemListView::IconsLayout); break;
2106 case CompactView: m_view->setItemLayout(KFileItemListView::CompactLayout); break;
2107 case DetailsView: m_view->setItemLayout(KFileItemListView::DetailsLayout); break;
2108 default: Q_ASSERT(false); break;
2109 }
2110 }
2111
2112 void DolphinView::pasteToUrl(const QUrl& url)
2113 {
2114 KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), url);
2115 KJobWidgets::setWindow(job, this);
2116 m_clearSelectionBeforeSelectingNewItems = true;
2117 m_markFirstNewlySelectedItemAsCurrent = true;
2118 connect(job, &KIO::PasteJob::itemCreated, this, &DolphinView::slotItemCreated);
2119 connect(job, &KIO::PasteJob::result, this, &DolphinView::slotJobResult);
2120 }
2121
2122 QList<QUrl> DolphinView::simplifiedSelectedUrls() const
2123 {
2124 QList<QUrl> urls;
2125
2126 const KFileItemList items = selectedItems();
2127 urls.reserve(items.count());
2128 for (const KFileItem& item : items) {
2129 urls.append(item.url());
2130 }
2131
2132 if (itemsExpandable()) {
2133 // TODO: Check if we still need KDirModel for this in KDE 5.0
2134 urls = KDirModel::simplifiedUrlList(urls);
2135 }
2136
2137 return urls;
2138 }
2139
2140 QMimeData* DolphinView::selectionMimeData() const
2141 {
2142 const KItemListSelectionManager* selectionManager = m_container->controller()->selectionManager();
2143 const KItemSet selectedIndexes = selectionManager->selectedItems();
2144
2145 return m_model->createMimeData(selectedIndexes);
2146 }
2147
2148 void DolphinView::updateWritableState()
2149 {
2150 const bool wasFolderWritable = m_isFolderWritable;
2151 m_isFolderWritable = false;
2152
2153 KFileItem item = m_model->rootItem();
2154 if (item.isNull()) {
2155 // Try to find out if the URL is writable even if the "root item" is
2156 // null, see https://bugs.kde.org/show_bug.cgi?id=330001
2157 item = KFileItem(url());
2158 item.setDelayedMimeTypes(true);
2159 }
2160
2161 KFileItemListProperties capabilities(KFileItemList() << item);
2162 m_isFolderWritable = capabilities.supportsWriting();
2163
2164 if (m_isFolderWritable != wasFolderWritable) {
2165 Q_EMIT writeStateChanged(m_isFolderWritable);
2166 }
2167 }
2168
2169 QUrl DolphinView::viewPropertiesUrl() const
2170 {
2171 if (m_viewPropertiesContext.isEmpty()) {
2172 return m_url;
2173 }
2174
2175 QUrl url;
2176 url.setScheme(m_url.scheme());
2177 url.setPath(m_viewPropertiesContext);
2178 return url;
2179 }
2180
2181 void DolphinView::slotRenameDialogRenamingFinished(const QList<QUrl>& urls)
2182 {
2183 forceUrlsSelection(urls.first(), urls);
2184 }
2185
2186 void DolphinView::forceUrlsSelection(const QUrl& current, const QList<QUrl>& selected)
2187 {
2188 clearSelection();
2189 m_clearSelectionBeforeSelectingNewItems = true;
2190 markUrlAsCurrent(current);
2191 markUrlsAsSelected(selected);
2192 }
2193
2194 void DolphinView::copyPathToClipboard()
2195 {
2196 const KFileItemList list = selectedItems();
2197 if (list.isEmpty()) {
2198 return;
2199 }
2200 const KFileItem& item = list.at(0);
2201 QString path = item.localPath();
2202 if (path.isEmpty()) {
2203 path = item.url().toDisplayString();
2204 }
2205 QClipboard* clipboard = QApplication::clipboard();
2206 if (clipboard == nullptr) {
2207 return;
2208 }
2209 clipboard->setText(path);
2210 }
2211
2212 void DolphinView::slotIncreaseZoom()
2213 {
2214 setZoomLevel(zoomLevel() + 1);
2215 }
2216
2217 void DolphinView::slotDecreaseZoom()
2218 {
2219 setZoomLevel(zoomLevel() - 1);
2220 }
2221
2222 void DolphinView::slotSwipeUp()
2223 {
2224 Q_EMIT goUpRequested();
2225 }
2226
2227 void DolphinView::showLoadingPlaceholder()
2228 {
2229 m_placeholderLabel->setText(i18n("Loading..."));
2230 m_placeholderLabel->setVisible(true);
2231 }
2232
2233 void DolphinView::updatePlaceholderLabel()
2234 {
2235 m_showLoadingPlaceholderTimer->stop();
2236 if (itemsCount() > 0) {
2237 m_placeholderLabel->setVisible(false);
2238 return;
2239 }
2240
2241 if (m_loadingState == LoadingState::Loading) {
2242 m_placeholderLabel->setVisible(false);
2243 m_showLoadingPlaceholderTimer->start();
2244 return;
2245 }
2246
2247 if (m_loadingState == LoadingState::Canceled) {
2248 m_placeholderLabel->setText(i18n("Loading canceled"));
2249 } else if (!nameFilter().isEmpty()) {
2250 m_placeholderLabel->setText(i18n("No items matching the filter"));
2251 } else if (m_url.scheme() == QLatin1String("baloosearch") || m_url.scheme() == QLatin1String("filenamesearch")) {
2252 m_placeholderLabel->setText(i18n("No items matching the search"));
2253 } else if (m_url.scheme() == QLatin1String("trash") && m_url.path() == QLatin1String("/")) {
2254 m_placeholderLabel->setText(i18n("Trash is empty"));
2255 } else if (m_url.scheme() == QLatin1String("tags")) {
2256 if (m_url.path() == QLatin1Char('/')) {
2257 m_placeholderLabel->setText(i18n("No tags"));
2258 } else {
2259 const QString tagName = m_url.path().mid(1); // Remove leading /
2260 m_placeholderLabel->setText(i18n("No files tagged with \"%1\"", tagName));
2261 }
2262
2263 } else if (m_url.scheme() == QLatin1String("recentlyused")) {
2264 m_placeholderLabel->setText(i18n("No recently used items"));
2265 } else if (m_url.scheme() == QLatin1String("smb")) {
2266 m_placeholderLabel->setText(i18n("No shared folders found"));
2267 } else if (m_url.scheme() == QLatin1String("network")) {
2268 m_placeholderLabel->setText(i18n("No relevant network resources found"));
2269 } else if (m_url.scheme() == QLatin1String("mtp") && m_url.path() == QLatin1String("/")) {
2270 m_placeholderLabel->setText(i18n("No MTP-compatible devices found"));
2271 } else if (m_url.scheme() == QLatin1String("afc") && m_url.path() == QLatin1String("/")) {
2272 m_placeholderLabel->setText(i18n("No Apple devices found"));
2273 } else if (m_url.scheme() == QLatin1String("bluetooth")) {
2274 m_placeholderLabel->setText(i18n("No Bluetooth devices found"));
2275 } else {
2276 m_placeholderLabel->setText(i18n("Folder is empty"));
2277 }
2278
2279 m_placeholderLabel->setVisible(true);
2280 }
2281
2282 void DolphinView::tryShowNameToolTip(QHelpEvent* event)
2283 {
2284 if (!GeneralSettings::showToolTips() && m_mode == DolphinView::IconsView) {
2285 const std::optional<int> index = m_view->itemAt(event->pos());
2286
2287 if (!index.has_value()) {
2288 return;
2289 }
2290
2291 // Check whether the filename has been elided
2292 const bool isElided = m_view->isElided(index.value());
2293
2294 if(isElided) {
2295 const KFileItem item = m_model->fileItem(index.value());
2296 const QString text = item.text();
2297 const QPoint pos = mapToGlobal(event->pos());
2298 QToolTip::showText(pos, text);
2299 }
2300 }
2301 }