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"