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