File indexing completed on 2024-05-05 05:51:25

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2010 Thomas Fjellstrom <thomas@fjellstrom.ca>
0003    SPDX-FileCopyrightText: 2014 Joseph Wenninger <jowenn@kde.org>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 // BEGIN Includes
0009 #include "katefiletree.h"
0010 
0011 #include "filehistorywidget.h"
0012 #include "katefileactions.h"
0013 #include "katefiletreemodel.h"
0014 #include "katefiletreeproxymodel.h"
0015 
0016 #include <ktexteditor/application.h>
0017 #include <ktexteditor/document.h>
0018 #include <ktexteditor/editor.h>
0019 
0020 #include <KIO/CopyJob>
0021 #include <KIO/DeleteJob>
0022 #include <KIO/OpenFileManagerWindowJob>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <KStandardAction>
0026 #include <KTextEditor/MainWindow>
0027 
0028 #include <QActionGroup>
0029 #include <QApplication>
0030 #include <QClipboard>
0031 #include <QContextMenuEvent>
0032 #include <QDir>
0033 #include <QHeaderView>
0034 #include <QInputDialog>
0035 #include <QLineEdit>
0036 #include <QMenu>
0037 #include <QMimeDatabase>
0038 #include <QStyledItemDelegate>
0039 // END Includes
0040 
0041 namespace
0042 {
0043 KTextEditor::Document *docFromIndex(const QModelIndex &index)
0044 {
0045     return index.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
0046 }
0047 
0048 QList<KTextEditor::Document *> docTreeFromIndex(const QModelIndex &index)
0049 {
0050     return index.data(KateFileTreeModel::DocumentTreeRole).value<QList<KTextEditor::Document *>>();
0051 }
0052 
0053 bool closeDocs(const QList<KTextEditor::Document *> &docs)
0054 {
0055     return KTextEditor::Editor::instance()->application()->closeDocuments(docs);
0056 }
0057 
0058 class CloseIconStyleDelegate : public QStyledItemDelegate
0059 {
0060 public:
0061     explicit CloseIconStyleDelegate(QObject *parent = nullptr)
0062         : QStyledItemDelegate(parent)
0063     {
0064     }
0065 
0066     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0067     {
0068         QStyledItemDelegate::paint(painter, option, index);
0069 
0070         if (!m_showCloseBtn) {
0071             return;
0072         }
0073 
0074         if (index.column() == 1 && option.state & QStyle::State_Enabled && option.state & QStyle::State_MouseOver) {
0075             const QIcon icon = QIcon::fromTheme(QStringLiteral("tab-close"));
0076             const int w = option.decorationSize.width();
0077             QRect iconRect(option.rect.right() - w, option.rect.top(), w, option.rect.height());
0078             icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter);
0079         }
0080     }
0081 
0082     void setShowCloseButton(bool s)
0083     {
0084         m_showCloseBtn = s;
0085     }
0086 
0087 private:
0088     bool m_showCloseBtn = false;
0089 };
0090 } // namespace
0091 
0092 // BEGIN KateFileTree
0093 
0094 KateFileTree::KateFileTree(KTextEditor::MainWindow *mainWindow, QWidget *parent)
0095     : QTreeView(parent)
0096     , m_mainWindow(mainWindow)
0097 {
0098     setIndentation(12);
0099     setAllColumnsShowFocus(true);
0100     setFocusPolicy(Qt::NoFocus);
0101     setSelectionBehavior(QAbstractItemView::SelectRows);
0102     // for hover close button
0103     viewport()->setAttribute(Qt::WA_Hover);
0104 
0105     // DND
0106     setDefaultDropAction(Qt::MoveAction);
0107     setDragDropMode(QAbstractItemView::InternalMove);
0108     setDragDropOverwriteMode(false);
0109     setAcceptDrops(true);
0110     setDropIndicatorShown(true);
0111     setDragEnabled(true);
0112     setUniformRowHeights(true);
0113 
0114     setItemDelegate(new CloseIconStyleDelegate(this));
0115 
0116     // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows)
0117     connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked);
0118     connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked);
0119 
0120     m_filelistReloadDocument = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reloa&d"), this);
0121     connect(m_filelistReloadDocument, &QAction::triggered, this, &KateFileTree::slotDocumentReload);
0122     m_filelistReloadDocument->setWhatsThis(i18n("Reload selected document(s) from disk."));
0123 
0124     m_filelistCloseDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this);
0125     connect(m_filelistCloseDocument, &QAction::triggered, this, &KateFileTree::slotDocumentClose);
0126     m_filelistCloseDocument->setWhatsThis(i18n("Close the current document."));
0127 
0128     m_filelistExpandRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Expand Recursively"), this);
0129     connect(m_filelistExpandRecursive, &QAction::triggered, this, &KateFileTree::slotExpandRecursive);
0130     m_filelistExpandRecursive->setWhatsThis(i18n("Expand the file list sub tree recursively."));
0131 
0132     m_filelistCollapseRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Collapse Recursively"), this);
0133     connect(m_filelistCollapseRecursive, &QAction::triggered, this, &KateFileTree::slotCollapseRecursive);
0134     m_filelistCollapseRecursive->setWhatsThis(i18n("Collapse the file list sub tree recursively."));
0135 
0136     m_filelistCloseOtherDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close Other"), this);
0137     connect(m_filelistCloseOtherDocument, &QAction::triggered, this, &KateFileTree::slotDocumentCloseOther);
0138     m_filelistCloseOtherDocument->setWhatsThis(i18n("Close other documents in this folder."));
0139 
0140     m_filelistOpenContainingFolder =
0141         new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Containing Folder"), this);
0142     connect(m_filelistOpenContainingFolder, &QAction::triggered, this, &KateFileTree::slotOpenContainingFolder);
0143     m_filelistOpenContainingFolder->setWhatsThis(i18n("Open the folder this file is located in."));
0144 
0145     m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy-path")), i18nc("@action:inmenu", "Copy Location"), this);
0146     connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename);
0147     m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard."));
0148 
0149     m_filelistRenameFile = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename..."), this);
0150     connect(m_filelistRenameFile, &QAction::triggered, this, &KateFileTree::slotRenameFile);
0151     m_filelistRenameFile->setWhatsThis(i18n("Rename the selected file."));
0152 
0153     m_filelistPrintDocument = KStandardAction::print(this, &KateFileTree::slotPrintDocument, this);
0154     m_filelistPrintDocument->setWhatsThis(i18n("Print selected document."));
0155 
0156     m_filelistPrintDocumentPreview = KStandardAction::printPreview(this, &KateFileTree::slotPrintDocumentPreview, this);
0157     m_filelistPrintDocumentPreview->setWhatsThis(i18n("Show print preview of current document"));
0158 
0159     m_filelistDeleteDocument = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this);
0160     connect(m_filelistDeleteDocument, &QAction::triggered, this, &KateFileTree::slotDocumentDelete);
0161     m_filelistDeleteDocument->setWhatsThis(i18n("Close and delete selected file from storage."));
0162 
0163     setupContextMenuActionGroups();
0164 
0165     m_resetHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18nc("@action:inmenu", "Clear History"), this);
0166     connect(m_resetHistory, &QAction::triggered, this, &KateFileTree::slotResetHistory);
0167     m_resetHistory->setWhatsThis(i18n("Clear edit/view history."));
0168 
0169     QPalette p = palette();
0170     p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight));
0171     p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText));
0172     setPalette(p);
0173 }
0174 
0175 KateFileTree::~KateFileTree() = default;
0176 
0177 void KateFileTree::setModel(QAbstractItemModel *model)
0178 {
0179     m_proxyModel = static_cast<KateFileTreeProxyModel *>(model);
0180     Q_ASSERT(m_proxyModel); // we don't really work with anything else
0181     QTreeView::setModel(model);
0182     m_sourceModel = static_cast<KateFileTreeModel *>(m_proxyModel->sourceModel());
0183 
0184     header()->hide();
0185     header()->setStretchLastSection(false);
0186     header()->setSectionResizeMode(0, QHeaderView::Stretch);
0187 
0188     const int minSize = m_hasCloseButton ? 16 : 1;
0189     header()->setMinimumSectionSize(minSize);
0190     header()->setSectionResizeMode(1, QHeaderView::Fixed);
0191     header()->resizeSection(1, minSize);
0192 
0193     // proxy never emits rowsMoved
0194     connect(m_proxyModel->sourceModel(), &QAbstractItemModel::rowsMoved, this, &KateFileTree::onRowsMoved);
0195 }
0196 
0197 void KateFileTree::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &destination, int row)
0198 {
0199     QModelIndex movedIndex = m_proxyModel->mapFromSource(m_sourceModel->index(row, 0, destination));
0200     // We moved stuff, make sure if child was expanded, we expand all parents too.
0201     if (movedIndex.isValid() && isExpanded(movedIndex) && !isExpanded(movedIndex.parent())) {
0202         QModelIndex movedParent = movedIndex.parent();
0203         while (movedParent.isValid() && !isExpanded(movedParent)) {
0204             expand(movedParent);
0205             movedParent = movedParent.parent();
0206         }
0207     }
0208 }
0209 
0210 void KateFileTree::setShowCloseButton(bool show)
0211 {
0212     m_hasCloseButton = show;
0213     static_cast<CloseIconStyleDelegate *>(itemDelegate())->setShowCloseButton(show);
0214 
0215     if (!header())
0216         return;
0217 
0218     const int minSize = show ? 16 : 1;
0219     header()->setMinimumSectionSize(minSize);
0220     header()->resizeSection(1, minSize);
0221     header()->viewport()->update();
0222 }
0223 
0224 void KateFileTree::setMiddleClickToClose(bool value)
0225 {
0226     m_middleClickToClose = value;
0227     if (value) {
0228         viewport()->installEventFilter(this);
0229     } else {
0230         viewport()->removeEventFilter(this);
0231     }
0232 }
0233 
0234 void KateFileTree::setupContextMenuActionGroups()
0235 {
0236     QActionGroup *modeGroup = new QActionGroup(this);
0237 
0238     m_treeModeAction = setupOption(modeGroup,
0239                                    QIcon::fromTheme(QStringLiteral("view-list-tree")),
0240                                    i18nc("@action:inmenu", "Tree Mode"),
0241                                    i18n("Set view style to Tree Mode"),
0242                                    &KateFileTree::slotTreeMode,
0243                                    Qt::Checked);
0244 
0245     m_listModeAction = setupOption(modeGroup,
0246                                    QIcon::fromTheme(QStringLiteral("view-list-text")),
0247                                    i18nc("@action:inmenu", "List Mode"),
0248                                    i18n("Set view style to List Mode"),
0249                                    &KateFileTree::slotListMode);
0250 
0251     QActionGroup *sortGroup = new QActionGroup(this);
0252 
0253     m_sortByFile = setupOption(sortGroup,
0254                                QIcon(),
0255                                i18nc("@action:inmenu sorting option", "Document Name"),
0256                                i18n("Sort by Document Name"),
0257                                &KateFileTree::slotSortName,
0258                                Qt::Checked);
0259 
0260     m_sortByPath =
0261         setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), &KateFileTree::slotSortPath);
0262 
0263     m_sortByOpeningOrder = setupOption(sortGroup,
0264                                        QIcon(),
0265                                        i18nc("@action:inmenu sorting option", "Opening Order"),
0266                                        i18n("Sort by Opening Order"),
0267                                        &KateFileTree::slotSortOpeningOrder);
0268 
0269     m_customSorting = new QAction(QIcon(), i18n("Custom Sorting"), this);
0270     m_customSorting->setCheckable(true);
0271     m_customSorting->setActionGroup(sortGroup);
0272     connect(m_customSorting, &QAction::triggered, this, [this] {
0273         Q_EMIT sortRoleChanged(CustomSorting);
0274     });
0275 }
0276 
0277 QAction *KateFileTree::setupOption(QActionGroup *group,
0278                                    const QIcon &icon,
0279                                    const QString &text,
0280                                    const QString &whatsThis,
0281                                    const Func &slot,
0282                                    Qt::CheckState checked /* = Qt::Unchecked */)
0283 {
0284     QAction *new_action = new QAction(icon, text, this);
0285     new_action->setWhatsThis(whatsThis);
0286     new_action->setActionGroup(group);
0287     new_action->setCheckable(true);
0288     new_action->setChecked(checked == Qt::Checked);
0289     connect(new_action, &QAction::triggered, this, slot);
0290     return new_action;
0291 }
0292 
0293 void KateFileTree::slotListMode()
0294 {
0295     Q_EMIT viewModeChanged(true);
0296 }
0297 
0298 void KateFileTree::slotTreeMode()
0299 {
0300     Q_EMIT viewModeChanged(false);
0301 }
0302 
0303 void KateFileTree::slotSortName()
0304 {
0305     Q_EMIT sortRoleChanged(Qt::DisplayRole);
0306 }
0307 
0308 void KateFileTree::slotSortPath()
0309 {
0310     Q_EMIT sortRoleChanged(KateFileTreeModel::PathRole);
0311 }
0312 
0313 void KateFileTree::slotSortOpeningOrder()
0314 {
0315     Q_EMIT sortRoleChanged(KateFileTreeModel::OpeningOrderRole);
0316 }
0317 
0318 void KateFileTree::slotCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
0319 {
0320     Q_UNUSED(previous);
0321     if (!current.isValid()) {
0322         return;
0323     }
0324 
0325     KTextEditor::Document *doc = m_proxyModel->docFromIndex(current);
0326     if (doc) {
0327         m_previouslySelected = current;
0328     }
0329 }
0330 
0331 void KateFileTree::closeClicked(const QModelIndex &index)
0332 {
0333     if (m_proxyModel->isDir(index)) {
0334         const QList<KTextEditor::Document *> list = m_proxyModel->docTreeFromIndex(index);
0335         closeDocs(list);
0336         return;
0337     } else if (m_proxyModel->isWidgetDir(index)) {
0338         const auto idx = index.siblingAtColumn(0);
0339         const auto count = m_proxyModel->rowCount(idx);
0340         QWidgetList widgets;
0341         widgets.reserve(count);
0342         for (int i = 0; i < count; ++i) {
0343             widgets << m_proxyModel->index(i, 0, idx).data(KateFileTreeModel::WidgetRole).value<QWidget *>();
0344         }
0345 
0346         for (const auto &w : widgets) {
0347             closeWidget(w);
0348         }
0349     }
0350 
0351     if (auto *doc = m_proxyModel->docFromIndex(index)) {
0352         closeDocs({doc});
0353     } else if (auto *w = index.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0354         Q_EMIT closeWidget(w);
0355     }
0356 }
0357 
0358 void KateFileTree::mouseClicked(const QModelIndex &index)
0359 {
0360     if (m_hasCloseButton && index.column() == 1) {
0361         closeClicked(index);
0362         return;
0363     }
0364 
0365     if (auto *doc = m_proxyModel->docFromIndex(index)) {
0366         Q_EMIT activateDocument(doc);
0367     } else if (auto *w = index.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0368         Q_EMIT activateWidget(w);
0369     }
0370 }
0371 
0372 void KateFileTree::contextMenuEvent(QContextMenuEvent *event)
0373 {
0374     m_indexContextMenu = indexAt(event->pos());
0375     if (m_indexContextMenu.isValid()) {
0376         selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0377     }
0378 
0379     const bool listMode = m_sourceModel->listMode();
0380     m_treeModeAction->setChecked(!listMode);
0381     m_listModeAction->setChecked(listMode);
0382 
0383     const int sortRole = m_proxyModel->sortRole();
0384     m_sortByFile->setChecked(sortRole == Qt::DisplayRole);
0385     m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole);
0386     m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole);
0387     m_customSorting->setChecked(sortRole == CustomSorting);
0388 
0389     KTextEditor::Document *doc = docFromIndex(m_indexContextMenu);
0390 
0391     bool isDir = m_proxyModel->isDir(m_indexContextMenu);
0392     bool isWidgetDir = m_proxyModel->isWidgetDir(m_indexContextMenu);
0393     bool isWidget = m_indexContextMenu.data(KateFileTreeModel::WidgetRole).value<QWidget *>() != nullptr;
0394 
0395     QMenu menu(this);
0396     if (doc) {
0397         if (doc->url().isValid()) {
0398             QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With"));
0399             openWithMenu->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
0400             connect(openWithMenu, &QMenu::aboutToShow, this, [this, openWithMenu]() {
0401                 slotFixOpenWithMenu(openWithMenu);
0402             });
0403             connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction);
0404 
0405             menu.addSeparator();
0406             menu.addAction(m_filelistCopyFilename);
0407             menu.addAction(m_filelistRenameFile);
0408             menu.addAction(m_filelistDeleteDocument);
0409             menu.addAction(m_filelistReloadDocument);
0410 
0411             if (doc->url().isLocalFile()) {
0412                 auto a = menu.addAction(i18n("Show File Git History"));
0413                 connect(a, &QAction::triggered, this, [doc] {
0414                     auto url = doc->url();
0415                     if (url.isValid() && url.isLocalFile()) {
0416                         FileHistory::showFileHistory(url.toLocalFile());
0417                     }
0418                 });
0419             }
0420 
0421             menu.addSeparator();
0422             menu.addAction(m_filelistOpenContainingFolder);
0423 
0424             menu.addSeparator();
0425             menu.addAction(m_filelistCloseDocument);
0426             menu.addAction(m_filelistCloseOtherDocument);
0427 
0428             menu.addSeparator();
0429             menu.addAction(m_filelistPrintDocument);
0430             menu.addAction(m_filelistPrintDocumentPreview);
0431         } else {
0432             // untitled documents
0433             menu.addAction(m_filelistCloseDocument);
0434 
0435             menu.addSeparator();
0436         }
0437     } else if (isDir || isWidgetDir || isWidget) {
0438         if (isDir) {
0439             menu.addAction(m_filelistReloadDocument);
0440         }
0441 
0442         menu.addSeparator();
0443         menu.addAction(m_filelistCloseDocument);
0444 
0445         menu.addSeparator();
0446         menu.addAction(m_filelistExpandRecursive);
0447         menu.addAction(m_filelistCollapseRecursive);
0448     }
0449 
0450     menu.addSeparator();
0451     QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode"));
0452     view_menu->addAction(m_treeModeAction);
0453     view_menu->addAction(m_listModeAction);
0454 
0455     QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By"));
0456     sort_menu->addAction(m_sortByFile);
0457     sort_menu->addAction(m_sortByPath);
0458     sort_menu->addAction(m_sortByOpeningOrder);
0459     sort_menu->addAction(m_customSorting);
0460 
0461     m_filelistCloseDocument->setEnabled(m_indexContextMenu.isValid());
0462 
0463     menu.addAction(m_resetHistory);
0464 
0465     menu.exec(viewport()->mapToGlobal(event->pos()));
0466 
0467     if (m_previouslySelected.isValid()) {
0468         selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0469     }
0470 
0471     event->accept();
0472 }
0473 
0474 bool KateFileTree::eventFilter(QObject *o, QEvent *e)
0475 {
0476     if (m_middleClickToClose && o == viewport() && e->type() == QEvent::MouseButtonRelease) {
0477         auto me = static_cast<QMouseEvent *>(e);
0478         if (me->button() == Qt::MiddleButton && me->modifiers() == Qt::NoModifier) {
0479             closeClicked(indexAt(me->pos()));
0480             return true;
0481         }
0482     }
0483 
0484     return QObject::eventFilter(o, e);
0485 }
0486 
0487 void KateFileTree::slotFixOpenWithMenu(QMenu *menu)
0488 {
0489     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0490     if (!doc) {
0491         return;
0492     }
0493 
0494     KateFileActions::prepareOpenWithMenu(doc->url(), menu);
0495 }
0496 
0497 void KateFileTree::slotOpenWithMenuAction(QAction *a)
0498 {
0499     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0500     if (!doc) {
0501         return;
0502     }
0503 
0504     KateFileActions::showOpenWithMenu(m_mainWindow->window(), doc->url(), a);
0505 }
0506 
0507 void KateFileTree::slotDocumentClose()
0508 {
0509     m_previouslySelected = QModelIndex();
0510     if (!m_indexContextMenu.isValid()) {
0511         return;
0512     }
0513     const auto closeColumnIndex = m_indexContextMenu.sibling(m_indexContextMenu.row(), 1);
0514     closeClicked(closeColumnIndex);
0515 }
0516 
0517 void KateFileTree::addChildrenTolist(const QModelIndex &index, QList<QPersistentModelIndex> *worklist)
0518 {
0519     const int count = m_proxyModel->rowCount(index);
0520     worklist->reserve(worklist->size() + count);
0521     for (int i = 0; i < count; ++i) {
0522         worklist->append(m_proxyModel->index(i, 0, index));
0523     }
0524 }
0525 
0526 void KateFileTree::slotExpandRecursive()
0527 {
0528     if (!m_indexContextMenu.isValid()) {
0529         return;
0530     }
0531 
0532     // Work list for DFS walk over sub tree
0533     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
0534 
0535     while (!worklist.isEmpty()) {
0536         QPersistentModelIndex index = worklist.takeLast();
0537 
0538         // Expand current item
0539         expand(index);
0540 
0541         // Append all children of current item
0542         addChildrenTolist(index, &worklist);
0543     }
0544 }
0545 
0546 void KateFileTree::slotCollapseRecursive()
0547 {
0548     if (!m_indexContextMenu.isValid()) {
0549         return;
0550     }
0551 
0552     // Work list for DFS walk over sub tree
0553     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
0554 
0555     while (!worklist.isEmpty()) {
0556         QPersistentModelIndex index = worklist.takeLast();
0557 
0558         // Expand current item
0559         collapse(index);
0560 
0561         // Prepend all children of current item
0562         addChildrenTolist(index, &worklist);
0563     }
0564 }
0565 
0566 void KateFileTree::slotDocumentCloseOther()
0567 {
0568     QList<KTextEditor::Document *> closingDocuments = m_proxyModel->docTreeFromIndex(m_indexContextMenu.parent());
0569     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0570     closingDocuments.removeOne(doc);
0571     closeDocs(closingDocuments);
0572 }
0573 
0574 void KateFileTree::slotDocumentReload()
0575 {
0576     const QList<KTextEditor::Document *> docs = docTreeFromIndex(m_indexContextMenu);
0577     for (auto *doc : docs) {
0578         doc->documentReload();
0579     }
0580 }
0581 
0582 void KateFileTree::slotOpenContainingFolder()
0583 {
0584     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0585 
0586     if (!doc) {
0587         return;
0588     }
0589 
0590     KateFileActions::openContainingFolder(doc);
0591 }
0592 
0593 void KateFileTree::slotCopyFilename()
0594 {
0595     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0596 
0597     if (!doc) {
0598         return;
0599     }
0600 
0601     KateFileActions::copyFilePathToClipboard(doc);
0602 }
0603 
0604 void KateFileTree::slotRenameFile()
0605 {
0606     KateFileActions::renameDocumentFile(this, m_proxyModel->docFromIndex(m_indexContextMenu));
0607 }
0608 
0609 void KateFileTree::slotDocumentFirst()
0610 {
0611     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(0, 0));
0612     if (doc) {
0613         Q_EMIT activateDocument(doc);
0614     }
0615 }
0616 
0617 void KateFileTree::slotDocumentLast()
0618 {
0619     int count = m_proxyModel->rowCount(m_proxyModel->parent(currentIndex()));
0620     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(count - 1, 0));
0621     if (doc) {
0622         Q_EMIT activateDocument(doc);
0623     }
0624 }
0625 
0626 void KateFileTree::slotDocumentPrev()
0627 {
0628     QModelIndex current_index = currentIndex();
0629     QModelIndex prev;
0630 
0631     // scan up the tree skipping any dir nodes
0632     while (current_index.isValid()) {
0633         if (current_index.row() > 0) {
0634             current_index = m_proxyModel->sibling(current_index.row() - 1, current_index.column(), current_index);
0635             if (!current_index.isValid()) {
0636                 break;
0637             }
0638 
0639             if (m_proxyModel->isDir(current_index)) {
0640                 // try and select the last child in this parent
0641                 int children = m_proxyModel->rowCount(current_index);
0642                 current_index = m_proxyModel->index(children - 1, 0, current_index);
0643                 if (m_proxyModel->isDir(current_index)) {
0644                     // since we're a dir, keep going
0645                     while (m_proxyModel->isDir(current_index)) {
0646                         children = m_proxyModel->rowCount(current_index);
0647                         current_index = m_proxyModel->index(children - 1, 0, current_index);
0648                     }
0649 
0650                     if (!m_proxyModel->isDir(current_index)) {
0651                         prev = current_index;
0652                         break;
0653                     }
0654 
0655                     continue;
0656                 } else {
0657                     // we're the previous file, set prev
0658                     prev = current_index;
0659                     break;
0660                 }
0661             } else { // found document item
0662                 prev = current_index;
0663                 break;
0664             }
0665         } else {
0666             // just select the parent, the logic above will handle the rest
0667             current_index = m_proxyModel->parent(current_index);
0668             if (!current_index.isValid()) {
0669                 // paste the root node here, try and wrap around
0670 
0671                 int children = m_proxyModel->rowCount(current_index);
0672                 QModelIndex last_index = m_proxyModel->index(children - 1, 0, current_index);
0673                 if (!last_index.isValid()) {
0674                     break;
0675                 }
0676 
0677                 if (m_proxyModel->isDir(last_index)) {
0678                     // last node is a dir, select last child row
0679                     int last_children = m_proxyModel->rowCount(last_index);
0680                     prev = m_proxyModel->index(last_children - 1, 0, last_index);
0681                     // bug here?
0682                     break;
0683                 } else {
0684                     // got last file node
0685                     prev = last_index;
0686                     break;
0687                 }
0688             }
0689         }
0690     }
0691 
0692     if (prev.isValid()) {
0693         if (auto *doc = m_proxyModel->docFromIndex(prev)) {
0694             Q_EMIT activateDocument(doc);
0695         } else if (auto *w = prev.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0696             Q_EMIT activateWidget(w);
0697         }
0698     }
0699 }
0700 
0701 void KateFileTree::slotDocumentNext()
0702 {
0703     QModelIndex current_index = currentIndex();
0704     int parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
0705     QModelIndex next;
0706 
0707     // scan down the tree skipping any dir nodes
0708     while (current_index.isValid()) {
0709         if (current_index.row() < parent_row_count - 1) {
0710             current_index = m_proxyModel->sibling(current_index.row() + 1, current_index.column(), current_index);
0711             if (!current_index.isValid()) {
0712                 break;
0713             }
0714 
0715             if (m_proxyModel->isDir(current_index)) {
0716                 // we have a dir node
0717                 while (m_proxyModel->isDir(current_index)) {
0718                     current_index = m_proxyModel->index(0, 0, current_index);
0719                 }
0720 
0721                 parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
0722 
0723                 if (!m_proxyModel->isDir(current_index)) {
0724                     next = current_index;
0725                     break;
0726                 }
0727             } else { // found document item
0728                 next = current_index;
0729                 break;
0730             }
0731         } else {
0732             // select the parent's next sibling
0733             QModelIndex parent_index = m_proxyModel->parent(current_index);
0734             int grandparent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(parent_index));
0735 
0736             current_index = parent_index;
0737             parent_row_count = grandparent_row_count;
0738 
0739             // at least if we're not past the last node
0740             if (!current_index.isValid()) {
0741                 // paste the root node here, try and wrap around
0742                 QModelIndex last_index = m_proxyModel->index(0, 0, QModelIndex());
0743                 if (!last_index.isValid()) {
0744                     break;
0745                 }
0746 
0747                 if (m_proxyModel->isDir(last_index)) {
0748                     // last node is a dir, select first child row
0749                     while (m_proxyModel->isDir(last_index)) {
0750                         if (m_proxyModel->rowCount(last_index)) {
0751                             // has children, select first
0752                             last_index = m_proxyModel->index(0, 0, last_index);
0753                         }
0754                     }
0755 
0756                     next = last_index;
0757                     break;
0758                 } else {
0759                     // got first file node
0760                     next = last_index;
0761                     break;
0762                 }
0763             }
0764         }
0765     }
0766 
0767     if (next.isValid()) {
0768         if (auto *doc = m_proxyModel->docFromIndex(next)) {
0769             Q_EMIT activateDocument(doc);
0770         } else if (auto *w = next.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0771             Q_EMIT activateWidget(w);
0772         }
0773     }
0774 }
0775 
0776 void KateFileTree::slotPrintDocument()
0777 {
0778     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0779 
0780     if (!doc) {
0781         return;
0782     }
0783 
0784     doc->print();
0785 }
0786 
0787 void KateFileTree::slotPrintDocumentPreview()
0788 {
0789     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0790 
0791     if (!doc) {
0792         return;
0793     }
0794 
0795     doc->printPreview();
0796 }
0797 
0798 void KateFileTree::slotResetHistory()
0799 {
0800     m_sourceModel->resetHistory();
0801 }
0802 
0803 void KateFileTree::slotDocumentDelete()
0804 {
0805     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0806     KateFileActions::deleteDocumentFile(m_mainWindow->window(), doc);
0807 }
0808 
0809 // END KateFileTree
0810 
0811 #include "moc_katefiletree.cpp"