File indexing completed on 2024-05-12 09:51:15
0001 /* 0002 * SPDX-FileCopyrightText: 2006-2009 Peter Penz <peter.penz19@gmail.com> 0003 * SPDX-FileCopyrightText: 2006 Gregor Kališnik <gregor@podnapisi.net> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "dolphinview.h" 0009 0010 #include "dolphin_detailsmodesettings.h" 0011 #include "dolphin_generalsettings.h" 0012 #include "dolphinitemlistview.h" 0013 #include "dolphinnewfilemenuobserver.h" 0014 #include "draganddrophelper.h" 0015 #include "kitemviews/kfileitemlistview.h" 0016 #include "kitemviews/kfileitemmodel.h" 0017 #include "kitemviews/kitemlistcontainer.h" 0018 #include "kitemviews/kitemlistcontroller.h" 0019 #include "kitemviews/kitemlistheader.h" 0020 #include "kitemviews/kitemlistselectionmanager.h" 0021 #include "kitemviews/private/kitemlistroleeditor.h" 0022 #include "selectionmode/singleclickselectionproxystyle.h" 0023 #include "settings/viewmodes/viewmodesettings.h" 0024 #include "versioncontrol/versioncontrolobserver.h" 0025 #include "viewproperties.h" 0026 #include "views/tooltips/tooltipmanager.h" 0027 #include "zoomlevelinfo.h" 0028 0029 #if HAVE_BALOO 0030 #include <Baloo/IndexerConfig> 0031 #endif 0032 #include <KColorScheme> 0033 #include <KDesktopFile> 0034 #include <KDirModel> 0035 #include <KFileItemListProperties> 0036 #include <KFormat> 0037 #include <KIO/CopyJob> 0038 #include <KIO/DeleteOrTrashJob> 0039 #include <KIO/DropJob> 0040 #include <KIO/JobUiDelegate> 0041 #include <KIO/Paste> 0042 #include <KIO/PasteJob> 0043 #include <KIO/RenameFileDialog> 0044 #include <KJobWidgets> 0045 #include <KLocalizedString> 0046 #include <KMessageBox> 0047 #include <KProtocolManager> 0048 #include <KUrlMimeData> 0049 0050 #include <kwidgetsaddons_version.h> 0051 0052 #include <QAbstractItemView> 0053 #include <QActionGroup> 0054 #include <QApplication> 0055 #include <QClipboard> 0056 #include <QDropEvent> 0057 #include <QGraphicsOpacityEffect> 0058 #include <QGraphicsSceneDragDropEvent> 0059 #include <QLabel> 0060 #include <QMenu> 0061 #include <QMimeDatabase> 0062 #include <QPixmapCache> 0063 #include <QScrollBar> 0064 #include <QSize> 0065 #include <QTimer> 0066 #include <QToolTip> 0067 #include <QVBoxLayout> 0068 0069 DolphinView::DolphinView(const QUrl &url, QWidget *parent) 0070 : QWidget(parent) 0071 , m_active(true) 0072 , m_tabsForFiles(false) 0073 , m_assureVisibleCurrentIndex(false) 0074 , m_isFolderWritable(true) 0075 , m_dragging(false) 0076 , m_selectNextItem(false) 0077 , m_url(url) 0078 , m_viewPropertiesContext() 0079 , m_mode(DolphinView::IconsView) 0080 , m_visibleRoles() 0081 , m_topLayout(nullptr) 0082 , m_model(nullptr) 0083 , m_view(nullptr) 0084 , m_container(nullptr) 0085 , m_toolTipManager(nullptr) 0086 , m_selectionChangedTimer(nullptr) 0087 , m_currentItemUrl() 0088 , m_scrollToCurrentItem(false) 0089 , m_restoredContentsPosition() 0090 , m_controlWheelAccumulatedDelta(0) 0091 , m_selectedUrls() 0092 , m_clearSelectionBeforeSelectingNewItems(false) 0093 , m_markFirstNewlySelectedItemAsCurrent(false) 0094 , m_versionControlObserver(nullptr) 0095 , m_twoClicksRenamingTimer(nullptr) 0096 , m_placeholderLabel(nullptr) 0097 , m_showLoadingPlaceholderTimer(nullptr) 0098 { 0099 m_topLayout = new QVBoxLayout(this); 0100 m_topLayout->setSpacing(0); 0101 m_topLayout->setContentsMargins(0, 0, 0, 0); 0102 0103 // When a new item has been created by the "Create New..." menu, the item should 0104 // get selected and it must be assured that the item will get visible. As the 0105 // creation is done asynchronously, several signals must be checked: 0106 connect(&DolphinNewFileMenuObserver::instance(), &DolphinNewFileMenuObserver::itemCreated, this, &DolphinView::observeCreatedItem); 0107 0108 m_selectionChangedTimer = new QTimer(this); 0109 m_selectionChangedTimer->setSingleShot(true); 0110 m_selectionChangedTimer->setInterval(300); 0111 connect(m_selectionChangedTimer, &QTimer::timeout, this, &DolphinView::emitSelectionChangedSignal); 0112 0113 m_model = new KFileItemModel(this); 0114 m_view = new DolphinItemListView(); 0115 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::FollowSetting); 0116 m_view->setVisibleRoles({"text"}); 0117 applyModeToView(); 0118 0119 KItemListController *controller = new KItemListController(m_model, m_view, this); 0120 const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; 0121 controller->setAutoActivationDelay(delay); 0122 0123 // The EnlargeSmallPreviews setting can only be changed after the model 0124 // has been set in the view by KItemListController. 0125 m_view->setEnlargeSmallPreviews(GeneralSettings::enlargeSmallPreviews()); 0126 0127 m_container = new KItemListContainer(controller, this); 0128 m_container->installEventFilter(this); 0129 #ifndef QT_NO_ACCESSIBILITY 0130 m_view->setAccessibleParentsObject(m_container); 0131 #endif 0132 setFocusProxy(m_container); 0133 connect(m_container->horizontalScrollBar(), &QScrollBar::valueChanged, this, [=] { 0134 hideToolTip(); 0135 }); 0136 connect(m_container->verticalScrollBar(), &QScrollBar::valueChanged, this, [=] { 0137 hideToolTip(); 0138 }); 0139 0140 m_showLoadingPlaceholderTimer = new QTimer(this); 0141 m_showLoadingPlaceholderTimer->setInterval(500); 0142 m_showLoadingPlaceholderTimer->setSingleShot(true); 0143 connect(m_showLoadingPlaceholderTimer, &QTimer::timeout, this, &DolphinView::showLoadingPlaceholder); 0144 0145 // Show some placeholder text for empty folders 0146 // This is made using a heavily-modified QLabel rather than a KTitleWidget 0147 // because KTitleWidget can't be told to turn off mouse-selectable text 0148 m_placeholderLabel = new QLabel(this); 0149 // Don't consume mouse events 0150 m_placeholderLabel->setAttribute(Qt::WA_TransparentForMouseEvents); 0151 0152 QFont placeholderLabelFont; 0153 // To match the size of a level 2 Heading/KTitleWidget 0154 placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3)); 0155 m_placeholderLabel->setFont(placeholderLabelFont); 0156 m_placeholderLabel->setWordWrap(true); 0157 m_placeholderLabel->setAlignment(Qt::AlignCenter); 0158 // Match opacity of QML placeholder label component 0159 auto *effect = new QGraphicsOpacityEffect(m_placeholderLabel); 0160 effect->setOpacity(0.5); 0161 m_placeholderLabel->setGraphicsEffect(effect); 0162 // Set initial text and visibility 0163 updatePlaceholderLabel(); 0164 0165 auto *centeringLayout = new QVBoxLayout(m_container); 0166 centeringLayout->addWidget(m_placeholderLabel); 0167 centeringLayout->setAlignment(m_placeholderLabel, Qt::AlignCenter); 0168 0169 controller->setSelectionBehavior(KItemListController::MultiSelection); 0170 connect(controller, &KItemListController::itemActivated, this, &DolphinView::slotItemActivated); 0171 connect(controller, &KItemListController::itemsActivated, this, &DolphinView::slotItemsActivated); 0172 connect(controller, &KItemListController::itemMiddleClicked, this, &DolphinView::slotItemMiddleClicked); 0173 connect(controller, &KItemListController::itemContextMenuRequested, this, &DolphinView::slotItemContextMenuRequested); 0174 connect(controller, &KItemListController::viewContextMenuRequested, this, &DolphinView::slotViewContextMenuRequested); 0175 connect(controller, &KItemListController::headerContextMenuRequested, this, &DolphinView::slotHeaderContextMenuRequested); 0176 connect(controller, &KItemListController::mouseButtonPressed, this, &DolphinView::slotMouseButtonPressed); 0177 connect(controller, &KItemListController::itemHovered, this, &DolphinView::slotItemHovered); 0178 connect(controller, &KItemListController::itemUnhovered, this, &DolphinView::slotItemUnhovered); 0179 connect(controller, &KItemListController::itemDropEvent, this, &DolphinView::slotItemDropEvent); 0180 connect(controller, &KItemListController::escapePressed, this, &DolphinView::stopLoading); 0181 connect(controller, &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); 0182 connect(controller, &KItemListController::selectedItemTextPressed, this, &DolphinView::slotSelectedItemTextPressed); 0183 connect(controller, &KItemListController::increaseZoom, this, &DolphinView::slotIncreaseZoom); 0184 connect(controller, &KItemListController::decreaseZoom, this, &DolphinView::slotDecreaseZoom); 0185 connect(controller, &KItemListController::swipeUp, this, &DolphinView::slotSwipeUp); 0186 connect(controller, &KItemListController::selectionModeChangeRequested, this, &DolphinView::selectionModeChangeRequested); 0187 0188 connect(m_model, &KFileItemModel::directoryLoadingStarted, this, &DolphinView::slotDirectoryLoadingStarted); 0189 connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); 0190 connect(m_model, &KFileItemModel::directoryLoadingCanceled, this, &DolphinView::slotDirectoryLoadingCanceled); 0191 connect(m_model, &KFileItemModel::directoryLoadingProgress, this, &DolphinView::directoryLoadingProgress); 0192 connect(m_model, &KFileItemModel::directorySortingProgress, this, &DolphinView::directorySortingProgress); 0193 connect(m_model, &KFileItemModel::itemsChanged, this, &DolphinView::slotItemsChanged); 0194 connect(m_model, &KFileItemModel::itemsRemoved, this, &DolphinView::itemCountChanged); 0195 connect(m_model, &KFileItemModel::itemsInserted, this, &DolphinView::itemCountChanged); 0196 connect(m_model, &KFileItemModel::infoMessage, this, &DolphinView::infoMessage); 0197 connect(m_model, &KFileItemModel::errorMessage, this, &DolphinView::errorMessage); 0198 connect(m_model, &KFileItemModel::directoryRedirection, this, &DolphinView::slotDirectoryRedirection); 0199 connect(m_model, &KFileItemModel::urlIsFileError, this, &DolphinView::urlIsFileError); 0200 connect(m_model, &KFileItemModel::fileItemsChanged, this, &DolphinView::fileItemsChanged); 0201 connect(m_model, &KFileItemModel::currentDirectoryRemoved, this, &DolphinView::currentDirectoryRemoved); 0202 0203 connect(this, &DolphinView::itemCountChanged, this, &DolphinView::updatePlaceholderLabel); 0204 0205 m_view->installEventFilter(this); 0206 connect(m_view, &DolphinItemListView::sortOrderChanged, this, &DolphinView::slotSortOrderChangedByHeader); 0207 connect(m_view, &DolphinItemListView::sortRoleChanged, this, &DolphinView::slotSortRoleChangedByHeader); 0208 connect(m_view, &DolphinItemListView::visibleRolesChanged, this, &DolphinView::slotVisibleRolesChangedByHeader); 0209 connect(m_view, &DolphinItemListView::roleEditingCanceled, this, &DolphinView::slotRoleEditingCanceled); 0210 0211 connect(m_view, &DolphinItemListView::columnHovered, this, [this](int columnIndex) { 0212 m_hoveredColumnHeaderIndex = columnIndex; 0213 }); 0214 connect(m_view, &DolphinItemListView::columnUnHovered, this, [this](int /* columnIndex */) { 0215 m_hoveredColumnHeaderIndex = std::nullopt; 0216 }); 0217 connect(m_view->header(), &KItemListHeader::columnWidthChangeFinished, this, &DolphinView::slotHeaderColumnWidthChangeFinished); 0218 connect(m_view->header(), &KItemListHeader::sidePaddingChanged, this, &DolphinView::slotSidePaddingWidthChanged); 0219 0220 KItemListSelectionManager *selectionManager = controller->selectionManager(); 0221 connect(selectionManager, &KItemListSelectionManager::selectionChanged, this, &DolphinView::slotSelectionChanged); 0222 0223 #if HAVE_BALOO 0224 m_toolTipManager = new ToolTipManager(this); 0225 connect(m_toolTipManager, &ToolTipManager::urlActivated, this, &DolphinView::urlActivated); 0226 #endif 0227 0228 m_versionControlObserver = new VersionControlObserver(this); 0229 m_versionControlObserver->setView(this); 0230 m_versionControlObserver->setModel(m_model); 0231 connect(m_versionControlObserver, &VersionControlObserver::infoMessage, this, &DolphinView::infoMessage); 0232 connect(m_versionControlObserver, &VersionControlObserver::errorMessage, this, &DolphinView::errorMessage); 0233 connect(m_versionControlObserver, &VersionControlObserver::operationCompletedMessage, this, &DolphinView::operationCompletedMessage); 0234 0235 m_twoClicksRenamingTimer = new QTimer(this); 0236 m_twoClicksRenamingTimer->setSingleShot(true); 0237 connect(m_twoClicksRenamingTimer, &QTimer::timeout, this, &DolphinView::slotTwoClicksRenamingTimerTimeout); 0238 0239 applyViewProperties(); 0240 m_topLayout->addWidget(m_container); 0241 0242 loadDirectory(url); 0243 } 0244 0245 DolphinView::~DolphinView() 0246 { 0247 disconnect(m_container->controller(), &KItemListController::modelChanged, this, &DolphinView::slotModelChanged); 0248 } 0249 0250 QUrl DolphinView::url() const 0251 { 0252 return m_url; 0253 } 0254 0255 void DolphinView::setActive(bool active) 0256 { 0257 if (active == m_active) { 0258 return; 0259 } 0260 0261 m_active = active; 0262 0263 updatePalette(); 0264 0265 if (active) { 0266 m_container->setFocus(); 0267 Q_EMIT activated(); 0268 } 0269 } 0270 0271 bool DolphinView::isActive() const 0272 { 0273 return m_active; 0274 } 0275 0276 void DolphinView::setViewMode(Mode mode) 0277 { 0278 if (mode != m_mode) { 0279 // Reset scrollbars before changing the view mode. 0280 m_container->horizontalScrollBar()->setValue(0); 0281 m_container->verticalScrollBar()->setValue(0); 0282 0283 ViewProperties props(viewPropertiesUrl()); 0284 props.setViewMode(mode); 0285 0286 // We pass the new ViewProperties to applyViewProperties, rather than 0287 // storing them on disk and letting applyViewProperties() read them 0288 // from there, to prevent that changing the view mode fails if the 0289 // .directory file is not writable (see bug 318534). 0290 applyViewProperties(props); 0291 } 0292 } 0293 0294 DolphinView::Mode DolphinView::viewMode() const 0295 { 0296 return m_mode; 0297 } 0298 0299 void DolphinView::setSelectionModeEnabled(const bool enabled) 0300 { 0301 if (enabled) { 0302 if (!m_proxyStyle) { 0303 m_proxyStyle = std::make_unique<SelectionMode::SingleClickSelectionProxyStyle>(); 0304 } 0305 setStyle(m_proxyStyle.get()); 0306 m_view->setStyle(m_proxyStyle.get()); 0307 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::False); 0308 } else { 0309 setStyle(nullptr); 0310 m_view->setStyle(nullptr); 0311 m_view->setEnabledSelectionToggles(DolphinItemListView::SelectionTogglesEnabled::FollowSetting); 0312 } 0313 m_container->controller()->setSelectionModeEnabled(enabled); 0314 } 0315 0316 bool DolphinView::selectionMode() const 0317 { 0318 return m_container->controller()->selectionMode(); 0319 } 0320 0321 void DolphinView::setPreviewsShown(bool show) 0322 { 0323 if (previewsShown() == show) { 0324 return; 0325 } 0326 0327 ViewProperties props(viewPropertiesUrl()); 0328 props.setPreviewsShown(show); 0329 0330 const int oldZoomLevel = m_view->zoomLevel(); 0331 m_view->setPreviewsShown(show); 0332 Q_EMIT previewsShownChanged(show); 0333 0334 const int newZoomLevel = m_view->zoomLevel(); 0335 if (newZoomLevel != oldZoomLevel) { 0336 Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel); 0337 } 0338 } 0339 0340 bool DolphinView::previewsShown() const 0341 { 0342 return m_view->previewsShown(); 0343 } 0344 0345 void DolphinView::setHiddenFilesShown(bool show) 0346 { 0347 if (m_model->showHiddenFiles() == show) { 0348 return; 0349 } 0350 0351 const KFileItemList itemList = selectedItems(); 0352 m_selectedUrls.clear(); 0353 m_selectedUrls = itemList.urlList(); 0354 0355 ViewProperties props(viewPropertiesUrl()); 0356 props.setHiddenFilesShown(show); 0357 0358 m_model->setShowHiddenFiles(show); 0359 Q_EMIT hiddenFilesShownChanged(show); 0360 } 0361 0362 bool DolphinView::hiddenFilesShown() const 0363 { 0364 return m_model->showHiddenFiles(); 0365 } 0366 0367 void DolphinView::setGroupedSorting(bool grouped) 0368 { 0369 if (grouped == groupedSorting()) { 0370 return; 0371 } 0372 0373 ViewProperties props(viewPropertiesUrl()); 0374 props.setGroupedSorting(grouped); 0375 props.save(); 0376 0377 m_container->controller()->model()->setGroupedSorting(grouped); 0378 0379 Q_EMIT groupedSortingChanged(grouped); 0380 } 0381 0382 bool DolphinView::groupedSorting() const 0383 { 0384 return m_model->groupedSorting(); 0385 } 0386 0387 KFileItemList DolphinView::items() const 0388 { 0389 KFileItemList list; 0390 const int itemCount = m_model->count(); 0391 list.reserve(itemCount); 0392 0393 for (int i = 0; i < itemCount; ++i) { 0394 list.append(m_model->fileItem(i)); 0395 } 0396 0397 return list; 0398 } 0399 0400 int DolphinView::itemsCount() const 0401 { 0402 return m_model->count(); 0403 } 0404 0405 KFileItemList DolphinView::selectedItems() const 0406 { 0407 const KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 0408 0409 KFileItemList selectedItems; 0410 const auto items = selectionManager->selectedItems(); 0411 selectedItems.reserve(items.count()); 0412 for (int index : items) { 0413 selectedItems.append(m_model->fileItem(index)); 0414 } 0415 return selectedItems; 0416 } 0417 0418 int DolphinView::selectedItemsCount() const 0419 { 0420 const KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 0421 return selectionManager->selectedItems().count(); 0422 } 0423 0424 void DolphinView::markUrlsAsSelected(const QList<QUrl> &urls) 0425 { 0426 m_selectedUrls = urls; 0427 m_selectJobCreatedItems = false; 0428 } 0429 0430 void DolphinView::markUrlAsCurrent(const QUrl &url) 0431 { 0432 m_currentItemUrl = url; 0433 m_scrollToCurrentItem = true; 0434 } 0435 0436 void DolphinView::selectItems(const QRegularExpression ®exp, bool enabled) 0437 { 0438 const KItemListSelectionManager::SelectionMode mode = enabled ? KItemListSelectionManager::Select : KItemListSelectionManager::Deselect; 0439 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 0440 0441 for (int index = 0; index < m_model->count(); index++) { 0442 const KFileItem item = m_model->fileItem(index); 0443 if (regexp.match(item.text()).hasMatch()) { 0444 // An alternative approach would be to store the matching items in a KItemSet and 0445 // select them in one go after the loop, but we'd need a new function 0446 // KItemListSelectionManager::setSelected(KItemSet, SelectionMode mode) 0447 // for that. 0448 selectionManager->setSelected(index, 1, mode); 0449 } 0450 } 0451 } 0452 0453 void DolphinView::setZoomLevel(int level) 0454 { 0455 const int oldZoomLevel = zoomLevel(); 0456 m_view->setZoomLevel(level); 0457 if (zoomLevel() != oldZoomLevel) { 0458 hideToolTip(); 0459 Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel); 0460 } 0461 } 0462 0463 int DolphinView::zoomLevel() const 0464 { 0465 return m_view->zoomLevel(); 0466 } 0467 0468 void DolphinView::setSortRole(const QByteArray &role) 0469 { 0470 if (role != sortRole()) { 0471 updateSortRole(role); 0472 } 0473 } 0474 0475 QByteArray DolphinView::sortRole() const 0476 { 0477 const KItemModelBase *model = m_container->controller()->model(); 0478 return model->sortRole(); 0479 } 0480 0481 void DolphinView::setSortOrder(Qt::SortOrder order) 0482 { 0483 if (sortOrder() != order) { 0484 updateSortOrder(order); 0485 } 0486 } 0487 0488 Qt::SortOrder DolphinView::sortOrder() const 0489 { 0490 return m_model->sortOrder(); 0491 } 0492 0493 void DolphinView::setSortFoldersFirst(bool foldersFirst) 0494 { 0495 if (sortFoldersFirst() != foldersFirst) { 0496 updateSortFoldersFirst(foldersFirst); 0497 } 0498 } 0499 0500 bool DolphinView::sortFoldersFirst() const 0501 { 0502 return m_model->sortDirectoriesFirst(); 0503 } 0504 0505 void DolphinView::setSortHiddenLast(bool hiddenLast) 0506 { 0507 if (sortHiddenLast() != hiddenLast) { 0508 updateSortHiddenLast(hiddenLast); 0509 } 0510 } 0511 0512 bool DolphinView::sortHiddenLast() const 0513 { 0514 return m_model->sortHiddenLast(); 0515 } 0516 0517 void DolphinView::setVisibleRoles(const QList<QByteArray> &roles) 0518 { 0519 const QList<QByteArray> previousRoles = roles; 0520 0521 ViewProperties props(viewPropertiesUrl()); 0522 props.setVisibleRoles(roles); 0523 0524 m_visibleRoles = roles; 0525 m_view->setVisibleRoles(roles); 0526 0527 Q_EMIT visibleRolesChanged(m_visibleRoles, previousRoles); 0528 } 0529 0530 QList<QByteArray> DolphinView::visibleRoles() const 0531 { 0532 return m_visibleRoles; 0533 } 0534 0535 void DolphinView::reload() 0536 { 0537 QByteArray viewState; 0538 QDataStream saveStream(&viewState, QIODevice::WriteOnly); 0539 saveState(saveStream); 0540 0541 setUrl(url()); 0542 loadDirectory(url(), true); 0543 0544 QDataStream restoreStream(viewState); 0545 restoreState(restoreStream); 0546 } 0547 0548 void DolphinView::readSettings() 0549 { 0550 const int oldZoomLevel = m_view->zoomLevel(); 0551 0552 GeneralSettings::self()->load(); 0553 m_view->readSettings(); 0554 applyViewProperties(); 0555 0556 const int delay = GeneralSettings::autoExpandFolders() ? 750 : -1; 0557 m_container->controller()->setAutoActivationDelay(delay); 0558 0559 const int newZoomLevel = m_view->zoomLevel(); 0560 if (newZoomLevel != oldZoomLevel) { 0561 Q_EMIT zoomLevelChanged(newZoomLevel, oldZoomLevel); 0562 } 0563 } 0564 0565 void DolphinView::writeSettings() 0566 { 0567 GeneralSettings::self()->save(); 0568 m_view->writeSettings(); 0569 } 0570 0571 void DolphinView::setNameFilter(const QString &nameFilter) 0572 { 0573 m_model->setNameFilter(nameFilter); 0574 } 0575 0576 QString DolphinView::nameFilter() const 0577 { 0578 return m_model->nameFilter(); 0579 } 0580 0581 void DolphinView::setMimeTypeFilters(const QStringList &filters) 0582 { 0583 return m_model->setMimeTypeFilters(filters); 0584 } 0585 0586 QStringList DolphinView::mimeTypeFilters() const 0587 { 0588 return m_model->mimeTypeFilters(); 0589 } 0590 0591 void DolphinView::requestStatusBarText() 0592 { 0593 if (m_statJobForStatusBarText) { 0594 // Kill the pending request. 0595 m_statJobForStatusBarText->kill(); 0596 } 0597 0598 if (m_container->controller()->selectionManager()->hasSelection()) { 0599 int folderCount = 0; 0600 int fileCount = 0; 0601 KIO::filesize_t totalFileSize = 0; 0602 0603 // Give a summary of the status of the selected files 0604 const KFileItemList list = selectedItems(); 0605 for (const KFileItem &item : list) { 0606 if (item.isDir()) { 0607 ++folderCount; 0608 } else { 0609 ++fileCount; 0610 totalFileSize += item.size(); 0611 } 0612 } 0613 0614 if (folderCount + fileCount == 1) { 0615 // If only one item is selected, show info about it 0616 Q_EMIT statusBarTextChanged(list.first().getStatusBarInfo()); 0617 } else { 0618 // At least 2 items are selected 0619 emitStatusBarText(folderCount, fileCount, totalFileSize, HasSelection); 0620 } 0621 } else { // has no selection 0622 if (!m_model->rootItem().url().isValid()) { 0623 return; 0624 } 0625 0626 m_statJobForStatusBarText = KIO::stat(m_model->rootItem().url(), KIO::StatJob::SourceSide, KIO::StatRecursiveSize, KIO::HideProgressInfo); 0627 connect(m_statJobForStatusBarText, &KJob::result, this, &DolphinView::slotStatJobResult); 0628 m_statJobForStatusBarText->start(); 0629 } 0630 } 0631 0632 void DolphinView::emitStatusBarText(const int folderCount, const int fileCount, KIO::filesize_t totalFileSize, const Selection selection) 0633 { 0634 QString foldersText; 0635 QString filesText; 0636 QString summary; 0637 0638 if (selection == HasSelection) { 0639 // At least 2 items are selected because the case of 1 selected item is handled in 0640 // DolphinView::requestStatusBarText(). 0641 foldersText = i18ncp("@info:status", "1 folder selected", "%1 folders selected", folderCount); 0642 filesText = i18ncp("@info:status", "1 file selected", "%1 files selected", fileCount); 0643 } else { 0644 foldersText = i18ncp("@info:status", "1 folder", "%1 folders", folderCount); 0645 filesText = i18ncp("@info:status", "1 file", "%1 files", fileCount); 0646 } 0647 0648 if (fileCount > 0 && folderCount > 0) { 0649 summary = i18nc("@info:status folders, files (size)", "%1, %2 (%3)", foldersText, filesText, KFormat().formatByteSize(totalFileSize)); 0650 } else if (fileCount > 0) { 0651 summary = i18nc("@info:status files (size)", "%1 (%2)", filesText, KFormat().formatByteSize(totalFileSize)); 0652 } else if (folderCount > 0) { 0653 summary = foldersText; 0654 } else { 0655 summary = i18nc("@info:status", "0 folders, 0 files"); 0656 } 0657 Q_EMIT statusBarTextChanged(summary); 0658 } 0659 0660 QList<QAction *> DolphinView::versionControlActions(const KFileItemList &items) const 0661 { 0662 QList<QAction *> actions; 0663 0664 if (items.isEmpty()) { 0665 const KFileItem item = m_model->rootItem(); 0666 if (!item.isNull()) { 0667 actions = m_versionControlObserver->actions(KFileItemList() << item); 0668 } 0669 } else { 0670 actions = m_versionControlObserver->actions(items); 0671 } 0672 0673 return actions; 0674 } 0675 0676 void DolphinView::setUrl(const QUrl &url) 0677 { 0678 if (url == m_url) { 0679 return; 0680 } 0681 0682 clearSelection(); 0683 0684 m_url = url; 0685 0686 hideToolTip(); 0687 0688 disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); 0689 0690 // It is important to clear the items from the model before 0691 // applying the view properties, otherwise expensive operations 0692 // might be done on the existing items although they get cleared 0693 // anyhow afterwards by loadDirectory(). 0694 m_model->clear(); 0695 applyViewProperties(); 0696 loadDirectory(url); 0697 0698 Q_EMIT urlChanged(url); 0699 } 0700 0701 void DolphinView::selectAll() 0702 { 0703 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 0704 selectionManager->setSelected(0, m_model->count()); 0705 } 0706 0707 void DolphinView::invertSelection() 0708 { 0709 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 0710 selectionManager->setSelected(0, m_model->count(), KItemListSelectionManager::Toggle); 0711 } 0712 0713 void DolphinView::clearSelection() 0714 { 0715 m_selectJobCreatedItems = false; 0716 m_selectedUrls.clear(); 0717 m_container->controller()->selectionManager()->clearSelection(); 0718 } 0719 0720 void DolphinView::renameSelectedItems() 0721 { 0722 const KFileItemList items = selectedItems(); 0723 if (items.isEmpty()) { 0724 return; 0725 } 0726 0727 if (items.count() == 1 && GeneralSettings::renameInline()) { 0728 const int index = m_model->index(items.first()); 0729 0730 QMetaObject::Connection *const connection = new QMetaObject::Connection; 0731 *connection = connect(m_view, &KItemListView::scrollingStopped, this, [=]() { 0732 QObject::disconnect(*connection); 0733 delete connection; 0734 0735 m_view->editRole(index, "text"); 0736 0737 hideToolTip(); 0738 0739 connect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); 0740 }); 0741 m_view->scrollToItem(index); 0742 0743 } else { 0744 KIO::RenameFileDialog *dialog = new KIO::RenameFileDialog(items, this); 0745 connect(dialog, &KIO::RenameFileDialog::renamingFinished, this, &DolphinView::slotRenameDialogRenamingFinished); 0746 0747 dialog->open(); 0748 } 0749 0750 // Assure that the current index remains visible when KFileItemModel 0751 // will notify the view about changed items (which might result in 0752 // a changed sorting). 0753 m_assureVisibleCurrentIndex = true; 0754 } 0755 0756 void DolphinView::trashSelectedItems() 0757 { 0758 const QList<QUrl> list = simplifiedSelectedUrls(); 0759 0760 using Iface = KIO::AskUserActionInterface; 0761 auto *trashJob = new KIO::DeleteOrTrashJob(list, Iface::Trash, Iface::DefaultConfirmation, this); 0762 connect(trashJob, &KJob::result, this, &DolphinView::slotTrashFileFinished); 0763 m_selectNextItem = true; 0764 trashJob->start(); 0765 } 0766 0767 void DolphinView::deleteSelectedItems() 0768 { 0769 const QList<QUrl> list = simplifiedSelectedUrls(); 0770 0771 using Iface = KIO::AskUserActionInterface; 0772 auto *trashJob = new KIO::DeleteOrTrashJob(list, Iface::Delete, Iface::DefaultConfirmation, this); 0773 connect(trashJob, &KJob::result, this, &DolphinView::slotTrashFileFinished); 0774 m_selectNextItem = true; 0775 trashJob->start(); 0776 } 0777 0778 void DolphinView::cutSelectedItemsToClipboard() 0779 { 0780 QMimeData *mimeData = selectionMimeData(); 0781 KIO::setClipboardDataCut(mimeData, true); 0782 KUrlMimeData::exportUrlsToPortal(mimeData); 0783 QApplication::clipboard()->setMimeData(mimeData); 0784 } 0785 0786 void DolphinView::copySelectedItemsToClipboard() 0787 { 0788 QMimeData *mimeData = selectionMimeData(); 0789 KUrlMimeData::exportUrlsToPortal(mimeData); 0790 QApplication::clipboard()->setMimeData(mimeData); 0791 } 0792 0793 void DolphinView::copySelectedItems(const KFileItemList &selection, const QUrl &destinationUrl) 0794 { 0795 if (selection.isEmpty() || !destinationUrl.isValid()) { 0796 return; 0797 } 0798 0799 m_clearSelectionBeforeSelectingNewItems = true; 0800 m_markFirstNewlySelectedItemAsCurrent = true; 0801 m_selectJobCreatedItems = true; 0802 0803 KIO::CopyJob *job = KIO::copy(selection.urlList(), destinationUrl, KIO::DefaultFlags); 0804 KJobWidgets::setWindow(job, this); 0805 0806 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); 0807 connect(job, &KIO::CopyJob::copying, this, &DolphinView::slotItemCreatedFromJob); 0808 connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotItemCreatedFromJob); 0809 KIO::FileUndoManager::self()->recordCopyJob(job); 0810 } 0811 0812 void DolphinView::moveSelectedItems(const KFileItemList &selection, const QUrl &destinationUrl) 0813 { 0814 if (selection.isEmpty() || !destinationUrl.isValid()) { 0815 return; 0816 } 0817 0818 m_clearSelectionBeforeSelectingNewItems = true; 0819 m_markFirstNewlySelectedItemAsCurrent = true; 0820 m_selectJobCreatedItems = true; 0821 0822 KIO::CopyJob *job = KIO::move(selection.urlList(), destinationUrl, KIO::DefaultFlags); 0823 KJobWidgets::setWindow(job, this); 0824 0825 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); 0826 connect(job, &KIO::CopyJob::moving, this, &DolphinView::slotItemCreatedFromJob); 0827 connect(job, &KIO::CopyJob::copyingDone, this, &DolphinView::slotItemCreatedFromJob); 0828 KIO::FileUndoManager::self()->recordCopyJob(job); 0829 } 0830 0831 void DolphinView::paste() 0832 { 0833 pasteToUrl(url()); 0834 } 0835 0836 void DolphinView::pasteIntoFolder() 0837 { 0838 const KFileItemList items = selectedItems(); 0839 if ((items.count() == 1) && items.first().isDir()) { 0840 pasteToUrl(items.first().url()); 0841 } 0842 } 0843 0844 void DolphinView::duplicateSelectedItems() 0845 { 0846 const KFileItemList itemList = selectedItems(); 0847 if (itemList.isEmpty()) { 0848 return; 0849 } 0850 0851 const QMimeDatabase db; 0852 0853 // Duplicate all selected items and append "copy" to the end of the file name 0854 // but before the filename extension, if present 0855 QList<QUrl> newSelection; 0856 for (const auto &item : itemList) { 0857 const QUrl originalURL = item.url(); 0858 const QString originalDirectoryPath = originalURL.adjusted(QUrl::RemoveFilename).path(); 0859 const QString originalFileName = item.name(); 0860 0861 QString extension = db.suffixForFileName(originalFileName); 0862 0863 QUrl duplicateURL = originalURL; 0864 0865 // No extension; new filename is "<oldfilename> copy" 0866 if (extension.isEmpty()) { 0867 duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFileName)); 0868 // There's an extension; new filename is "<oldfilename> copy.<extension>" 0869 } else { 0870 // Need to add a dot since QMimeDatabase::suffixForFileName() doesn't include it 0871 extension = QLatin1String(".") + extension; 0872 const QString originalFilenameWithoutExtension = originalFileName.chopped(extension.size()); 0873 // Preserve file's original filename extension in case the casing differs 0874 // from what QMimeDatabase::suffixForFileName() returned 0875 const QString originalExtension = originalFileName.right(extension.size()); 0876 duplicateURL.setPath(originalDirectoryPath + i18nc("<filename> copy", "%1 copy", originalFilenameWithoutExtension) + originalExtension); 0877 } 0878 0879 KIO::CopyJob *job = KIO::copyAs(originalURL, duplicateURL); 0880 KJobWidgets::setWindow(job, this); 0881 0882 if (job) { 0883 newSelection << duplicateURL; 0884 KIO::FileUndoManager::self()->recordCopyJob(job); 0885 } 0886 } 0887 0888 forceUrlsSelection(newSelection.first(), newSelection); 0889 } 0890 0891 void DolphinView::stopLoading() 0892 { 0893 m_model->cancelDirectoryLoading(); 0894 } 0895 0896 void DolphinView::updatePalette() 0897 { 0898 QColor color = KColorScheme(isActiveWindow() ? QPalette::Active : QPalette::Inactive, KColorScheme::View).background().color(); 0899 if (!m_active) { 0900 color.setAlpha(150); 0901 } 0902 0903 QWidget *viewport = m_container->viewport(); 0904 if (viewport) { 0905 QPalette palette; 0906 palette.setColor(viewport->backgroundRole(), color); 0907 viewport->setPalette(palette); 0908 } 0909 0910 update(); 0911 } 0912 0913 void DolphinView::abortTwoClicksRenaming() 0914 { 0915 m_twoClicksRenamingItemUrl.clear(); 0916 m_twoClicksRenamingTimer->stop(); 0917 } 0918 0919 bool DolphinView::eventFilter(QObject *watched, QEvent *event) 0920 { 0921 switch (event->type()) { 0922 case QEvent::PaletteChange: 0923 updatePalette(); 0924 QPixmapCache::clear(); 0925 break; 0926 0927 case QEvent::WindowActivate: 0928 case QEvent::WindowDeactivate: 0929 updatePalette(); 0930 break; 0931 0932 case QEvent::KeyPress: 0933 hideToolTip(ToolTipManager::HideBehavior::Instantly); 0934 if (GeneralSettings::useTabForSwitchingSplitView()) { 0935 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 0936 if (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() == Qt::NoModifier) { 0937 Q_EMIT toggleActiveViewRequested(); 0938 return true; 0939 } 0940 } 0941 break; 0942 case QEvent::KeyRelease: 0943 if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Control) { 0944 m_controlWheelAccumulatedDelta = 0; 0945 } 0946 break; 0947 case QEvent::FocusIn: 0948 if (watched == m_container) { 0949 setActive(true); 0950 } 0951 break; 0952 0953 case QEvent::GraphicsSceneDragEnter: 0954 if (watched == m_view) { 0955 m_dragging = true; 0956 abortTwoClicksRenaming(); 0957 } 0958 break; 0959 0960 case QEvent::GraphicsSceneDragLeave: 0961 if (watched == m_view) { 0962 m_dragging = false; 0963 } 0964 break; 0965 0966 case QEvent::GraphicsSceneDrop: 0967 if (watched == m_view) { 0968 m_dragging = false; 0969 } 0970 break; 0971 0972 case QEvent::ToolTip: { 0973 const auto helpEvent = static_cast<QHelpEvent *>(event); 0974 if (tryShowNameToolTip(helpEvent)) { 0975 return true; 0976 0977 } else if (m_hoveredColumnHeaderIndex) { 0978 const auto rolesInfo = KFileItemModel::rolesInformation(); 0979 const auto visibleRole = m_visibleRoles.value(*m_hoveredColumnHeaderIndex); 0980 0981 for (const KFileItemModel::RoleInfo &info : rolesInfo) { 0982 if (visibleRole == info.role) { 0983 QToolTip::showText(helpEvent->globalPos(), info.tooltip, this); 0984 return true; 0985 } 0986 } 0987 } 0988 break; 0989 } 0990 default: 0991 break; 0992 } 0993 0994 return QWidget::eventFilter(watched, event); 0995 } 0996 0997 void DolphinView::wheelEvent(QWheelEvent *event) 0998 { 0999 if (event->modifiers().testFlag(Qt::ControlModifier)) { 1000 m_controlWheelAccumulatedDelta += event->angleDelta().y(); 1001 1002 if (m_controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) { 1003 slotDecreaseZoom(); 1004 m_controlWheelAccumulatedDelta += QWheelEvent::DefaultDeltasPerStep; 1005 } else if (m_controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) { 1006 slotIncreaseZoom(); 1007 m_controlWheelAccumulatedDelta -= QWheelEvent::DefaultDeltasPerStep; 1008 } 1009 1010 event->accept(); 1011 } else { 1012 event->ignore(); 1013 } 1014 } 1015 1016 void DolphinView::hideEvent(QHideEvent *event) 1017 { 1018 hideToolTip(); 1019 QWidget::hideEvent(event); 1020 } 1021 1022 bool DolphinView::event(QEvent *event) 1023 { 1024 if (event->type() == QEvent::WindowDeactivate) { 1025 /* See Bug 297355 1026 * Dolphin leaves file preview tooltips open even when is not visible. 1027 * 1028 * Hide tool-tip when Dolphin loses focus. 1029 */ 1030 hideToolTip(); 1031 abortTwoClicksRenaming(); 1032 } 1033 1034 return QWidget::event(event); 1035 } 1036 1037 void DolphinView::activate() 1038 { 1039 setActive(true); 1040 } 1041 1042 void DolphinView::slotItemActivated(int index) 1043 { 1044 abortTwoClicksRenaming(); 1045 1046 const KFileItem item = m_model->fileItem(index); 1047 if (!item.isNull()) { 1048 Q_EMIT itemActivated(item); 1049 } 1050 } 1051 1052 void DolphinView::slotItemsActivated(const KItemSet &indexes) 1053 { 1054 Q_ASSERT(indexes.count() >= 2); 1055 1056 abortTwoClicksRenaming(); 1057 1058 const auto modifiers = QGuiApplication::keyboardModifiers(); 1059 1060 if (indexes.count() > 5) { 1061 QString question = i18np("Are you sure you want to open 1 item?", "Are you sure you want to open %1 items?", indexes.count()); 1062 const int answer = KMessageBox::warningContinueCancel( 1063 this, 1064 question, 1065 {}, 1066 KGuiItem(i18ncp("@action:button", "Open %1 Item", "Open %1 Items", indexes.count()), QStringLiteral("document-open")), 1067 KStandardGuiItem::cancel(), 1068 QStringLiteral("ConfirmOpenManyFolders")); 1069 if (answer != KMessageBox::PrimaryAction && answer != KMessageBox::Continue) { 1070 return; 1071 } 1072 } 1073 1074 KFileItemList items; 1075 items.reserve(indexes.count()); 1076 1077 for (int index : indexes) { 1078 KFileItem item = m_model->fileItem(index); 1079 const QUrl &url = openItemAsFolderUrl(item); 1080 1081 if (!url.isEmpty()) { 1082 // Open folders in new tabs or in new windows depending on the modifier 1083 // The ctrl+shift behavior is ignored because we are handling multiple items 1084 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked 1085 if (modifiers & Qt::ShiftModifier && !(modifiers & Qt::ControlModifier)) { 1086 Q_EMIT windowRequested(url); 1087 } else { 1088 Q_EMIT tabRequested(url); 1089 } 1090 } else { 1091 items.append(item); 1092 } 1093 } 1094 1095 if (items.count() == 1) { 1096 Q_EMIT itemActivated(items.first()); 1097 } else if (items.count() > 1) { 1098 Q_EMIT itemsActivated(items); 1099 } 1100 } 1101 1102 void DolphinView::slotItemMiddleClicked(int index) 1103 { 1104 const KFileItem &item = m_model->fileItem(index); 1105 const QUrl &url = openItemAsFolderUrl(item); 1106 const auto modifiers = QGuiApplication::keyboardModifiers(); 1107 if (!url.isEmpty()) { 1108 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked 1109 if (modifiers & Qt::ShiftModifier) { 1110 Q_EMIT activeTabRequested(url); 1111 } else { 1112 Q_EMIT tabRequested(url); 1113 } 1114 } else if (isTabsForFilesEnabled()) { 1115 // keep in sync with KUrlNavigator::slotNavigatorButtonClicked 1116 if (modifiers & Qt::ShiftModifier) { 1117 Q_EMIT activeTabRequested(item.url()); 1118 } else { 1119 Q_EMIT tabRequested(item.url()); 1120 } 1121 } else { 1122 Q_EMIT fileMiddleClickActivated(item); 1123 } 1124 } 1125 1126 void DolphinView::slotItemContextMenuRequested(int index, const QPointF &pos) 1127 { 1128 // Force emit of a selection changed signal before we request the 1129 // context menu, to update the edit-actions first. (See Bug 294013) 1130 if (m_selectionChangedTimer->isActive()) { 1131 emitSelectionChangedSignal(); 1132 } 1133 1134 const KFileItem item = m_model->fileItem(index); 1135 Q_EMIT requestContextMenu(pos.toPoint(), item, selectedItems(), url()); 1136 } 1137 1138 void DolphinView::slotViewContextMenuRequested(const QPointF &pos) 1139 { 1140 Q_EMIT requestContextMenu(pos.toPoint(), KFileItem(), selectedItems(), url()); 1141 } 1142 1143 void DolphinView::slotHeaderContextMenuRequested(const QPointF &pos) 1144 { 1145 ViewProperties props(viewPropertiesUrl()); 1146 1147 QPointer<QMenu> menu = new QMenu(this); 1148 1149 KItemListView *view = m_container->controller()->view(); 1150 const QList<QByteArray> visibleRolesSet = view->visibleRoles(); 1151 1152 bool indexingEnabled = false; 1153 #if HAVE_BALOO 1154 Baloo::IndexerConfig config; 1155 indexingEnabled = config.fileIndexingEnabled(); 1156 #endif 1157 1158 QString groupName; 1159 QMenu *groupMenu = nullptr; 1160 1161 // Add all roles to the menu that can be shown or hidden by the user 1162 const QList<KFileItemModel::RoleInfo> rolesInfo = KFileItemModel::rolesInformation(); 1163 for (const KFileItemModel::RoleInfo &info : rolesInfo) { 1164 if (info.role == "text") { 1165 // It should not be possible to hide the "text" role 1166 continue; 1167 } 1168 1169 const QString text = m_model->roleDescription(info.role); 1170 QAction *action = nullptr; 1171 if (info.group.isEmpty()) { 1172 action = menu->addAction(text); 1173 } else { 1174 if (!groupMenu || info.group != groupName) { 1175 groupName = info.group; 1176 groupMenu = menu->addMenu(groupName); 1177 } 1178 1179 action = groupMenu->addAction(text); 1180 } 1181 1182 action->setCheckable(true); 1183 action->setChecked(visibleRolesSet.contains(info.role)); 1184 action->setData(info.role); 1185 action->setToolTip(info.tooltip); 1186 1187 const bool enable = (!info.requiresBaloo && !info.requiresIndexer) || (info.requiresBaloo) || (info.requiresIndexer && indexingEnabled); 1188 action->setEnabled(enable); 1189 } 1190 1191 menu->addSeparator(); 1192 1193 QActionGroup *widthsGroup = new QActionGroup(menu); 1194 const bool autoColumnWidths = props.headerColumnWidths().isEmpty(); 1195 1196 QAction *toggleSidePaddingAction = menu->addAction(i18nc("@action:inmenu", "Side Padding")); 1197 toggleSidePaddingAction->setCheckable(true); 1198 toggleSidePaddingAction->setChecked(view->header()->sidePadding() > 0); 1199 1200 QAction *autoAdjustWidthsAction = menu->addAction(i18nc("@action:inmenu", "Automatic Column Widths")); 1201 autoAdjustWidthsAction->setCheckable(true); 1202 autoAdjustWidthsAction->setChecked(autoColumnWidths); 1203 autoAdjustWidthsAction->setActionGroup(widthsGroup); 1204 1205 QAction *customWidthsAction = menu->addAction(i18nc("@action:inmenu", "Custom Column Widths")); 1206 customWidthsAction->setCheckable(true); 1207 customWidthsAction->setChecked(!autoColumnWidths); 1208 customWidthsAction->setActionGroup(widthsGroup); 1209 1210 QAction *action = menu->exec(pos.toPoint()); 1211 if (menu && action) { 1212 KItemListHeader *header = view->header(); 1213 1214 if (action == autoAdjustWidthsAction) { 1215 // Clear the column-widths from the viewproperties and turn on 1216 // the automatic resizing of the columns 1217 props.setHeaderColumnWidths(QList<int>()); 1218 header->setAutomaticColumnResizing(true); 1219 } else if (action == customWidthsAction) { 1220 // Apply the current column-widths as custom column-widths and turn 1221 // off the automatic resizing of the columns 1222 QList<int> columnWidths; 1223 const auto visibleRoles = view->visibleRoles(); 1224 columnWidths.reserve(visibleRoles.count()); 1225 for (const QByteArray &role : visibleRoles) { 1226 columnWidths.append(header->columnWidth(role)); 1227 } 1228 props.setHeaderColumnWidths(columnWidths); 1229 header->setAutomaticColumnResizing(false); 1230 } else if (action == toggleSidePaddingAction) { 1231 header->setSidePadding(toggleSidePaddingAction->isChecked() ? 20 : 0); 1232 } else { 1233 // Show or hide the selected role 1234 const QByteArray selectedRole = action->data().toByteArray(); 1235 1236 QList<QByteArray> visibleRoles = view->visibleRoles(); 1237 if (action->isChecked()) { 1238 visibleRoles.append(selectedRole); 1239 } else { 1240 visibleRoles.removeOne(selectedRole); 1241 } 1242 1243 view->setVisibleRoles(visibleRoles); 1244 props.setVisibleRoles(visibleRoles); 1245 1246 QList<int> columnWidths; 1247 if (!header->automaticColumnResizing()) { 1248 const auto visibleRoles = view->visibleRoles(); 1249 columnWidths.reserve(visibleRoles.count()); 1250 for (const QByteArray &role : visibleRoles) { 1251 columnWidths.append(header->columnWidth(role)); 1252 } 1253 } 1254 props.setHeaderColumnWidths(columnWidths); 1255 } 1256 } 1257 1258 delete menu; 1259 } 1260 1261 void DolphinView::slotHeaderColumnWidthChangeFinished(const QByteArray &role, qreal current) 1262 { 1263 const QList<QByteArray> visibleRoles = m_view->visibleRoles(); 1264 1265 ViewProperties props(viewPropertiesUrl()); 1266 QList<int> columnWidths = props.headerColumnWidths(); 1267 if (columnWidths.count() != visibleRoles.count()) { 1268 columnWidths.clear(); 1269 columnWidths.reserve(visibleRoles.count()); 1270 const KItemListHeader *header = m_view->header(); 1271 for (const QByteArray &role : visibleRoles) { 1272 const int width = header->columnWidth(role); 1273 columnWidths.append(width); 1274 } 1275 } 1276 1277 const int roleIndex = visibleRoles.indexOf(role); 1278 Q_ASSERT(roleIndex >= 0 && roleIndex < columnWidths.count()); 1279 columnWidths[roleIndex] = current; 1280 1281 props.setHeaderColumnWidths(columnWidths); 1282 } 1283 1284 void DolphinView::slotSidePaddingWidthChanged(qreal width) 1285 { 1286 ViewProperties props(viewPropertiesUrl()); 1287 DetailsModeSettings::setSidePadding(int(width)); 1288 m_view->writeSettings(); 1289 } 1290 1291 void DolphinView::slotItemHovered(int index) 1292 { 1293 const KFileItem item = m_model->fileItem(index); 1294 1295 if (GeneralSettings::showToolTips() && !m_dragging) { 1296 QRectF itemRect = m_container->controller()->view()->itemContextRect(index); 1297 const QPoint pos = m_container->mapToGlobal(itemRect.topLeft().toPoint()); 1298 itemRect.moveTo(pos); 1299 1300 #if HAVE_BALOO 1301 auto nativeParent = nativeParentWidget(); 1302 if (nativeParent) { 1303 m_toolTipManager->showToolTip(item, itemRect, nativeParent->windowHandle()); 1304 } 1305 #endif 1306 } 1307 1308 Q_EMIT requestItemInfo(item); 1309 } 1310 1311 void DolphinView::slotItemUnhovered(int index) 1312 { 1313 Q_UNUSED(index) 1314 hideToolTip(); 1315 Q_EMIT requestItemInfo(KFileItem()); 1316 } 1317 1318 void DolphinView::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent *event) 1319 { 1320 QUrl destUrl; 1321 KFileItem destItem = m_model->fileItem(index); 1322 if (destItem.isNull() || (!destItem.isDir() && !destItem.isDesktopFile())) { 1323 // Use the URL of the view as drop target if the item is no directory 1324 // or desktop-file 1325 destItem = m_model->rootItem(); 1326 destUrl = url(); 1327 } else { 1328 // The item represents a directory or desktop-file 1329 destUrl = destItem.mostLocalUrl(); 1330 } 1331 1332 QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers()); 1333 dropUrls(destUrl, &dropEvent, this); 1334 1335 setActive(true); 1336 } 1337 1338 void DolphinView::dropUrls(const QUrl &destUrl, QDropEvent *dropEvent, QWidget *dropWidget) 1339 { 1340 KIO::DropJob *job = DragAndDropHelper::dropUrls(destUrl, dropEvent, dropWidget); 1341 1342 if (job) { 1343 connect(job, &KIO::DropJob::result, this, &DolphinView::slotJobResult); 1344 1345 if (destUrl == url()) { 1346 // Mark the dropped urls as selected. 1347 m_clearSelectionBeforeSelectingNewItems = true; 1348 m_markFirstNewlySelectedItemAsCurrent = true; 1349 m_selectJobCreatedItems = true; 1350 connect(job, &KIO::DropJob::itemCreated, this, &DolphinView::slotItemCreated); 1351 connect(job, &KIO::DropJob::copyJobStarted, this, [this](const KIO::CopyJob *copyJob) { 1352 connect(copyJob, &KIO::CopyJob::copying, this, &DolphinView::slotItemCreatedFromJob); 1353 connect(copyJob, &KIO::CopyJob::moving, this, &DolphinView::slotItemCreatedFromJob); 1354 connect(copyJob, &KIO::CopyJob::linking, this, [this](KIO::Job *job, const QString &src, const QUrl &dest) { 1355 Q_UNUSED(job) 1356 Q_UNUSED(src) 1357 slotItemCreated(dest); 1358 }); 1359 }); 1360 } 1361 } 1362 } 1363 1364 void DolphinView::slotModelChanged(KItemModelBase *current, KItemModelBase *previous) 1365 { 1366 if (previous != nullptr) { 1367 Q_ASSERT(qobject_cast<KFileItemModel *>(previous)); 1368 KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(previous); 1369 disconnect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); 1370 m_versionControlObserver->setModel(nullptr); 1371 } 1372 1373 if (current) { 1374 Q_ASSERT(qobject_cast<KFileItemModel *>(current)); 1375 KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(current); 1376 connect(fileItemModel, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::slotDirectoryLoadingCompleted); 1377 m_versionControlObserver->setModel(fileItemModel); 1378 } 1379 } 1380 1381 void DolphinView::slotMouseButtonPressed(int itemIndex, Qt::MouseButtons buttons) 1382 { 1383 Q_UNUSED(itemIndex) 1384 1385 hideToolTip(); 1386 1387 if (buttons & Qt::BackButton) { 1388 Q_EMIT goBackRequested(); 1389 } else if (buttons & Qt::ForwardButton) { 1390 Q_EMIT goForwardRequested(); 1391 } 1392 } 1393 1394 void DolphinView::slotSelectedItemTextPressed(int index) 1395 { 1396 if (GeneralSettings::renameInline() && !m_view->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick)) { 1397 const KFileItem item = m_model->fileItem(index); 1398 const KFileItemListProperties capabilities(KFileItemList() << item); 1399 if (capabilities.supportsMoving()) { 1400 m_twoClicksRenamingItemUrl = item.url(); 1401 m_twoClicksRenamingTimer->start(QApplication::doubleClickInterval()); 1402 } 1403 } 1404 } 1405 1406 void DolphinView::slotItemCreatedFromJob(KIO::Job *, const QUrl &, const QUrl &to) 1407 { 1408 slotItemCreated(to); 1409 } 1410 1411 void DolphinView::slotItemCreated(const QUrl &url) 1412 { 1413 if (m_markFirstNewlySelectedItemAsCurrent) { 1414 markUrlAsCurrent(url); 1415 m_markFirstNewlySelectedItemAsCurrent = false; 1416 } 1417 if (m_selectJobCreatedItems && !m_selectedUrls.contains(url)) { 1418 m_selectedUrls << url; 1419 } 1420 } 1421 1422 void DolphinView::onDirectoryLoadingCompletedAfterJob() 1423 { 1424 // the model should now contain all the items created by the job 1425 m_selectJobCreatedItems = true; // to make sure we overwrite selection 1426 // update the view: scroll into View and selection 1427 updateViewState(); 1428 m_selectJobCreatedItems = false; 1429 m_selectedUrls.clear(); 1430 } 1431 1432 void DolphinView::slotJobResult(KJob *job) 1433 { 1434 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) { 1435 Q_EMIT errorMessage(job->errorString()); 1436 } 1437 if (!m_selectJobCreatedItems) { 1438 m_selectedUrls.clear(); 1439 return; 1440 } 1441 if (!m_selectedUrls.isEmpty()) { 1442 m_selectedUrls = KDirModel::simplifiedUrlList(m_selectedUrls); 1443 1444 updateSelectionState(); 1445 if (!m_selectedUrls.isEmpty()) { 1446 // not all urls were found, the model may not be up to date 1447 connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &DolphinView::onDirectoryLoadingCompletedAfterJob, Qt::SingleShotConnection); 1448 } else { 1449 m_selectJobCreatedItems = false; 1450 m_selectedUrls.clear(); 1451 } 1452 } 1453 } 1454 1455 void DolphinView::slotSelectionChanged(const KItemSet ¤t, const KItemSet &previous) 1456 { 1457 m_selectNextItem = false; 1458 const int currentCount = current.count(); 1459 const int previousCount = previous.count(); 1460 const bool selectionStateChanged = (currentCount == 0 && previousCount > 0) || (currentCount > 0 && previousCount == 0); 1461 1462 // If nothing has been selected before and something got selected (or if something 1463 // was selected before and now nothing is selected) the selectionChangedSignal must 1464 // be emitted asynchronously as fast as possible to update the edit-actions. 1465 m_selectionChangedTimer->setInterval(selectionStateChanged ? 0 : 300); 1466 m_selectionChangedTimer->start(); 1467 } 1468 1469 void DolphinView::emitSelectionChangedSignal() 1470 { 1471 m_selectionChangedTimer->stop(); 1472 Q_EMIT selectionChanged(selectedItems()); 1473 } 1474 1475 void DolphinView::slotStatJobResult(KJob *job) 1476 { 1477 int folderCount = 0; 1478 int fileCount = 0; 1479 KIO::filesize_t totalFileSize = 0; 1480 bool countFileSize = true; 1481 1482 const auto entry = static_cast<KIO::StatJob *>(job)->statResult(); 1483 if (entry.contains(KIO::UDSEntry::UDS_RECURSIVE_SIZE)) { 1484 // We have a precomputed value. 1485 totalFileSize = static_cast<KIO::filesize_t>(entry.numberValue(KIO::UDSEntry::UDS_RECURSIVE_SIZE)); 1486 countFileSize = false; 1487 } 1488 1489 const int itemCount = m_model->count(); 1490 for (int i = 0; i < itemCount; ++i) { 1491 const KFileItem item = m_model->fileItem(i); 1492 if (item.isDir()) { 1493 ++folderCount; 1494 } else { 1495 ++fileCount; 1496 if (countFileSize) { 1497 totalFileSize += item.size(); 1498 } 1499 } 1500 } 1501 emitStatusBarText(folderCount, fileCount, totalFileSize, NoSelection); 1502 } 1503 1504 void DolphinView::updateSortRole(const QByteArray &role) 1505 { 1506 ViewProperties props(viewPropertiesUrl()); 1507 props.setSortRole(role); 1508 1509 KItemModelBase *model = m_container->controller()->model(); 1510 model->setSortRole(role); 1511 1512 Q_EMIT sortRoleChanged(role); 1513 } 1514 1515 void DolphinView::updateSortOrder(Qt::SortOrder order) 1516 { 1517 ViewProperties props(viewPropertiesUrl()); 1518 props.setSortOrder(order); 1519 1520 m_model->setSortOrder(order); 1521 1522 Q_EMIT sortOrderChanged(order); 1523 } 1524 1525 void DolphinView::updateSortFoldersFirst(bool foldersFirst) 1526 { 1527 ViewProperties props(viewPropertiesUrl()); 1528 props.setSortFoldersFirst(foldersFirst); 1529 1530 m_model->setSortDirectoriesFirst(foldersFirst); 1531 1532 Q_EMIT sortFoldersFirstChanged(foldersFirst); 1533 } 1534 1535 void DolphinView::updateSortHiddenLast(bool hiddenLast) 1536 { 1537 ViewProperties props(viewPropertiesUrl()); 1538 props.setSortHiddenLast(hiddenLast); 1539 1540 m_model->setSortHiddenLast(hiddenLast); 1541 1542 Q_EMIT sortHiddenLastChanged(hiddenLast); 1543 } 1544 1545 QPair<bool, QString> DolphinView::pasteInfo() const 1546 { 1547 const QMimeData *mimeData = QApplication::clipboard()->mimeData(); 1548 QPair<bool, QString> info; 1549 info.second = KIO::pasteActionText(mimeData, &info.first, rootItem()); 1550 return info; 1551 } 1552 1553 void DolphinView::setTabsForFilesEnabled(bool tabsForFiles) 1554 { 1555 m_tabsForFiles = tabsForFiles; 1556 } 1557 1558 bool DolphinView::isTabsForFilesEnabled() const 1559 { 1560 return m_tabsForFiles; 1561 } 1562 1563 bool DolphinView::itemsExpandable() const 1564 { 1565 return m_mode == DetailsView; 1566 } 1567 1568 bool DolphinView::isExpanded(const KFileItem &item) const 1569 { 1570 Q_ASSERT(item.isDir()); 1571 Q_ASSERT(items().contains(item)); 1572 if (!itemsExpandable()) { 1573 return false; 1574 } 1575 return m_model->isExpanded(m_model->index(item)); 1576 } 1577 1578 void DolphinView::restoreState(QDataStream &stream) 1579 { 1580 // Read the version number of the view state and check if the version is supported. 1581 quint32 version = 0; 1582 stream >> version; 1583 if (version != 1) { 1584 // The version of the view state isn't supported, we can't restore it. 1585 return; 1586 } 1587 1588 // Restore the current item that had the keyboard focus 1589 stream >> m_currentItemUrl; 1590 1591 // Restore the previously selected items 1592 stream >> m_selectedUrls; 1593 1594 // Restore the view position 1595 stream >> m_restoredContentsPosition; 1596 1597 // Restore expanded folders (only relevant for the details view - will be ignored by the view in other view modes) 1598 QSet<QUrl> urls; 1599 stream >> urls; 1600 m_model->restoreExpandedDirectories(urls); 1601 } 1602 1603 void DolphinView::saveState(QDataStream &stream) 1604 { 1605 stream << quint32(1); // View state version 1606 1607 // Save the current item that has the keyboard focus 1608 const int currentIndex = m_container->controller()->selectionManager()->currentItem(); 1609 if (currentIndex != -1) { 1610 KFileItem item = m_model->fileItem(currentIndex); 1611 Q_ASSERT(!item.isNull()); // If the current index is valid a item must exist 1612 QUrl currentItemUrl = item.url(); 1613 stream << currentItemUrl; 1614 } else { 1615 stream << QUrl(); 1616 } 1617 1618 // Save the selected urls 1619 stream << selectedItems().urlList(); 1620 1621 // Save view position 1622 const qreal x = m_container->horizontalScrollBar()->value(); 1623 const qreal y = m_container->verticalScrollBar()->value(); 1624 stream << QPoint(x, y); 1625 1626 // Save expanded folders (only relevant for the details view - the set will be empty in other view modes) 1627 stream << m_model->expandedDirectories(); 1628 } 1629 1630 KFileItem DolphinView::rootItem() const 1631 { 1632 return m_model->rootItem(); 1633 } 1634 1635 void DolphinView::setViewPropertiesContext(const QString &context) 1636 { 1637 m_viewPropertiesContext = context; 1638 } 1639 1640 QString DolphinView::viewPropertiesContext() const 1641 { 1642 return m_viewPropertiesContext; 1643 } 1644 1645 QUrl DolphinView::openItemAsFolderUrl(const KFileItem &item, const bool browseThroughArchives) 1646 { 1647 if (item.isNull()) { 1648 return QUrl(); 1649 } 1650 1651 QUrl url = item.targetUrl(); 1652 1653 if (item.isDir()) { 1654 return url; 1655 } 1656 1657 if (item.isMimeTypeKnown()) { 1658 const QString &mimetype = item.mimetype(); 1659 1660 if (browseThroughArchives && item.isFile() && url.isLocalFile()) { 1661 // Generic mechanism for redirecting to tar:/<path>/ when clicking on a tar file, 1662 // zip:/<path>/ when clicking on a zip file, etc. 1663 // The .protocol file specifies the mimetype that the kioslave handles. 1664 // Note that we don't use mimetype inheritance since we don't want to 1665 // open OpenDocument files as zip folders... 1666 const QString &protocol = KProtocolManager::protocolForArchiveMimetype(mimetype); 1667 if (!protocol.isEmpty()) { 1668 url.setScheme(protocol); 1669 return url; 1670 } 1671 } 1672 1673 if (mimetype == QLatin1String("application/x-desktop")) { 1674 // Redirect to the URL in Type=Link desktop files, unless it is a http(s) URL. 1675 KDesktopFile desktopFile(url.toLocalFile()); 1676 if (desktopFile.hasLinkType()) { 1677 const QString linkUrl = desktopFile.readUrl(); 1678 if (!linkUrl.startsWith(QLatin1String("http"))) { 1679 return QUrl::fromUserInput(linkUrl); 1680 } 1681 } 1682 } 1683 } 1684 1685 return QUrl(); 1686 } 1687 1688 void DolphinView::resetZoomLevel() 1689 { 1690 ViewModeSettings settings{m_mode}; 1691 settings.useDefaults(true); 1692 const int defaultIconSize = settings.iconSize(); 1693 settings.useDefaults(false); 1694 1695 setZoomLevel(ZoomLevelInfo::zoomLevelForIconSize(QSize(defaultIconSize, defaultIconSize))); 1696 } 1697 1698 void DolphinView::observeCreatedItem(const QUrl &url) 1699 { 1700 if (m_active) { 1701 forceUrlsSelection(url, {url}); 1702 } 1703 } 1704 1705 void DolphinView::slotDirectoryRedirection(const QUrl &oldUrl, const QUrl &newUrl) 1706 { 1707 if (oldUrl.matches(url(), QUrl::StripTrailingSlash)) { 1708 Q_EMIT redirection(oldUrl, newUrl); 1709 m_url = newUrl; // #186947 1710 } 1711 } 1712 1713 void DolphinView::updateSelectionState() 1714 { 1715 if (!m_selectedUrls.isEmpty()) { 1716 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 1717 1718 // if there is a selection already, leave it that way 1719 // unless some drop/paste job are in the process of creating items 1720 if (!selectionManager->hasSelection() || m_selectJobCreatedItems) { 1721 if (m_clearSelectionBeforeSelectingNewItems) { 1722 selectionManager->clearSelection(); 1723 m_clearSelectionBeforeSelectingNewItems = false; 1724 } 1725 1726 KItemSet selectedItems = selectionManager->selectedItems(); 1727 1728 QList<QUrl>::iterator it = m_selectedUrls.begin(); 1729 while (it != m_selectedUrls.end()) { 1730 const int index = m_model->index(*it); 1731 if (index >= 0) { 1732 selectedItems.insert(index); 1733 it = m_selectedUrls.erase(it); 1734 } else { 1735 ++it; 1736 } 1737 } 1738 1739 if (!selectedItems.isEmpty()) { 1740 selectionManager->beginAnchoredSelection(selectionManager->currentItem()); 1741 selectionManager->setSelectedItems(selectedItems); 1742 } 1743 } 1744 } 1745 } 1746 1747 void DolphinView::updateViewState() 1748 { 1749 if (m_currentItemUrl != QUrl()) { 1750 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 1751 1752 // if there is a selection already, leave it that way 1753 if (!selectionManager->hasSelection()) { 1754 const int currentIndex = m_model->index(m_currentItemUrl); 1755 if (currentIndex != -1) { 1756 selectionManager->setCurrentItem(currentIndex); 1757 1758 // scroll to current item and reset the state 1759 if (m_scrollToCurrentItem) { 1760 m_view->scrollToItem(currentIndex, KItemListView::ViewItemPosition::Middle); 1761 m_scrollToCurrentItem = false; 1762 } 1763 m_currentItemUrl = QUrl(); 1764 } else { 1765 selectionManager->setCurrentItem(0); 1766 } 1767 } else { 1768 m_currentItemUrl = QUrl(); 1769 } 1770 } 1771 1772 if (!m_restoredContentsPosition.isNull()) { 1773 const int x = m_restoredContentsPosition.x(); 1774 const int y = m_restoredContentsPosition.y(); 1775 m_restoredContentsPosition = QPoint(); 1776 1777 m_container->horizontalScrollBar()->setValue(x); 1778 m_container->verticalScrollBar()->setValue(y); 1779 } 1780 1781 updateSelectionState(); 1782 } 1783 1784 void DolphinView::hideToolTip(const ToolTipManager::HideBehavior behavior) 1785 { 1786 if (GeneralSettings::showToolTips()) { 1787 #if HAVE_BALOO 1788 m_toolTipManager->hideToolTip(behavior); 1789 #else 1790 Q_UNUSED(behavior) 1791 #endif 1792 } else if (m_mode == DolphinView::IconsView) { 1793 QToolTip::hideText(); 1794 } 1795 } 1796 1797 bool DolphinView::handleSpaceAsNormalKey() const 1798 { 1799 return !m_container->hasFocus() || m_container->controller()->isSearchAsYouTypeActive(); 1800 } 1801 1802 void DolphinView::slotTwoClicksRenamingTimerTimeout() 1803 { 1804 const KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 1805 1806 // verify that only one item is selected 1807 if (selectionManager->selectedItems().count() == 1) { 1808 const int index = selectionManager->currentItem(); 1809 const QUrl fileItemUrl = m_model->fileItem(index).url(); 1810 1811 // check if the selected item was the same item that started the twoClicksRenaming 1812 if (fileItemUrl.isValid() && m_twoClicksRenamingItemUrl == fileItemUrl) { 1813 renameSelectedItems(); 1814 } 1815 } 1816 } 1817 1818 void DolphinView::slotTrashFileFinished(KJob *job) 1819 { 1820 if (job->error() == 0) { 1821 selectNextItem(); // Fixes BUG: 419914 via selecting next item 1822 Q_EMIT operationCompletedMessage(i18nc("@info:status", "Trash operation completed.")); 1823 } else if (job->error() != KIO::ERR_USER_CANCELED) { 1824 Q_EMIT errorMessage(job->errorString()); 1825 } 1826 } 1827 1828 void DolphinView::slotDeleteFileFinished(KJob *job) 1829 { 1830 if (job->error() == 0) { 1831 selectNextItem(); // Fixes BUG: 419914 via selecting next item 1832 Q_EMIT operationCompletedMessage(i18nc("@info:status", "Delete operation completed.")); 1833 } else if (job->error() != KIO::ERR_USER_CANCELED) { 1834 Q_EMIT errorMessage(job->errorString()); 1835 } 1836 } 1837 1838 void DolphinView::selectNextItem() 1839 { 1840 if (m_active && m_selectNextItem) { 1841 KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 1842 if (selectedItems().isEmpty()) { 1843 Q_ASSERT_X(false, "DolphinView", "Selecting the next item failed."); 1844 return; 1845 } 1846 const auto lastSelectedIndex = m_model->index(selectedItems().last()); 1847 if (lastSelectedIndex < 0) { 1848 Q_ASSERT_X(false, "DolphinView", "Selecting the next item failed."); 1849 return; 1850 } 1851 auto nextItem = lastSelectedIndex + 1; 1852 if (nextItem >= itemsCount()) { 1853 nextItem = lastSelectedIndex - selectedItemsCount(); 1854 } 1855 if (nextItem >= 0) { 1856 selectionManager->setSelected(nextItem, 1); 1857 } 1858 m_selectNextItem = false; 1859 } 1860 } 1861 1862 void DolphinView::slotRenamingResult(KJob *job) 1863 { 1864 if (job->error()) { 1865 KIO::CopyJob *copyJob = qobject_cast<KIO::CopyJob *>(job); 1866 Q_ASSERT(copyJob); 1867 const QUrl newUrl = copyJob->destUrl(); 1868 const int index = m_model->index(newUrl); 1869 if (index >= 0) { 1870 QHash<QByteArray, QVariant> data; 1871 const QUrl oldUrl = copyJob->srcUrls().at(0); 1872 data.insert("text", oldUrl.fileName()); 1873 m_model->setData(index, data); 1874 } 1875 } 1876 } 1877 1878 void DolphinView::slotDirectoryLoadingStarted() 1879 { 1880 m_loadingState = LoadingState::Loading; 1881 updatePlaceholderLabel(); 1882 1883 // Disable the writestate temporary until it can be determined in a fast way 1884 // in DolphinView::slotDirectoryLoadingCompleted() 1885 if (m_isFolderWritable) { 1886 m_isFolderWritable = false; 1887 Q_EMIT writeStateChanged(m_isFolderWritable); 1888 } 1889 1890 Q_EMIT directoryLoadingStarted(); 1891 } 1892 1893 void DolphinView::slotDirectoryLoadingCompleted() 1894 { 1895 m_loadingState = LoadingState::Completed; 1896 1897 // Update the view-state. This has to be done asynchronously 1898 // because the view might not be in its final state yet. 1899 QTimer::singleShot(0, this, &DolphinView::updateViewState); 1900 1901 // Update the placeholder label in case we found that the folder was empty 1902 // after loading it 1903 1904 Q_EMIT directoryLoadingCompleted(); 1905 1906 updatePlaceholderLabel(); 1907 updateWritableState(); 1908 } 1909 1910 void DolphinView::slotDirectoryLoadingCanceled() 1911 { 1912 m_loadingState = LoadingState::Canceled; 1913 1914 updatePlaceholderLabel(); 1915 1916 Q_EMIT directoryLoadingCanceled(); 1917 } 1918 1919 void DolphinView::slotItemsChanged() 1920 { 1921 m_assureVisibleCurrentIndex = false; 1922 } 1923 1924 void DolphinView::slotSortOrderChangedByHeader(Qt::SortOrder current, Qt::SortOrder previous) 1925 { 1926 Q_UNUSED(previous) 1927 Q_ASSERT(m_model->sortOrder() == current); 1928 1929 ViewProperties props(viewPropertiesUrl()); 1930 props.setSortOrder(current); 1931 1932 Q_EMIT sortOrderChanged(current); 1933 } 1934 1935 void DolphinView::slotSortRoleChangedByHeader(const QByteArray ¤t, const QByteArray &previous) 1936 { 1937 Q_UNUSED(previous) 1938 Q_ASSERT(m_model->sortRole() == current); 1939 1940 ViewProperties props(viewPropertiesUrl()); 1941 props.setSortRole(current); 1942 1943 Q_EMIT sortRoleChanged(current); 1944 } 1945 1946 void DolphinView::slotVisibleRolesChangedByHeader(const QList<QByteArray> ¤t, const QList<QByteArray> &previous) 1947 { 1948 Q_UNUSED(previous) 1949 Q_ASSERT(m_container->controller()->view()->visibleRoles() == current); 1950 1951 const QList<QByteArray> previousVisibleRoles = m_visibleRoles; 1952 1953 m_visibleRoles = current; 1954 1955 ViewProperties props(viewPropertiesUrl()); 1956 props.setVisibleRoles(m_visibleRoles); 1957 1958 Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles); 1959 } 1960 1961 void DolphinView::slotRoleEditingCanceled() 1962 { 1963 disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); 1964 } 1965 1966 void DolphinView::slotRoleEditingFinished(int index, const QByteArray &role, const QVariant &value) 1967 { 1968 disconnect(m_view, &DolphinItemListView::roleEditingFinished, this, &DolphinView::slotRoleEditingFinished); 1969 1970 const KFileItemList items = selectedItems(); 1971 if (items.count() != 1) { 1972 return; 1973 } 1974 1975 if (role == "text") { 1976 const KFileItem oldItem = items.first(); 1977 const EditResult retVal = value.value<EditResult>(); 1978 const QString newName = retVal.newName; 1979 if (!newName.isEmpty() && newName != oldItem.text() && newName != QLatin1Char('.') && newName != QLatin1String("..")) { 1980 const QUrl oldUrl = oldItem.url(); 1981 1982 QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename); 1983 newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); 1984 1985 #ifndef Q_OS_WIN 1986 // Confirm hiding file/directory by renaming inline 1987 if (!hiddenFilesShown() && newName.startsWith(QLatin1Char('.')) && !oldItem.name().startsWith(QLatin1Char('.'))) { 1988 KGuiItem yesGuiItem(i18nc("@action:button", "Rename and Hide"), QStringLiteral("view-hidden")); 1989 1990 const auto code = 1991 KMessageBox::questionTwoActions(this, 1992 oldItem.isFile() ? i18n("Adding a dot to the beginning of this file's name will hide it from view.\n" 1993 "Do you still want to rename it?") 1994 : i18n("Adding a dot to the beginning of this folder's name will hide it from view.\n" 1995 "Do you still want to rename it?"), 1996 oldItem.isFile() ? i18n("Hide this File?") : i18n("Hide this Folder?"), 1997 yesGuiItem, 1998 KStandardGuiItem::cancel(), 1999 QStringLiteral("ConfirmHide")); 2000 2001 if (code == KMessageBox::SecondaryAction) { 2002 return; 2003 } 2004 } 2005 #endif 2006 2007 const bool newNameExistsAlready = (m_model->index(newUrl) >= 0); 2008 if (!newNameExistsAlready && m_model->index(oldUrl) == index) { 2009 // Only change the data in the model if no item with the new name 2010 // is in the model yet. If there is an item with the new name 2011 // already, calling KIO::CopyJob will open a dialog 2012 // asking for a new name, and KFileItemModel will update the 2013 // data when the dir lister signals that the file name has changed. 2014 QHash<QByteArray, QVariant> data; 2015 data.insert(role, retVal.newName); 2016 m_model->setData(index, data); 2017 } 2018 2019 KIO::Job *job = KIO::moveAs(oldUrl, newUrl); 2020 KJobWidgets::setWindow(job, this); 2021 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); 2022 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 2023 2024 if (!newNameExistsAlready) { 2025 forceUrlsSelection(newUrl, {newUrl}); 2026 2027 // Only connect the result signal if there is no item with the new name 2028 // in the model yet, see bug 328262. 2029 connect(job, &KJob::result, this, &DolphinView::slotRenamingResult); 2030 } 2031 } 2032 if (retVal.direction != EditDone) { 2033 const short indexShift = retVal.direction == EditNext ? 1 : -1; 2034 m_container->controller()->selectionManager()->setSelected(index, 1, KItemListSelectionManager::Deselect); 2035 m_container->controller()->selectionManager()->setSelected(index + indexShift, 1, KItemListSelectionManager::Select); 2036 renameSelectedItems(); 2037 } 2038 } 2039 } 2040 2041 void DolphinView::loadDirectory(const QUrl &url, bool reload) 2042 { 2043 if (!url.isValid()) { 2044 const QString location(url.toDisplayString(QUrl::PreferLocalFile)); 2045 if (location.isEmpty()) { 2046 Q_EMIT errorMessage(i18nc("@info:status", "The location is empty.")); 2047 } else { 2048 Q_EMIT errorMessage(i18nc("@info:status", "The location '%1' is invalid.", location)); 2049 } 2050 return; 2051 } 2052 2053 if (reload) { 2054 m_model->refreshDirectory(url); 2055 } else { 2056 m_model->loadDirectory(url); 2057 } 2058 } 2059 2060 void DolphinView::applyViewProperties() 2061 { 2062 const ViewProperties props(viewPropertiesUrl()); 2063 applyViewProperties(props); 2064 } 2065 2066 void DolphinView::applyViewProperties(const ViewProperties &props) 2067 { 2068 m_view->beginTransaction(); 2069 2070 const Mode mode = props.viewMode(); 2071 if (m_mode != mode) { 2072 const Mode previousMode = m_mode; 2073 m_mode = mode; 2074 2075 // Changing the mode might result in changing 2076 // the zoom level. Remember the old zoom level so 2077 // that zoomLevelChanged() can get emitted. 2078 const int oldZoomLevel = m_view->zoomLevel(); 2079 applyModeToView(); 2080 2081 Q_EMIT modeChanged(m_mode, previousMode); 2082 2083 if (m_view->zoomLevel() != oldZoomLevel) { 2084 Q_EMIT zoomLevelChanged(m_view->zoomLevel(), oldZoomLevel); 2085 } 2086 } 2087 2088 const bool hiddenFilesShown = props.hiddenFilesShown(); 2089 if (hiddenFilesShown != m_model->showHiddenFiles()) { 2090 m_model->setShowHiddenFiles(hiddenFilesShown); 2091 Q_EMIT hiddenFilesShownChanged(hiddenFilesShown); 2092 } 2093 2094 const bool groupedSorting = props.groupedSorting(); 2095 if (groupedSorting != m_model->groupedSorting()) { 2096 m_model->setGroupedSorting(groupedSorting); 2097 Q_EMIT groupedSortingChanged(groupedSorting); 2098 } 2099 2100 const QByteArray sortRole = props.sortRole(); 2101 if (sortRole != m_model->sortRole()) { 2102 m_model->setSortRole(sortRole); 2103 Q_EMIT sortRoleChanged(sortRole); 2104 } 2105 2106 const Qt::SortOrder sortOrder = props.sortOrder(); 2107 if (sortOrder != m_model->sortOrder()) { 2108 m_model->setSortOrder(sortOrder); 2109 Q_EMIT sortOrderChanged(sortOrder); 2110 } 2111 2112 const bool sortFoldersFirst = props.sortFoldersFirst(); 2113 if (sortFoldersFirst != m_model->sortDirectoriesFirst()) { 2114 m_model->setSortDirectoriesFirst(sortFoldersFirst); 2115 Q_EMIT sortFoldersFirstChanged(sortFoldersFirst); 2116 } 2117 2118 const bool sortHiddenLast = props.sortHiddenLast(); 2119 if (sortHiddenLast != m_model->sortHiddenLast()) { 2120 m_model->setSortHiddenLast(sortHiddenLast); 2121 Q_EMIT sortHiddenLastChanged(sortHiddenLast); 2122 } 2123 2124 const QList<QByteArray> visibleRoles = props.visibleRoles(); 2125 if (visibleRoles != m_visibleRoles) { 2126 const QList<QByteArray> previousVisibleRoles = m_visibleRoles; 2127 m_visibleRoles = visibleRoles; 2128 m_view->setVisibleRoles(visibleRoles); 2129 Q_EMIT visibleRolesChanged(m_visibleRoles, previousVisibleRoles); 2130 } 2131 2132 const bool previewsShown = props.previewsShown(); 2133 if (previewsShown != m_view->previewsShown()) { 2134 const int oldZoomLevel = zoomLevel(); 2135 2136 m_view->setPreviewsShown(previewsShown); 2137 Q_EMIT previewsShownChanged(previewsShown); 2138 2139 // Changing the preview-state might result in a changed zoom-level 2140 if (oldZoomLevel != zoomLevel()) { 2141 Q_EMIT zoomLevelChanged(zoomLevel(), oldZoomLevel); 2142 } 2143 } 2144 2145 KItemListView *itemListView = m_container->controller()->view(); 2146 if (itemListView->isHeaderVisible()) { 2147 KItemListHeader *header = itemListView->header(); 2148 const QList<int> headerColumnWidths = props.headerColumnWidths(); 2149 const int rolesCount = m_visibleRoles.count(); 2150 if (headerColumnWidths.count() == rolesCount) { 2151 header->setAutomaticColumnResizing(false); 2152 2153 QHash<QByteArray, qreal> columnWidths; 2154 for (int i = 0; i < rolesCount; ++i) { 2155 columnWidths.insert(m_visibleRoles[i], headerColumnWidths[i]); 2156 } 2157 header->setColumnWidths(columnWidths); 2158 } else { 2159 header->setAutomaticColumnResizing(true); 2160 } 2161 header->setSidePadding(DetailsModeSettings::sidePadding()); 2162 } 2163 2164 m_view->endTransaction(); 2165 } 2166 2167 void DolphinView::applyModeToView() 2168 { 2169 switch (m_mode) { 2170 case IconsView: 2171 m_view->setItemLayout(KFileItemListView::IconsLayout); 2172 break; 2173 case CompactView: 2174 m_view->setItemLayout(KFileItemListView::CompactLayout); 2175 break; 2176 case DetailsView: 2177 m_view->setItemLayout(KFileItemListView::DetailsLayout); 2178 break; 2179 default: 2180 Q_ASSERT(false); 2181 break; 2182 } 2183 } 2184 2185 void DolphinView::pasteToUrl(const QUrl &url) 2186 { 2187 KIO::PasteJob *job = KIO::paste(QApplication::clipboard()->mimeData(), url); 2188 KJobWidgets::setWindow(job, this); 2189 m_clearSelectionBeforeSelectingNewItems = true; 2190 m_markFirstNewlySelectedItemAsCurrent = true; 2191 m_selectJobCreatedItems = true; 2192 // TODO KF6 use KIO::PasteJob::copyJobStarted to hook to earlier events copying/moving 2193 connect(job, &KIO::PasteJob::itemCreated, this, &DolphinView::slotItemCreated); 2194 connect(job, &KIO::PasteJob::result, this, &DolphinView::slotJobResult); 2195 } 2196 2197 QList<QUrl> DolphinView::simplifiedSelectedUrls() const 2198 { 2199 QList<QUrl> urls; 2200 2201 const KFileItemList items = selectedItems(); 2202 urls.reserve(items.count()); 2203 for (const KFileItem &item : items) { 2204 urls.append(item.url()); 2205 } 2206 2207 if (itemsExpandable()) { 2208 // TODO: Check if we still need KDirModel for this in KDE 5.0 2209 urls = KDirModel::simplifiedUrlList(urls); 2210 } 2211 2212 return urls; 2213 } 2214 2215 QMimeData *DolphinView::selectionMimeData() const 2216 { 2217 const KItemListSelectionManager *selectionManager = m_container->controller()->selectionManager(); 2218 const KItemSet selectedIndexes = selectionManager->selectedItems(); 2219 2220 return m_model->createMimeData(selectedIndexes); 2221 } 2222 2223 void DolphinView::updateWritableState() 2224 { 2225 const bool wasFolderWritable = m_isFolderWritable; 2226 m_isFolderWritable = false; 2227 2228 KFileItem item = m_model->rootItem(); 2229 if (item.isNull()) { 2230 // Try to find out if the URL is writable even if the "root item" is 2231 // null, see https://bugs.kde.org/show_bug.cgi?id=330001 2232 item = KFileItem(url()); 2233 item.setDelayedMimeTypes(true); 2234 } 2235 2236 KFileItemListProperties capabilities(KFileItemList() << item); 2237 m_isFolderWritable = capabilities.supportsWriting(); 2238 2239 if (m_isFolderWritable != wasFolderWritable) { 2240 Q_EMIT writeStateChanged(m_isFolderWritable); 2241 } 2242 } 2243 2244 bool DolphinView::isFolderWritable() const 2245 { 2246 return m_isFolderWritable; 2247 } 2248 2249 QUrl DolphinView::viewPropertiesUrl() const 2250 { 2251 if (m_viewPropertiesContext.isEmpty()) { 2252 return m_url; 2253 } 2254 2255 QUrl url; 2256 url.setScheme(m_url.scheme()); 2257 url.setPath(m_viewPropertiesContext); 2258 return url; 2259 } 2260 2261 void DolphinView::slotRenameDialogRenamingFinished(const QList<QUrl> &urls) 2262 { 2263 forceUrlsSelection(urls.first(), urls); 2264 } 2265 2266 void DolphinView::forceUrlsSelection(const QUrl ¤t, const QList<QUrl> &selected) 2267 { 2268 clearSelection(); 2269 m_clearSelectionBeforeSelectingNewItems = true; 2270 markUrlAsCurrent(current); 2271 markUrlsAsSelected(selected); 2272 } 2273 2274 void DolphinView::copyPathToClipboard() 2275 { 2276 const KFileItemList list = selectedItems(); 2277 if (list.isEmpty()) { 2278 return; 2279 } 2280 const KFileItem &item = list.at(0); 2281 QString path = item.localPath(); 2282 if (path.isEmpty()) { 2283 path = item.url().toDisplayString(); 2284 } 2285 QClipboard *clipboard = QApplication::clipboard(); 2286 if (clipboard == nullptr) { 2287 return; 2288 } 2289 clipboard->setText(QDir::toNativeSeparators(path)); 2290 } 2291 2292 void DolphinView::slotIncreaseZoom() 2293 { 2294 setZoomLevel(zoomLevel() + 1); 2295 } 2296 2297 void DolphinView::slotDecreaseZoom() 2298 { 2299 setZoomLevel(zoomLevel() - 1); 2300 } 2301 2302 void DolphinView::slotSwipeUp() 2303 { 2304 Q_EMIT goUpRequested(); 2305 } 2306 2307 void DolphinView::showLoadingPlaceholder() 2308 { 2309 m_placeholderLabel->setText(i18n("Loading…")); 2310 m_placeholderLabel->setVisible(true); 2311 } 2312 2313 void DolphinView::updatePlaceholderLabel() 2314 { 2315 m_showLoadingPlaceholderTimer->stop(); 2316 if (itemsCount() > 0) { 2317 m_placeholderLabel->setVisible(false); 2318 return; 2319 } 2320 2321 if (m_loadingState == LoadingState::Loading) { 2322 m_placeholderLabel->setVisible(false); 2323 m_showLoadingPlaceholderTimer->start(); 2324 return; 2325 } 2326 2327 if (m_loadingState == LoadingState::Canceled) { 2328 m_placeholderLabel->setText(i18n("Loading canceled")); 2329 } else if (!nameFilter().isEmpty()) { 2330 m_placeholderLabel->setText(i18n("No items matching the filter")); 2331 } else if (m_url.scheme() == QLatin1String("baloosearch") || m_url.scheme() == QLatin1String("filenamesearch")) { 2332 m_placeholderLabel->setText(i18n("No items matching the search")); 2333 } else if (m_url.scheme() == QLatin1String("trash") && m_url.path() == QLatin1String("/")) { 2334 m_placeholderLabel->setText(i18n("Trash is empty")); 2335 } else if (m_url.scheme() == QLatin1String("tags")) { 2336 if (m_url.path() == QLatin1Char('/')) { 2337 m_placeholderLabel->setText(i18n("No tags")); 2338 } else { 2339 const QString tagName = m_url.path().mid(1); // Remove leading / 2340 m_placeholderLabel->setText(i18n("No files tagged with \"%1\"", tagName)); 2341 } 2342 2343 } else if (m_url.scheme() == QLatin1String("recentlyused")) { 2344 m_placeholderLabel->setText(i18n("No recently used items")); 2345 } else if (m_url.scheme() == QLatin1String("smb")) { 2346 m_placeholderLabel->setText(i18n("No shared folders found")); 2347 } else if (m_url.scheme() == QLatin1String("network")) { 2348 m_placeholderLabel->setText(i18n("No relevant network resources found")); 2349 } else if (m_url.scheme() == QLatin1String("mtp") && m_url.path() == QLatin1String("/")) { 2350 m_placeholderLabel->setText(i18n("No MTP-compatible devices found")); 2351 } else if (m_url.scheme() == QLatin1String("afc") && m_url.path() == QLatin1String("/")) { 2352 m_placeholderLabel->setText(i18n("No Apple devices found")); 2353 } else if (m_url.scheme() == QLatin1String("bluetooth")) { 2354 m_placeholderLabel->setText(i18n("No Bluetooth devices found")); 2355 } else { 2356 m_placeholderLabel->setText(i18n("Folder is empty")); 2357 } 2358 2359 m_placeholderLabel->setVisible(true); 2360 } 2361 2362 bool DolphinView::tryShowNameToolTip(QHelpEvent *event) 2363 { 2364 if (!GeneralSettings::showToolTips() && m_mode == DolphinView::IconsView) { 2365 const std::optional<int> index = m_view->itemAt(event->pos()); 2366 2367 if (!index.has_value()) { 2368 return false; 2369 } 2370 2371 // Check whether the filename has been elided 2372 const bool isElided = m_view->isElided(index.value()); 2373 2374 if (isElided) { 2375 const KFileItem item = m_model->fileItem(index.value()); 2376 const QString text = item.text(); 2377 const QPoint pos = mapToGlobal(event->pos()); 2378 QToolTip::showText(pos, text); 2379 return true; 2380 } 2381 } 2382 return false; 2383 } 2384 2385 #include "moc_dolphinview.cpp"