File indexing completed on 2024-05-12 05:47:41

0001 /*
0002  * SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz19@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "folderspanel.h"
0008 
0009 #include "dolphin_folderspanelsettings.h"
0010 #include "dolphin_generalsettings.h"
0011 #include "foldersitemlistwidget.h"
0012 #include "global.h"
0013 #include "kitemviews/kfileitemlistview.h"
0014 #include "kitemviews/kfileitemmodel.h"
0015 #include "kitemviews/kitemlistcontainer.h"
0016 #include "kitemviews/kitemlistcontroller.h"
0017 #include "kitemviews/kitemlistselectionmanager.h"
0018 #include "kitemviews/private/kitemlistroleeditor.h"
0019 #include "treeviewcontextmenu.h"
0020 #include "views/draganddrophelper.h"
0021 
0022 #include <KIO/CopyJob>
0023 #include <KIO/DropJob>
0024 #include <KIO/FileUndoManager>
0025 #include <KIO/RenameFileDialog>
0026 #include <KJobUiDelegate>
0027 #include <KJobWidgets>
0028 
0029 #include <QApplication>
0030 #include <QBoxLayout>
0031 #include <QGraphicsSceneDragDropEvent>
0032 #include <QGraphicsView>
0033 #include <QPropertyAnimation>
0034 #include <QTimer>
0035 
0036 FoldersPanel::FoldersPanel(QWidget *parent)
0037     : Panel(parent)
0038     , m_updateCurrentItem(false)
0039     , m_controller(nullptr)
0040     , m_model(nullptr)
0041 {
0042     setLayoutDirection(Qt::LeftToRight);
0043 }
0044 
0045 FoldersPanel::~FoldersPanel()
0046 {
0047     FoldersPanelSettings::self()->save();
0048 
0049     if (m_controller) {
0050         KItemListView *view = m_controller->view();
0051         m_controller->setView(nullptr);
0052         delete view;
0053     }
0054 }
0055 
0056 void FoldersPanel::setShowHiddenFiles(bool show)
0057 {
0058     FoldersPanelSettings::setHiddenFilesShown(show);
0059     m_model->setShowHiddenFiles(show);
0060 }
0061 
0062 bool FoldersPanel::showHiddenFiles() const
0063 {
0064     return FoldersPanelSettings::hiddenFilesShown();
0065 }
0066 
0067 void FoldersPanel::setLimitFoldersPanelToHome(bool enable)
0068 {
0069     FoldersPanelSettings::setLimitFoldersPanelToHome(enable);
0070     reloadTree();
0071 }
0072 
0073 bool FoldersPanel::limitFoldersPanelToHome() const
0074 {
0075     return FoldersPanelSettings::limitFoldersPanelToHome();
0076 }
0077 
0078 void FoldersPanel::setAutoScrolling(bool enable)
0079 {
0080     // TODO: Not supported yet in Dolphin 2.0
0081     FoldersPanelSettings::setAutoScrolling(enable);
0082 }
0083 
0084 bool FoldersPanel::autoScrolling() const
0085 {
0086     return FoldersPanelSettings::autoScrolling();
0087 }
0088 
0089 void FoldersPanel::rename(const KFileItem &item)
0090 {
0091     if (GeneralSettings::renameInline()) {
0092         const int index = m_model->index(item);
0093         m_controller->view()->editRole(index, "text");
0094     } else {
0095         KIO::RenameFileDialog *dialog = new KIO::RenameFileDialog(KFileItemList({item}), this);
0096         dialog->open();
0097     }
0098 }
0099 
0100 bool FoldersPanel::urlChanged()
0101 {
0102     if (!url().isValid() || url().scheme().contains(QLatin1String("search"))) {
0103         // Skip results shown by a search, as possible identical
0104         // directory names are useless without parent-path information.
0105         return false;
0106     }
0107 
0108     if (m_controller) {
0109         loadTree(url());
0110     }
0111 
0112     return true;
0113 }
0114 
0115 void FoldersPanel::reloadTree()
0116 {
0117     if (m_controller) {
0118         loadTree(url(), AllowJumpHome);
0119     }
0120 }
0121 
0122 void FoldersPanel::showEvent(QShowEvent *event)
0123 {
0124     if (event->spontaneous()) {
0125         Panel::showEvent(event);
0126         return;
0127     }
0128 
0129     if (!m_controller) {
0130         // Postpone the creating of the controller to the first show event.
0131         // This assures that no performance and memory overhead is given when the folders panel is not
0132         // used at all and stays invisible.
0133         KFileItemListView *view = new KFileItemListView();
0134         view->setWidgetCreator(new KItemListWidgetCreator<FoldersItemListWidget>());
0135         view->setSupportsItemExpanding(true);
0136         // Set the opacity to 0 initially. The opacity will be increased after the loading of the initial tree
0137         // has been finished in slotLoadingCompleted(). This prevents an unnecessary animation-mess when
0138         // opening the folders panel.
0139         view->setOpacity(0);
0140 
0141         connect(view, &KFileItemListView::roleEditingFinished, this, &FoldersPanel::slotRoleEditingFinished);
0142 
0143         m_model = new KFileItemModel(this);
0144         m_model->setShowDirectoriesOnly(true);
0145         m_model->setShowHiddenFiles(FoldersPanelSettings::hiddenFilesShown());
0146         // Use a QueuedConnection to give the view the possibility to react first on the
0147         // finished loading.
0148         connect(m_model, &KFileItemModel::directoryLoadingCompleted, this, &FoldersPanel::slotLoadingCompleted, Qt::QueuedConnection);
0149 
0150         m_controller = new KItemListController(m_model, view, this);
0151         m_controller->setSelectionBehavior(KItemListController::SingleSelection);
0152         m_controller->setAutoActivationBehavior(KItemListController::ExpansionOnly);
0153         m_controller->setMouseDoubleClickAction(KItemListController::ActivateAndExpandItem);
0154         m_controller->setAutoActivationDelay(750);
0155         m_controller->setSingleClickActivationEnforced(true);
0156 
0157         connect(m_controller, &KItemListController::itemActivated, this, &FoldersPanel::slotItemActivated);
0158         connect(m_controller, &KItemListController::itemMiddleClicked, this, &FoldersPanel::slotItemMiddleClicked);
0159         connect(m_controller, &KItemListController::itemContextMenuRequested, this, &FoldersPanel::slotItemContextMenuRequested);
0160         connect(m_controller, &KItemListController::viewContextMenuRequested, this, &FoldersPanel::slotViewContextMenuRequested);
0161         connect(m_controller, &KItemListController::itemDropEvent, this, &FoldersPanel::slotItemDropEvent);
0162 
0163         KItemListContainer *container = new KItemListContainer(m_controller, this);
0164 #ifndef QT_NO_ACCESSIBILITY
0165         view->setAccessibleParentsObject(container);
0166 #endif
0167         container->setEnabledFrame(false);
0168 
0169         QVBoxLayout *layout = new QVBoxLayout(this);
0170         layout->setContentsMargins(0, 0, 0, 0);
0171         layout->addWidget(container);
0172     }
0173 
0174     loadTree(url());
0175     Panel::showEvent(event);
0176 }
0177 
0178 void FoldersPanel::keyPressEvent(QKeyEvent *event)
0179 {
0180     const int key = event->key();
0181     if ((key == Qt::Key_Enter) || (key == Qt::Key_Return)) {
0182         event->accept();
0183     } else {
0184         Panel::keyPressEvent(event);
0185     }
0186 }
0187 
0188 void FoldersPanel::slotItemActivated(int index)
0189 {
0190     const KFileItem item = m_model->fileItem(index);
0191     if (!item.isNull()) {
0192         const auto modifiers = QGuiApplication::keyboardModifiers();
0193         // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
0194         if (modifiers & Qt::ControlModifier && modifiers & Qt::ShiftModifier) {
0195             Q_EMIT folderInNewActiveTab(item.url());
0196         } else if (modifiers & Qt::ControlModifier) {
0197             Q_EMIT folderInNewTab(item.url());
0198         } else if (modifiers & Qt::ShiftModifier) {
0199             // The shift modifier is not considered because it is used to expand the tree view without actually
0200             // opening the folder
0201             return;
0202         } else {
0203             Q_EMIT folderActivated(item.url());
0204         }
0205     }
0206 }
0207 
0208 void FoldersPanel::slotItemMiddleClicked(int index)
0209 {
0210     const KFileItem item = m_model->fileItem(index);
0211     if (!item.isNull()) {
0212         const auto modifiers = QGuiApplication::keyboardModifiers();
0213         // keep in sync with KUrlNavigator::slotNavigatorButtonClicked
0214         if (modifiers & Qt::ShiftModifier) {
0215             Q_EMIT folderInNewActiveTab(item.url());
0216         } else {
0217             Q_EMIT folderInNewTab(item.url());
0218         }
0219     }
0220 }
0221 
0222 void FoldersPanel::slotItemContextMenuRequested(int index, const QPointF &pos)
0223 {
0224     const KFileItem fileItem = m_model->fileItem(index);
0225 
0226     QPointer<TreeViewContextMenu> contextMenu = new TreeViewContextMenu(this, fileItem);
0227     contextMenu.data()->open(pos.toPoint());
0228     if (contextMenu.data()) {
0229         delete contextMenu.data();
0230     }
0231 }
0232 
0233 void FoldersPanel::slotViewContextMenuRequested(const QPointF &pos)
0234 {
0235     QPointer<TreeViewContextMenu> contextMenu = new TreeViewContextMenu(this, KFileItem());
0236     contextMenu.data()->open(pos.toPoint());
0237     if (contextMenu.data()) {
0238         delete contextMenu.data();
0239     }
0240 }
0241 
0242 void FoldersPanel::slotItemDropEvent(int index, QGraphicsSceneDragDropEvent *event)
0243 {
0244     if (index >= 0) {
0245         KFileItem destItem = m_model->fileItem(index);
0246         if (destItem.isNull()) {
0247             return;
0248         }
0249 
0250         QDropEvent dropEvent(event->pos().toPoint(), event->possibleActions(), event->mimeData(), event->buttons(), event->modifiers());
0251 
0252         KIO::DropJob *job = DragAndDropHelper::dropUrls(destItem.mostLocalUrl(), &dropEvent, this);
0253         if (job) {
0254             connect(job, &KIO::DropJob::result, this, [this](KJob *job) {
0255                 if (job->error() && job->error() != KIO::ERR_USER_CANCELED) {
0256                     Q_EMIT errorMessage(job->errorString());
0257                 }
0258             });
0259         }
0260     }
0261 }
0262 
0263 void FoldersPanel::slotRoleEditingFinished(int index, const QByteArray &role, const QVariant &value)
0264 {
0265     if (role == "text") {
0266         const KFileItem item = m_model->fileItem(index);
0267         const EditResult retVal = value.value<EditResult>();
0268         const QString newName = retVal.newName;
0269         if (!newName.isEmpty() && newName != item.text() && newName != QLatin1Char('.') && newName != QLatin1String("..")) {
0270             const QUrl oldUrl = item.url();
0271             QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
0272             newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
0273 
0274             KIO::Job *job = KIO::moveAs(oldUrl, newUrl);
0275             KJobWidgets::setWindow(job, this);
0276             KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job);
0277             job->uiDelegate()->setAutoErrorHandlingEnabled(true);
0278         }
0279     }
0280 }
0281 
0282 void FoldersPanel::slotLoadingCompleted()
0283 {
0284     if (m_controller->view()->opacity() == 0) {
0285         // The loading of the initial tree after opening the Folders panel
0286         // has been finished. Trigger the increasing of the opacity after
0287         // a short delay to give the view the chance to finish its internal
0288         // animations.
0289         // TODO: Check whether it makes sense to allow accessing the
0290         // view-internal delay for usecases like this.
0291         QTimer::singleShot(250, this, &FoldersPanel::startFadeInAnimation);
0292     }
0293 
0294     if (!m_updateCurrentItem) {
0295         return;
0296     }
0297 
0298     const int index = m_model->index(url());
0299     updateCurrentItem(index);
0300     m_updateCurrentItem = false;
0301 }
0302 
0303 void FoldersPanel::startFadeInAnimation()
0304 {
0305     QPropertyAnimation *anim = new QPropertyAnimation(m_controller->view(), "opacity", this);
0306     anim->setStartValue(0);
0307     anim->setEndValue(1);
0308     anim->setEasingCurve(QEasingCurve::InOutQuad);
0309     anim->start(QAbstractAnimation::DeleteWhenStopped);
0310     anim->setDuration(200);
0311 }
0312 
0313 void FoldersPanel::loadTree(const QUrl &url, FoldersPanel::NavigationBehaviour navigationBehaviour)
0314 {
0315     Q_ASSERT(m_controller);
0316 
0317     m_updateCurrentItem = false;
0318     bool jumpHome = false;
0319 
0320     QUrl baseUrl;
0321     if (!url.isLocalFile()) {
0322         // Clear the path for non-local URLs and use it as base
0323         baseUrl = url;
0324         baseUrl.setPath(QStringLiteral("/"));
0325     } else if (Dolphin::homeUrl().isParentOf(url) || (Dolphin::homeUrl() == url)) {
0326         if (FoldersPanelSettings::limitFoldersPanelToHome()) {
0327             baseUrl = Dolphin::homeUrl();
0328         } else {
0329             // Use the root directory as base for local URLs (#150941)
0330             baseUrl = QUrl::fromLocalFile(QDir::rootPath());
0331         }
0332     } else if (FoldersPanelSettings::limitFoldersPanelToHome() && navigationBehaviour == AllowJumpHome) {
0333         baseUrl = Dolphin::homeUrl();
0334         jumpHome = true;
0335     } else {
0336         // Use the root directory as base for local URLs (#150941)
0337         baseUrl = QUrl::fromLocalFile(QDir::rootPath());
0338     }
0339 
0340     if (m_model->directory() != baseUrl && !jumpHome) {
0341         m_updateCurrentItem = true;
0342         m_model->refreshDirectory(baseUrl);
0343     }
0344 
0345     const int index = m_model->index(url);
0346     if (jumpHome) {
0347         Q_EMIT folderActivated(baseUrl);
0348     } else if (index >= 0) {
0349         updateCurrentItem(index);
0350     } else if (url == baseUrl) {
0351         // clear the selection when visiting the base url
0352         updateCurrentItem(-1);
0353     } else {
0354         m_updateCurrentItem = true;
0355         m_model->expandParentDirectories(url);
0356 
0357         // slotLoadingCompleted() will be invoked after the model has
0358         // expanded the url
0359     }
0360 }
0361 
0362 void FoldersPanel::updateCurrentItem(int index)
0363 {
0364     KItemListSelectionManager *selectionManager = m_controller->selectionManager();
0365     selectionManager->setCurrentItem(index);
0366     selectionManager->clearSelection();
0367     selectionManager->setSelected(index);
0368 
0369     m_controller->view()->scrollToItem(index);
0370 }
0371 
0372 #include "moc_folderspanel.cpp"