File indexing completed on 2024-05-12 09:54:30

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::setupContextMenuActionGroups()
0225 {
0226     QActionGroup *modeGroup = new QActionGroup(this);
0227 
0228     m_treeModeAction = setupOption(modeGroup,
0229                                    QIcon::fromTheme(QStringLiteral("view-list-tree")),
0230                                    i18nc("@action:inmenu", "Tree Mode"),
0231                                    i18n("Set view style to Tree Mode"),
0232                                    &KateFileTree::slotTreeMode,
0233                                    Qt::Checked);
0234 
0235     m_listModeAction = setupOption(modeGroup,
0236                                    QIcon::fromTheme(QStringLiteral("view-list-text")),
0237                                    i18nc("@action:inmenu", "List Mode"),
0238                                    i18n("Set view style to List Mode"),
0239                                    &KateFileTree::slotListMode);
0240 
0241     QActionGroup *sortGroup = new QActionGroup(this);
0242 
0243     m_sortByFile = setupOption(sortGroup,
0244                                QIcon(),
0245                                i18nc("@action:inmenu sorting option", "Document Name"),
0246                                i18n("Sort by Document Name"),
0247                                &KateFileTree::slotSortName,
0248                                Qt::Checked);
0249 
0250     m_sortByPath =
0251         setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), &KateFileTree::slotSortPath);
0252 
0253     m_sortByOpeningOrder = setupOption(sortGroup,
0254                                        QIcon(),
0255                                        i18nc("@action:inmenu sorting option", "Opening Order"),
0256                                        i18n("Sort by Opening Order"),
0257                                        &KateFileTree::slotSortOpeningOrder);
0258 
0259     m_customSorting = new QAction(QIcon(), i18n("Custom Sorting"), this);
0260     m_customSorting->setCheckable(true);
0261     m_customSorting->setActionGroup(sortGroup);
0262     connect(m_customSorting, &QAction::triggered, this, [this] {
0263         Q_EMIT sortRoleChanged(CustomSorting);
0264     });
0265 }
0266 
0267 QAction *KateFileTree::setupOption(QActionGroup *group,
0268                                    const QIcon &icon,
0269                                    const QString &text,
0270                                    const QString &whatsThis,
0271                                    const Func &slot,
0272                                    Qt::CheckState checked /* = Qt::Unchecked */)
0273 {
0274     QAction *new_action = new QAction(icon, text, this);
0275     new_action->setWhatsThis(whatsThis);
0276     new_action->setActionGroup(group);
0277     new_action->setCheckable(true);
0278     new_action->setChecked(checked == Qt::Checked);
0279     connect(new_action, &QAction::triggered, this, slot);
0280     return new_action;
0281 }
0282 
0283 void KateFileTree::slotListMode()
0284 {
0285     Q_EMIT viewModeChanged(true);
0286 }
0287 
0288 void KateFileTree::slotTreeMode()
0289 {
0290     Q_EMIT viewModeChanged(false);
0291 }
0292 
0293 void KateFileTree::slotSortName()
0294 {
0295     Q_EMIT sortRoleChanged(Qt::DisplayRole);
0296 }
0297 
0298 void KateFileTree::slotSortPath()
0299 {
0300     Q_EMIT sortRoleChanged(KateFileTreeModel::PathRole);
0301 }
0302 
0303 void KateFileTree::slotSortOpeningOrder()
0304 {
0305     Q_EMIT sortRoleChanged(KateFileTreeModel::OpeningOrderRole);
0306 }
0307 
0308 void KateFileTree::slotCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
0309 {
0310     Q_UNUSED(previous);
0311     if (!current.isValid()) {
0312         return;
0313     }
0314 
0315     KTextEditor::Document *doc = m_proxyModel->docFromIndex(current);
0316     if (doc) {
0317         m_previouslySelected = current;
0318     }
0319 }
0320 
0321 void KateFileTree::closeClicked(const QModelIndex &index)
0322 {
0323     if (m_proxyModel->isDir(index)) {
0324         const QList<KTextEditor::Document *> list = m_proxyModel->docTreeFromIndex(index);
0325         closeDocs(list);
0326         return;
0327     } else if (m_proxyModel->isWidgetDir(index)) {
0328         const auto idx = index.siblingAtColumn(0);
0329         const auto count = m_proxyModel->rowCount(idx);
0330         QWidgetList widgets;
0331         widgets.reserve(count);
0332         for (int i = 0; i < count; ++i) {
0333             widgets << m_proxyModel->index(i, 0, idx).data(KateFileTreeModel::WidgetRole).value<QWidget *>();
0334         }
0335 
0336         for (const auto &w : widgets) {
0337             closeWidget(w);
0338         }
0339     }
0340 
0341     if (auto *doc = m_proxyModel->docFromIndex(index)) {
0342         closeDocs({doc});
0343     } else if (auto *w = index.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0344         Q_EMIT closeWidget(w);
0345     }
0346 }
0347 
0348 void KateFileTree::mouseClicked(const QModelIndex &index)
0349 {
0350     if (m_hasCloseButton && index.column() == 1) {
0351         closeClicked(index);
0352         return;
0353     }
0354 
0355     if (auto *doc = m_proxyModel->docFromIndex(index)) {
0356         Q_EMIT activateDocument(doc);
0357     } else if (auto *w = index.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0358         Q_EMIT activateWidget(w);
0359     }
0360 }
0361 
0362 void KateFileTree::contextMenuEvent(QContextMenuEvent *event)
0363 {
0364     m_indexContextMenu = indexAt(event->pos());
0365     if (m_indexContextMenu.isValid()) {
0366         selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0367     }
0368 
0369     const bool listMode = m_sourceModel->listMode();
0370     m_treeModeAction->setChecked(!listMode);
0371     m_listModeAction->setChecked(listMode);
0372 
0373     const int sortRole = m_proxyModel->sortRole();
0374     m_sortByFile->setChecked(sortRole == Qt::DisplayRole);
0375     m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole);
0376     m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole);
0377     m_customSorting->setChecked(sortRole == CustomSorting);
0378 
0379     KTextEditor::Document *doc = docFromIndex(m_indexContextMenu);
0380 
0381     bool isDir = m_proxyModel->isDir(m_indexContextMenu);
0382     bool isWidgetDir = m_proxyModel->isWidgetDir(m_indexContextMenu);
0383     bool isWidget = m_indexContextMenu.data(KateFileTreeModel::WidgetRole).value<QWidget *>() != nullptr;
0384 
0385     QMenu menu(this);
0386     if (doc) {
0387         if (doc->url().isValid()) {
0388             QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With"));
0389             openWithMenu->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
0390             connect(openWithMenu, &QMenu::aboutToShow, this, [this, openWithMenu]() {
0391                 slotFixOpenWithMenu(openWithMenu);
0392             });
0393             connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction);
0394 
0395             menu.addSeparator();
0396             menu.addAction(m_filelistCopyFilename);
0397             menu.addAction(m_filelistRenameFile);
0398             menu.addAction(m_filelistDeleteDocument);
0399             menu.addAction(m_filelistReloadDocument);
0400 
0401             if (doc->url().isLocalFile()) {
0402                 auto a = menu.addAction(i18n("Show File Git History"));
0403                 connect(a, &QAction::triggered, this, [doc] {
0404                     auto url = doc->url();
0405                     if (url.isValid() && url.isLocalFile()) {
0406                         FileHistory::showFileHistory(url.toLocalFile());
0407                     }
0408                 });
0409             }
0410 
0411             menu.addSeparator();
0412             menu.addAction(m_filelistOpenContainingFolder);
0413 
0414             menu.addSeparator();
0415             menu.addAction(m_filelistCloseDocument);
0416             menu.addAction(m_filelistCloseOtherDocument);
0417 
0418             menu.addSeparator();
0419             menu.addAction(m_filelistPrintDocument);
0420             menu.addAction(m_filelistPrintDocumentPreview);
0421         } else {
0422             // untitled documents
0423             menu.addAction(m_filelistCloseDocument);
0424 
0425             menu.addSeparator();
0426         }
0427     } else if (isDir || isWidgetDir || isWidget) {
0428         if (isDir) {
0429             menu.addAction(m_filelistReloadDocument);
0430         }
0431 
0432         menu.addSeparator();
0433         menu.addAction(m_filelistCloseDocument);
0434 
0435         menu.addSeparator();
0436         menu.addAction(m_filelistExpandRecursive);
0437         menu.addAction(m_filelistCollapseRecursive);
0438     }
0439 
0440     menu.addSeparator();
0441     QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode"));
0442     view_menu->addAction(m_treeModeAction);
0443     view_menu->addAction(m_listModeAction);
0444 
0445     QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By"));
0446     sort_menu->addAction(m_sortByFile);
0447     sort_menu->addAction(m_sortByPath);
0448     sort_menu->addAction(m_sortByOpeningOrder);
0449     sort_menu->addAction(m_customSorting);
0450 
0451     m_filelistCloseDocument->setEnabled(m_indexContextMenu.isValid());
0452 
0453     menu.addAction(m_resetHistory);
0454 
0455     menu.exec(viewport()->mapToGlobal(event->pos()));
0456 
0457     if (m_previouslySelected.isValid()) {
0458         selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
0459     }
0460 
0461     event->accept();
0462 }
0463 
0464 void KateFileTree::slotFixOpenWithMenu(QMenu *menu)
0465 {
0466     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0467     if (!doc) {
0468         return;
0469     }
0470 
0471     KateFileActions::prepareOpenWithMenu(doc->url(), menu);
0472 }
0473 
0474 void KateFileTree::slotOpenWithMenuAction(QAction *a)
0475 {
0476     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0477     if (!doc) {
0478         return;
0479     }
0480 
0481     KateFileActions::showOpenWithMenu(m_mainWindow->window(), doc->url(), a);
0482 }
0483 
0484 void KateFileTree::slotDocumentClose()
0485 {
0486     m_previouslySelected = QModelIndex();
0487     if (!m_indexContextMenu.isValid()) {
0488         return;
0489     }
0490     const auto closeColumnIndex = m_indexContextMenu.sibling(m_indexContextMenu.row(), 1);
0491     closeClicked(closeColumnIndex);
0492 }
0493 
0494 void KateFileTree::addChildrenTolist(const QModelIndex &index, QList<QPersistentModelIndex> *worklist)
0495 {
0496     const int count = m_proxyModel->rowCount(index);
0497     worklist->reserve(worklist->size() + count);
0498     for (int i = 0; i < count; ++i) {
0499         worklist->append(m_proxyModel->index(i, 0, index));
0500     }
0501 }
0502 
0503 void KateFileTree::slotExpandRecursive()
0504 {
0505     if (!m_indexContextMenu.isValid()) {
0506         return;
0507     }
0508 
0509     // Work list for DFS walk over sub tree
0510     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
0511 
0512     while (!worklist.isEmpty()) {
0513         QPersistentModelIndex index = worklist.takeLast();
0514 
0515         // Expand current item
0516         expand(index);
0517 
0518         // Append all children of current item
0519         addChildrenTolist(index, &worklist);
0520     }
0521 }
0522 
0523 void KateFileTree::slotCollapseRecursive()
0524 {
0525     if (!m_indexContextMenu.isValid()) {
0526         return;
0527     }
0528 
0529     // Work list for DFS walk over sub tree
0530     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
0531 
0532     while (!worklist.isEmpty()) {
0533         QPersistentModelIndex index = worklist.takeLast();
0534 
0535         // Expand current item
0536         collapse(index);
0537 
0538         // Prepend all children of current item
0539         addChildrenTolist(index, &worklist);
0540     }
0541 }
0542 
0543 void KateFileTree::slotDocumentCloseOther()
0544 {
0545     QList<KTextEditor::Document *> closingDocuments = m_proxyModel->docTreeFromIndex(m_indexContextMenu.parent());
0546     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0547     closingDocuments.removeOne(doc);
0548     closeDocs(closingDocuments);
0549 }
0550 
0551 void KateFileTree::slotDocumentReload()
0552 {
0553     const QList<KTextEditor::Document *> docs = docTreeFromIndex(m_indexContextMenu);
0554     for (auto *doc : docs) {
0555         doc->documentReload();
0556     }
0557 }
0558 
0559 void KateFileTree::slotOpenContainingFolder()
0560 {
0561     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0562 
0563     if (!doc) {
0564         return;
0565     }
0566 
0567     KateFileActions::openContainingFolder(doc);
0568 }
0569 
0570 void KateFileTree::slotCopyFilename()
0571 {
0572     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0573 
0574     if (!doc) {
0575         return;
0576     }
0577 
0578     KateFileActions::copyFilePathToClipboard(doc);
0579 }
0580 
0581 void KateFileTree::slotRenameFile()
0582 {
0583     KateFileActions::renameDocumentFile(this, m_proxyModel->docFromIndex(m_indexContextMenu));
0584 }
0585 
0586 void KateFileTree::slotDocumentFirst()
0587 {
0588     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(0, 0));
0589     if (doc) {
0590         Q_EMIT activateDocument(doc);
0591     }
0592 }
0593 
0594 void KateFileTree::slotDocumentLast()
0595 {
0596     int count = m_proxyModel->rowCount(m_proxyModel->parent(currentIndex()));
0597     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(count - 1, 0));
0598     if (doc) {
0599         Q_EMIT activateDocument(doc);
0600     }
0601 }
0602 
0603 void KateFileTree::slotDocumentPrev()
0604 {
0605     QModelIndex current_index = currentIndex();
0606     QModelIndex prev;
0607 
0608     // scan up the tree skipping any dir nodes
0609     while (current_index.isValid()) {
0610         if (current_index.row() > 0) {
0611             current_index = m_proxyModel->sibling(current_index.row() - 1, current_index.column(), current_index);
0612             if (!current_index.isValid()) {
0613                 break;
0614             }
0615 
0616             if (m_proxyModel->isDir(current_index)) {
0617                 // try and select the last child in this parent
0618                 int children = m_proxyModel->rowCount(current_index);
0619                 current_index = m_proxyModel->index(children - 1, 0, current_index);
0620                 if (m_proxyModel->isDir(current_index)) {
0621                     // since we're a dir, keep going
0622                     while (m_proxyModel->isDir(current_index)) {
0623                         children = m_proxyModel->rowCount(current_index);
0624                         current_index = m_proxyModel->index(children - 1, 0, current_index);
0625                     }
0626 
0627                     if (!m_proxyModel->isDir(current_index)) {
0628                         prev = current_index;
0629                         break;
0630                     }
0631 
0632                     continue;
0633                 } else {
0634                     // we're the previous file, set prev
0635                     prev = current_index;
0636                     break;
0637                 }
0638             } else { // found document item
0639                 prev = current_index;
0640                 break;
0641             }
0642         } else {
0643             // just select the parent, the logic above will handle the rest
0644             current_index = m_proxyModel->parent(current_index);
0645             if (!current_index.isValid()) {
0646                 // paste the root node here, try and wrap around
0647 
0648                 int children = m_proxyModel->rowCount(current_index);
0649                 QModelIndex last_index = m_proxyModel->index(children - 1, 0, current_index);
0650                 if (!last_index.isValid()) {
0651                     break;
0652                 }
0653 
0654                 if (m_proxyModel->isDir(last_index)) {
0655                     // last node is a dir, select last child row
0656                     int last_children = m_proxyModel->rowCount(last_index);
0657                     prev = m_proxyModel->index(last_children - 1, 0, last_index);
0658                     // bug here?
0659                     break;
0660                 } else {
0661                     // got last file node
0662                     prev = last_index;
0663                     break;
0664                 }
0665             }
0666         }
0667     }
0668 
0669     if (prev.isValid()) {
0670         if (auto *doc = m_proxyModel->docFromIndex(prev)) {
0671             Q_EMIT activateDocument(doc);
0672         } else if (auto *w = prev.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0673             Q_EMIT activateWidget(w);
0674         }
0675     }
0676 }
0677 
0678 void KateFileTree::slotDocumentNext()
0679 {
0680     QModelIndex current_index = currentIndex();
0681     int parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
0682     QModelIndex next;
0683 
0684     // scan down the tree skipping any dir nodes
0685     while (current_index.isValid()) {
0686         if (current_index.row() < parent_row_count - 1) {
0687             current_index = m_proxyModel->sibling(current_index.row() + 1, current_index.column(), current_index);
0688             if (!current_index.isValid()) {
0689                 break;
0690             }
0691 
0692             if (m_proxyModel->isDir(current_index)) {
0693                 // we have a dir node
0694                 while (m_proxyModel->isDir(current_index)) {
0695                     current_index = m_proxyModel->index(0, 0, current_index);
0696                 }
0697 
0698                 parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
0699 
0700                 if (!m_proxyModel->isDir(current_index)) {
0701                     next = current_index;
0702                     break;
0703                 }
0704             } else { // found document item
0705                 next = current_index;
0706                 break;
0707             }
0708         } else {
0709             // select the parent's next sibling
0710             QModelIndex parent_index = m_proxyModel->parent(current_index);
0711             int grandparent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(parent_index));
0712 
0713             current_index = parent_index;
0714             parent_row_count = grandparent_row_count;
0715 
0716             // at least if we're not past the last node
0717             if (!current_index.isValid()) {
0718                 // paste the root node here, try and wrap around
0719                 QModelIndex last_index = m_proxyModel->index(0, 0, QModelIndex());
0720                 if (!last_index.isValid()) {
0721                     break;
0722                 }
0723 
0724                 if (m_proxyModel->isDir(last_index)) {
0725                     // last node is a dir, select first child row
0726                     while (m_proxyModel->isDir(last_index)) {
0727                         if (m_proxyModel->rowCount(last_index)) {
0728                             // has children, select first
0729                             last_index = m_proxyModel->index(0, 0, last_index);
0730                         }
0731                     }
0732 
0733                     next = last_index;
0734                     break;
0735                 } else {
0736                     // got first file node
0737                     next = last_index;
0738                     break;
0739                 }
0740             }
0741         }
0742     }
0743 
0744     if (next.isValid()) {
0745         if (auto *doc = m_proxyModel->docFromIndex(next)) {
0746             Q_EMIT activateDocument(doc);
0747         } else if (auto *w = next.data(KateFileTreeModel::WidgetRole).value<QWidget *>()) {
0748             Q_EMIT activateWidget(w);
0749         }
0750     }
0751 }
0752 
0753 void KateFileTree::slotPrintDocument()
0754 {
0755     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0756 
0757     if (!doc) {
0758         return;
0759     }
0760 
0761     doc->print();
0762 }
0763 
0764 void KateFileTree::slotPrintDocumentPreview()
0765 {
0766     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0767 
0768     if (!doc) {
0769         return;
0770     }
0771 
0772     doc->printPreview();
0773 }
0774 
0775 void KateFileTree::slotResetHistory()
0776 {
0777     m_sourceModel->resetHistory();
0778 }
0779 
0780 void KateFileTree::slotDocumentDelete()
0781 {
0782     KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
0783     KateFileActions::deleteDocumentFile(m_mainWindow->window(), doc);
0784 }
0785 
0786 // END KateFileTree
0787 
0788 #include "moc_katefiletree.cpp"