File indexing completed on 2024-05-12 05:47:42
0001 /* 0002 * SPDX-FileCopyrightText: 2008-2012 Peter Penz <peter.penz19@gmail.com> 0003 * SPDX-FileCopyrightText: 2021 Kai Uwe Broulik <kde@broulik.de> 0004 * 0005 * Based on KFilePlacesView from kdelibs: 0006 * SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org> 0007 * SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> 0008 * 0009 * SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "placespanel.h" 0013 0014 #include "dolphin_generalsettings.h" 0015 #include "dolphin_placespanelsettings.h" 0016 #include "dolphinplacesmodelsingleton.h" 0017 #include "settings/dolphinsettingsdialog.h" 0018 #include "views/draganddrophelper.h" 0019 0020 #include <KFilePlacesModel> 0021 #include <KIO/DropJob> 0022 #include <KIO/Job> 0023 #include <KLocalizedString> 0024 #include <KProtocolManager> 0025 0026 #include <QIcon> 0027 #include <QMenu> 0028 #include <QMimeData> 0029 #include <QShowEvent> 0030 0031 #include <Solid/StorageAccess> 0032 0033 PlacesPanel::PlacesPanel(QWidget *parent) 0034 : KFilePlacesView(parent) 0035 { 0036 setDropOnPlaceEnabled(true); 0037 connect(this, &PlacesPanel::urlsDropped, this, &PlacesPanel::slotUrlsDropped); 0038 0039 setAutoResizeItemsEnabled(false); 0040 0041 setTeardownFunction([this](const QModelIndex &index) { 0042 slotTearDownRequested(index); 0043 }); 0044 0045 m_openInSplitView = new QAction(QIcon::fromTheme(QStringLiteral("view-right-new")), i18nc("@action:inmenu", "Open in Split View")); 0046 m_openInSplitView->setPriority(QAction::HighPriority); 0047 connect(m_openInSplitView, &QAction::triggered, this, [this]() { 0048 const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl(); 0049 Q_EMIT openInSplitViewRequested(url); 0050 }); 0051 addAction(m_openInSplitView); 0052 0053 m_configureTrashAction = new QAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure Trash…")); 0054 m_configureTrashAction->setPriority(QAction::HighPriority); 0055 connect(m_configureTrashAction, &QAction::triggered, this, &PlacesPanel::slotConfigureTrash); 0056 addAction(m_configureTrashAction); 0057 0058 connect(this, &PlacesPanel::contextMenuAboutToShow, this, &PlacesPanel::slotContextMenuAboutToShow); 0059 0060 connect(this, &PlacesPanel::iconSizeChanged, this, [](const QSize &newSize) { 0061 int iconSize = qMin(newSize.width(), newSize.height()); 0062 if (iconSize == 0) { 0063 // Don't store 0 size, let's keep -1 for default/small/automatic 0064 iconSize = -1; 0065 } 0066 PlacesPanelSettings *settings = PlacesPanelSettings::self(); 0067 settings->setIconSize(iconSize); 0068 settings->save(); 0069 }); 0070 } 0071 0072 PlacesPanel::~PlacesPanel() = default; 0073 0074 void PlacesPanel::setUrl(const QUrl &url) 0075 { 0076 // KFilePlacesView::setUrl no-ops when no model is set but we only set it in showEvent() 0077 // Remember the URL and set it in showEvent 0078 m_url = url; 0079 KFilePlacesView::setUrl(url); 0080 } 0081 0082 QList<QAction *> PlacesPanel::customContextMenuActions() const 0083 { 0084 return m_customContextMenuActions; 0085 } 0086 0087 void PlacesPanel::setCustomContextMenuActions(const QList<QAction *> &actions) 0088 { 0089 m_customContextMenuActions = actions; 0090 } 0091 0092 void PlacesPanel::proceedWithTearDown() 0093 { 0094 if (m_indexToTearDown.isValid()) { 0095 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0096 placesModel->requestTeardown(m_indexToTearDown); 0097 } else { 0098 qWarning() << "Places entry to tear down is no longer valid"; 0099 } 0100 } 0101 0102 void PlacesPanel::readSettings() 0103 { 0104 if (GeneralSettings::autoExpandFolders()) { 0105 setDragAutoActivationDelay(750); 0106 } else { 0107 setDragAutoActivationDelay(0); 0108 } 0109 0110 const int iconSize = qMax(0, PlacesPanelSettings::iconSize()); 0111 setIconSize(QSize(iconSize, iconSize)); 0112 } 0113 0114 void PlacesPanel::showEvent(QShowEvent *event) 0115 { 0116 if (!event->spontaneous() && !model()) { 0117 readSettings(); 0118 0119 auto *placesModel = DolphinPlacesModelSingleton::instance().placesModel(); 0120 setModel(placesModel); 0121 0122 connect(placesModel, &KFilePlacesModel::errorMessage, this, &PlacesPanel::errorMessage); 0123 connect(placesModel, &KFilePlacesModel::teardownDone, this, &PlacesPanel::slotTearDownDone); 0124 0125 connect(placesModel, &QAbstractItemModel::rowsInserted, this, &PlacesPanel::slotRowsInserted); 0126 connect(placesModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &PlacesPanel::slotRowsAboutToBeRemoved); 0127 0128 for (int i = 0; i < model()->rowCount(); ++i) { 0129 connectDeviceSignals(model()->index(i, 0, QModelIndex())); 0130 } 0131 0132 setUrl(m_url); 0133 } 0134 0135 KFilePlacesView::showEvent(event); 0136 } 0137 0138 static bool isInternalDrag(const QMimeData *mimeData) 0139 { 0140 const auto formats = mimeData->formats(); 0141 for (const auto &format : formats) { 0142 // from KFilePlacesModel::_k_internalMimetype 0143 if (format.startsWith(QLatin1String("application/x-kfileplacesmodel-"))) { 0144 return true; 0145 } 0146 } 0147 return false; 0148 } 0149 0150 void PlacesPanel::dragMoveEvent(QDragMoveEvent *event) 0151 { 0152 const QModelIndex index = indexAt(event->position().toPoint()); 0153 if (index.isValid()) { 0154 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0155 0156 // Reject drag ontop of a non-writable protocol 0157 // We don't know whether we're dropping inbetween or ontop of a place 0158 // so still allow internal drag events so that re-arranging still works. 0159 const QUrl url = placesModel->url(index); 0160 if (url.isValid() && !isInternalDrag(event->mimeData()) && !KProtocolManager::supportsWriting(url)) { 0161 event->setDropAction(Qt::IgnoreAction); 0162 } 0163 } 0164 0165 KFilePlacesView::dragMoveEvent(event); 0166 } 0167 0168 void PlacesPanel::slotConfigureTrash() 0169 { 0170 const QUrl url = currentIndex().data(KFilePlacesModel::UrlRole).toUrl(); 0171 0172 DolphinSettingsDialog *settingsDialog = new DolphinSettingsDialog(url, this); 0173 settingsDialog->setCurrentPage(settingsDialog->trashSettings); 0174 settingsDialog->setAttribute(Qt::WA_DeleteOnClose); 0175 settingsDialog->show(); 0176 } 0177 0178 void PlacesPanel::slotUrlsDropped(const QUrl &dest, QDropEvent *event, QWidget *parent) 0179 { 0180 KIO::DropJob *job = DragAndDropHelper::dropUrls(dest, event, parent); 0181 if (job) { 0182 connect(job, &KIO::DropJob::result, this, [this](KJob *job) { 0183 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) { 0184 Q_EMIT errorMessage(job->errorString()); 0185 } 0186 }); 0187 } 0188 } 0189 0190 void PlacesPanel::slotContextMenuAboutToShow(const QModelIndex &index, QMenu *menu) 0191 { 0192 Q_UNUSED(menu); 0193 0194 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0195 const QUrl url = placesModel->url(index); 0196 const Solid::Device device = placesModel->deviceForIndex(index); 0197 0198 m_configureTrashAction->setVisible(url.scheme() == QLatin1String("trash")); 0199 m_openInSplitView->setVisible(url.isValid()); 0200 0201 // show customContextMenuActions only on the view's context menu 0202 if (!url.isValid() && !device.isValid()) { 0203 addActions(m_customContextMenuActions); 0204 } else { 0205 const auto actions = this->actions(); 0206 for (QAction *action : actions) { 0207 if (m_customContextMenuActions.contains(action)) { 0208 removeAction(action); 0209 } 0210 } 0211 } 0212 } 0213 0214 void PlacesPanel::slotTearDownRequested(const QModelIndex &index) 0215 { 0216 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0217 0218 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>(); 0219 if (!storageAccess) { 0220 return; 0221 } 0222 0223 m_indexToTearDown = QPersistentModelIndex(index); 0224 0225 // disconnect the Solid::StorageAccess::teardownRequested 0226 // to prevent emitting PlacesPanel::storageTearDownExternallyRequested 0227 // after we have emitted PlacesPanel::storageTearDownRequested 0228 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally); 0229 Q_EMIT storageTearDownRequested(storageAccess->filePath()); 0230 } 0231 0232 void PlacesPanel::slotTearDownRequestedExternally(const QString &udi) 0233 { 0234 Q_UNUSED(udi); 0235 auto *storageAccess = static_cast<Solid::StorageAccess *>(sender()); 0236 0237 Q_EMIT storageTearDownExternallyRequested(storageAccess->filePath()); 0238 } 0239 0240 void PlacesPanel::slotTearDownDone(const QModelIndex &index, Solid::ErrorType error, const QVariant &errorData) 0241 { 0242 Q_UNUSED(errorData); // All error handling is currently done in frameworks. 0243 0244 if (index == m_indexToTearDown) { 0245 if (error == Solid::ErrorType::NoError) { 0246 // No error; it must have been unmounted successfully 0247 Q_EMIT storageTearDownSuccessful(); 0248 } 0249 m_indexToTearDown = QPersistentModelIndex(); 0250 } 0251 } 0252 0253 void PlacesPanel::slotRowsInserted(const QModelIndex &parent, int first, int last) 0254 { 0255 for (int i = first; i <= last; ++i) { 0256 connectDeviceSignals(model()->index(first, 0, parent)); 0257 } 0258 } 0259 0260 void PlacesPanel::slotRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) 0261 { 0262 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0263 0264 for (int i = first; i <= last; ++i) { 0265 const QModelIndex index = placesModel->index(i, 0, parent); 0266 0267 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>(); 0268 if (!storageAccess) { 0269 continue; 0270 } 0271 0272 disconnect(storageAccess, &Solid::StorageAccess::teardownRequested, this, nullptr); 0273 } 0274 } 0275 0276 void PlacesPanel::connectDeviceSignals(const QModelIndex &index) 0277 { 0278 auto *placesModel = static_cast<KFilePlacesModel *>(model()); 0279 0280 Solid::StorageAccess *storageAccess = placesModel->deviceForIndex(index).as<Solid::StorageAccess>(); 0281 if (!storageAccess) { 0282 return; 0283 } 0284 0285 connect(storageAccess, &Solid::StorageAccess::teardownRequested, this, &PlacesPanel::slotTearDownRequestedExternally); 0286 } 0287 0288 #include "moc_placespanel.cpp"