1 /* This file is part of the KDE project
2    SPDX-FileCopyrightText: 2010 Thomas Fjellstrom <thomas@fjellstrom.ca>
3    SPDX-FileCopyrightText: 2014 Joseph Wenninger <jowenn@kde.org>
4 
5    SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 // BEGIN Includes
9 #include "katefiletree.h"
10 
11 #include "katefiletreedebug.h"
12 #include "katefiletreemodel.h"
13 #include "katefiletreeproxymodel.h"
14 
15 #include <ktexteditor/application.h>
16 #include <ktexteditor/document.h>
17 #include <ktexteditor/editor.h>
18 
19 #include <KApplicationTrader>
20 #include <KIO/ApplicationLauncherJob>
21 #include <KIO/CopyJob>
22 #include <KIO/DeleteJob>
23 #include <KIO/JobUiDelegate>
24 #include <KIO/OpenFileManagerWindowJob>
25 #include <KLocalizedString>
26 #include <KMessageBox>
27 #include <KStandardAction>
28 
29 #include <QApplication>
30 #include <QClipboard>
31 #include <QContextMenuEvent>
32 #include <QDir>
33 #include <QHeaderView>
34 #include <QInputDialog>
35 #include <QLineEdit>
36 #include <QMenu>
37 #include <QMimeDatabase>
38 #include <QStyledItemDelegate>
39 // END Includes
40 
41 class StyleDelegate : public QStyledItemDelegate
42 {
43 public:
StyleDelegate(QObject * parent=nullptr)44     StyleDelegate(QObject *parent = nullptr)
45         : QStyledItemDelegate(parent)
46     {
47     }
48 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const49     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
50     {
51         QStyledItemDelegate::paint(painter, option, index);
52 
53         if (!m_closeBtn) {
54             return;
55         }
56 
57         auto doc = index.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
58         if (doc && index.column() == 1 && option.state & QStyle::State_MouseOver) {
59             const QIcon icon = QIcon::fromTheme(QStringLiteral("tab-close"));
60             int w = option.decorationSize.width();
61             QRect iconRect(option.rect.right() - w, option.rect.top(), w, option.rect.height());
62             icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter);
63         }
64     }
65 
setShowCloseButton(bool s)66     void setShowCloseButton(bool s)
67     {
68         m_closeBtn = s;
69     }
70 
71 private:
72     bool m_closeBtn = false;
73 };
74 
75 // BEGIN KateFileTree
76 
KateFileTree(QWidget * parent)77 KateFileTree::KateFileTree(QWidget *parent)
78     : QTreeView(parent)
79 {
80     setAcceptDrops(false);
81     setIndentation(12);
82     setAllColumnsShowFocus(true);
83     setFocusPolicy(Qt::NoFocus);
84     setDragEnabled(true);
85     setDragDropMode(QAbstractItemView::DragOnly);
86     setSelectionBehavior(QAbstractItemView::SelectRows);
87     // for hover close button
88     viewport()->setAttribute(Qt::WA_Hover);
89 
90     setItemDelegate(new StyleDelegate(this));
91 
92     // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows)
93     connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked);
94     connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked);
95 
96     m_filelistReloadDocument = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reloa&d"), this);
97     connect(m_filelistReloadDocument, &QAction::triggered, this, &KateFileTree::slotDocumentReload);
98     m_filelistReloadDocument->setWhatsThis(i18n("Reload selected document(s) from disk."));
99 
100     m_filelistCloseDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this);
101     connect(m_filelistCloseDocument, &QAction::triggered, this, &KateFileTree::slotDocumentClose);
102     m_filelistCloseDocument->setWhatsThis(i18n("Close the current document."));
103 
104     m_filelistExpandRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Expand recursively"), this);
105     connect(m_filelistExpandRecursive, &QAction::triggered, this, &KateFileTree::slotExpandRecursive);
106     m_filelistExpandRecursive->setWhatsThis(i18n("Expand the file list sub tree recursively."));
107 
108     m_filelistCollapseRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Collapse recursively"), this);
109     connect(m_filelistCollapseRecursive, &QAction::triggered, this, &KateFileTree::slotCollapseRecursive);
110     m_filelistCollapseRecursive->setWhatsThis(i18n("Collapse the file list sub tree recursively."));
111 
112     m_filelistCloseOtherDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close Other"), this);
113     connect(m_filelistCloseOtherDocument, &QAction::triggered, this, &KateFileTree::slotDocumentCloseOther);
114     m_filelistCloseOtherDocument->setWhatsThis(i18n("Close other documents in this folder."));
115 
116     m_filelistOpenContainingFolder =
117         new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Containing Folder"), this);
118     connect(m_filelistOpenContainingFolder, &QAction::triggered, this, &KateFileTree::slotOpenContainingFolder);
119     m_filelistOpenContainingFolder->setWhatsThis(i18n("Open the folder this file is located in."));
120 
121     m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy File Path"), this);
122     connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename);
123     m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard."));
124 
125     m_filelistRenameFile = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename..."), this);
126     connect(m_filelistRenameFile, &QAction::triggered, this, &KateFileTree::slotRenameFile);
127     m_filelistRenameFile->setWhatsThis(i18n("Rename the selected file."));
128 
129     m_filelistPrintDocument = KStandardAction::print(this, &KateFileTree::slotPrintDocument, this);
130     m_filelistPrintDocument->setWhatsThis(i18n("Print selected document."));
131 
132     m_filelistPrintDocumentPreview = KStandardAction::printPreview(this, &KateFileTree::slotPrintDocumentPreview, this);
133     m_filelistPrintDocumentPreview->setWhatsThis(i18n("Show print preview of current document"));
134 
135     m_filelistDeleteDocument = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this);
136     connect(m_filelistDeleteDocument, &QAction::triggered, this, &KateFileTree::slotDocumentDelete);
137     m_filelistDeleteDocument->setWhatsThis(i18n("Close and delete selected file from storage."));
138 
139     QActionGroup *modeGroup = new QActionGroup(this);
140 
141     m_treeModeAction = setupOption(modeGroup,
142                                    QIcon::fromTheme(QStringLiteral("view-list-tree")),
143                                    i18nc("@action:inmenu", "Tree Mode"),
144                                    i18n("Set view style to Tree Mode"),
145                                    SLOT(slotTreeMode()),
146                                    true);
147 
148     m_listModeAction = setupOption(modeGroup,
149                                    QIcon::fromTheme(QStringLiteral("view-list-text")),
150                                    i18nc("@action:inmenu", "List Mode"),
151                                    i18n("Set view style to List Mode"),
152                                    SLOT(slotListMode()),
153                                    false);
154 
155     QActionGroup *sortGroup = new QActionGroup(this);
156 
157     m_sortByFile =
158         setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Name"), i18n("Sort by Document Name"), SLOT(slotSortName()), true);
159 
160     m_sortByPath =
161         setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), SLOT(slotSortPath()), false);
162 
163     m_sortByOpeningOrder = setupOption(sortGroup,
164                                        QIcon(),
165                                        i18nc("@action:inmenu sorting option", "Opening Order"),
166                                        i18n("Sort by Opening Order"),
167                                        SLOT(slotSortOpeningOrder()),
168                                        false);
169 
170     m_resetHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18nc("@action:inmenu", "Clear History"), this);
171     connect(m_resetHistory, &QAction::triggered, this, &KateFileTree::slotResetHistory);
172     m_resetHistory->setWhatsThis(i18n("Clear edit/view history."));
173 
174     QPalette p = palette();
175     p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight));
176     p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText));
177     setPalette(p);
178 }
179 
~KateFileTree()180 KateFileTree::~KateFileTree()
181 {
182 }
183 
setModel(QAbstractItemModel * model)184 void KateFileTree::setModel(QAbstractItemModel *model)
185 {
186     Q_ASSERT(qobject_cast<KateFileTreeProxyModel *>(model)); // we don't really work with anything else
187     QTreeView::setModel(model);
188 
189     header()->hide();
190     header()->setStretchLastSection(false);
191     header()->setSectionResizeMode(0, QHeaderView::Stretch);
192 
193     int minSize = m_hasCloseButton ? 16 : 1;
194     header()->setMinimumSectionSize(minSize);
195     header()->setSectionResizeMode(1, QHeaderView::Fixed);
196     header()->resizeSection(1, minSize);
197 }
198 
setShowCloseButton(bool show)199 void KateFileTree::setShowCloseButton(bool show)
200 {
201     m_hasCloseButton = show;
202     static_cast<StyleDelegate *>(itemDelegate())->setShowCloseButton(show);
203 
204     if (!header())
205         return;
206 
207     int minSize = show ? 16 : 1;
208     header()->setMinimumSectionSize(minSize);
209     header()->resizeSection(1, minSize);
210     header()->viewport()->update();
211 }
212 
setupOption(QActionGroup * group,const QIcon & icon,const QString & label,const QString & whatsThis,const char * slot,bool checked)213 QAction *KateFileTree::setupOption(QActionGroup *group, const QIcon &icon, const QString &label, const QString &whatsThis, const char *slot, bool checked)
214 {
215     QAction *new_action = new QAction(icon, label, this);
216     new_action->setWhatsThis(whatsThis);
217     new_action->setActionGroup(group);
218     new_action->setCheckable(true);
219     new_action->setChecked(checked);
220     connect(new_action, SIGNAL(triggered()), this, slot);
221     return new_action;
222 }
223 
slotListMode()224 void KateFileTree::slotListMode()
225 {
226     Q_EMIT viewModeChanged(true);
227 }
228 
slotTreeMode()229 void KateFileTree::slotTreeMode()
230 {
231     Q_EMIT viewModeChanged(false);
232 }
233 
slotSortName()234 void KateFileTree::slotSortName()
235 {
236     Q_EMIT sortRoleChanged(Qt::DisplayRole);
237 }
238 
slotSortPath()239 void KateFileTree::slotSortPath()
240 {
241     Q_EMIT sortRoleChanged(KateFileTreeModel::PathRole);
242 }
243 
slotSortOpeningOrder()244 void KateFileTree::slotSortOpeningOrder()
245 {
246     Q_EMIT sortRoleChanged(KateFileTreeModel::OpeningOrderRole);
247 }
248 
slotCurrentChanged(const QModelIndex & current,const QModelIndex & previous)249 void KateFileTree::slotCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
250 {
251     Q_UNUSED(previous);
252     if (!current.isValid()) {
253         return;
254     }
255 
256     KTextEditor::Document *doc = model()->data(current, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
257     if (doc) {
258         m_previouslySelected = current;
259     }
260 }
261 
mouseClicked(const QModelIndex & index)262 void KateFileTree::mouseClicked(const QModelIndex &index)
263 {
264     if (auto doc = model()->data(index, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>()) {
265         if (m_hasCloseButton && index.column() == 1) {
266             KTextEditor::Editor::instance()->application()->closeDocuments({doc});
267             return;
268         }
269         Q_EMIT activateDocument(doc);
270     }
271 }
272 
contextMenuEvent(QContextMenuEvent * event)273 void KateFileTree::contextMenuEvent(QContextMenuEvent *event)
274 {
275     m_indexContextMenu = selectionModel()->currentIndex();
276 
277     selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
278 
279     KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
280     KateFileTreeModel *ftm = static_cast<KateFileTreeModel *>(ftpm->sourceModel());
281 
282     bool listMode = ftm->listMode();
283     m_treeModeAction->setChecked(!listMode);
284     m_listModeAction->setChecked(listMode);
285 
286     int sortRole = ftpm->sortRole();
287     m_sortByFile->setChecked(sortRole == Qt::DisplayRole);
288     m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole);
289     m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole);
290 
291     KTextEditor::Document *doc = m_indexContextMenu.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
292     const bool isFile = (nullptr != doc);
293 
294     QMenu menu;
295     menu.addAction(m_filelistReloadDocument);
296     menu.addAction(m_filelistCloseDocument);
297     menu.addAction(m_filelistExpandRecursive);
298     menu.addAction(m_filelistCollapseRecursive);
299 
300     if (isFile) {
301         menu.addAction(m_filelistCloseOtherDocument);
302         menu.addSeparator();
303         menu.addAction(m_filelistOpenContainingFolder);
304         menu.addAction(m_filelistCopyFilename);
305         menu.addAction(m_filelistRenameFile);
306         menu.addAction(m_filelistPrintDocument);
307         menu.addAction(m_filelistPrintDocumentPreview);
308         QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With"));
309         connect(openWithMenu, &QMenu::aboutToShow, this, &KateFileTree::slotFixOpenWithMenu);
310         connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction);
311 
312         const bool hasFileName = doc->url().isValid();
313         m_filelistOpenContainingFolder->setEnabled(hasFileName);
314         m_filelistCopyFilename->setEnabled(hasFileName);
315         m_filelistRenameFile->setEnabled(hasFileName);
316         m_filelistDeleteDocument->setEnabled(hasFileName);
317         menu.addAction(m_filelistDeleteDocument);
318     }
319 
320     menu.addSeparator();
321     QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode"));
322     view_menu->addAction(m_treeModeAction);
323     view_menu->addAction(m_listModeAction);
324 
325     QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By"));
326     sort_menu->addAction(m_sortByFile);
327     sort_menu->addAction(m_sortByPath);
328     sort_menu->addAction(m_sortByOpeningOrder);
329 
330     menu.addAction(m_resetHistory);
331 
332     menu.exec(viewport()->mapToGlobal(event->pos()));
333 
334     if (m_previouslySelected.isValid()) {
335         selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
336     }
337 
338     event->accept();
339 }
340 
slotFixOpenWithMenu()341 void KateFileTree::slotFixOpenWithMenu()
342 {
343     QMenu *menu = static_cast<QMenu *>(sender());
344     menu->clear();
345 
346     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
347     if (!doc) {
348         return;
349     }
350 
351     // get a list of appropriate services.
352     QMimeDatabase db;
353     QMimeType mime = db.mimeTypeForName(doc->mimeType());
354 
355     QAction *a = nullptr;
356     const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
357     // for each one, insert a menu item...
358     for (const auto &service : offers) {
359         if (service->name() == QLatin1String("Kate")) {
360             continue;
361         }
362         a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
363         a->setData(service->entryPath());
364     }
365     // append "Other..." to call the KDE "open with" dialog.
366     a = menu->addAction(i18n("&Other..."));
367     a->setData(QString());
368 }
369 
slotOpenWithMenuAction(QAction * a)370 void KateFileTree::slotOpenWithMenuAction(QAction *a)
371 {
372     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
373     if (!doc) {
374         return;
375     }
376 
377     const QList<QUrl> list({doc->url()});
378 
379     KService::Ptr app = KService::serviceByDesktopPath(a->data().toString());
380     // If app is null, ApplicationLauncherJob will invoke the open-with dialog
381     auto *job = new KIO::ApplicationLauncherJob(app);
382     job->setUrls(list);
383     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
384     job->start();
385 }
386 
Q_DECLARE_METATYPE(QList<KTextEditor::Document * >)387 Q_DECLARE_METATYPE(QList<KTextEditor::Document *>)
388 
389 void KateFileTree::slotDocumentClose()
390 {
391     m_previouslySelected = QModelIndex();
392     QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole);
393     if (!v.isValid()) {
394         return;
395     }
396     QList<KTextEditor::Document *> closingDocuments = v.value<QList<KTextEditor::Document *>>();
397     KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments);
398 }
399 
slotExpandRecursive()400 void KateFileTree::slotExpandRecursive()
401 {
402     if (!m_indexContextMenu.isValid()) {
403         return;
404     }
405 
406     // Work list for DFS walk over sub tree
407     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
408 
409     while (!worklist.isEmpty()) {
410         QPersistentModelIndex index = worklist.takeLast();
411 
412         // Expand current item
413         expand(index);
414 
415         // Append all children of current item
416         for (int i = 0; i < model()->rowCount(index); ++i) {
417             worklist.append(model()->index(i, 0, index));
418         }
419     }
420 }
421 
slotCollapseRecursive()422 void KateFileTree::slotCollapseRecursive()
423 {
424     if (!m_indexContextMenu.isValid()) {
425         return;
426     }
427 
428     // Work list for DFS walk over sub tree
429     QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
430 
431     while (!worklist.isEmpty()) {
432         QPersistentModelIndex index = worklist.takeLast();
433 
434         // Expand current item
435         collapse(index);
436 
437         // Prepend all children of current item
438         for (int i = 0; i < model()->rowCount(index); ++i) {
439             worklist.append(model()->index(i, 0, index));
440         }
441     }
442 }
443 
slotDocumentCloseOther()444 void KateFileTree::slotDocumentCloseOther()
445 {
446     QVariant v = model()->data(m_indexContextMenu.parent(), KateFileTreeModel::DocumentTreeRole);
447     if (!v.isValid()) {
448         return;
449     }
450 
451     QList<KTextEditor::Document *> closingDocuments = v.value<QList<KTextEditor::Document *>>();
452     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
453 
454     closingDocuments.removeOne(doc);
455 
456     KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments);
457 }
458 
slotDocumentReload()459 void KateFileTree::slotDocumentReload()
460 {
461     QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole);
462     if (!v.isValid()) {
463         return;
464     }
465 
466     const QList<KTextEditor::Document *> docs = v.value<QList<KTextEditor::Document *>>();
467     for (KTextEditor::Document *doc : docs) {
468         doc->documentReload();
469     }
470 }
471 
slotOpenContainingFolder()472 void KateFileTree::slotOpenContainingFolder()
473 {
474     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
475     if (doc) {
476         KIO::highlightInFileManager({doc->url()});
477     }
478 }
479 
slotCopyFilename()480 void KateFileTree::slotCopyFilename()
481 {
482     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
483 
484     // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here
485     //       (make sure that the mentioned bug 381052 does not reappear)
486 
487     if (doc) {
488         // ensure we prefer native separators, bug 381052
489         if (doc->url().isLocalFile()) {
490             QApplication::clipboard()->setText(QDir::toNativeSeparators(doc->url().toLocalFile()));
491         } else {
492             QApplication::clipboard()->setText(doc->url().url());
493         }
494     }
495 }
496 
slotRenameFile()497 void KateFileTree::slotRenameFile()
498 {
499     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
500 
501     // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here
502 
503     if (!doc) {
504         return;
505     }
506 
507     const QUrl oldFileUrl = doc->url();
508     const QString oldFileName = doc->url().fileName();
509     bool ok;
510 
511     QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok);
512     if (!ok) {
513         return;
514     }
515 
516     QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
517     newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName);
518 
519     if (!newFileUrl.isValid()) {
520         return;
521     }
522 
523     if (!doc->closeUrl()) {
524         return;
525     }
526 
527     doc->waitSaveComplete();
528 
529     KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl);
530     QSharedPointer<QMetaObject::Connection> sc(new QMetaObject::Connection());
531     auto success = [doc, sc](KIO::Job *, const QUrl &, const QUrl &realNewFileUrl, const QDateTime &, bool, bool) {
532         doc->openUrl(realNewFileUrl);
533         doc->documentSavedOrUploaded(doc, true);
534         QObject::disconnect(*sc);
535     };
536     *sc = connect(job, &KIO::CopyJob::copyingDone, doc, success);
537 
538     if (!job->exec()) {
539         KMessageBox::sorry(this, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString()));
540         doc->openUrl(oldFileUrl);
541     }
542 }
543 
slotDocumentFirst()544 void KateFileTree::slotDocumentFirst()
545 {
546     KTextEditor::Document *doc = model()->data(model()->index(0, 0), KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
547     if (doc) {
548         Q_EMIT activateDocument(doc);
549     }
550 }
551 
slotDocumentLast()552 void KateFileTree::slotDocumentLast()
553 {
554     int count = model()->rowCount(model()->parent(currentIndex()));
555     KTextEditor::Document *doc = model()->data(model()->index(count - 1, 0), KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
556     if (doc) {
557         Q_EMIT activateDocument(doc);
558     }
559 }
560 
slotDocumentPrev()561 void KateFileTree::slotDocumentPrev()
562 {
563     KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
564 
565     QModelIndex current_index = currentIndex();
566     QModelIndex prev;
567 
568     // scan up the tree skipping any dir nodes
569     while (current_index.isValid()) {
570         if (current_index.row() > 0) {
571             current_index = ftpm->sibling(current_index.row() - 1, current_index.column(), current_index);
572             if (!current_index.isValid()) {
573                 break;
574             }
575 
576             if (ftpm->isDir(current_index)) {
577                 // try and select the last child in this parent
578                 int children = ftpm->rowCount(current_index);
579                 current_index = ftpm->index(children - 1, 0, current_index);
580                 if (ftpm->isDir(current_index)) {
581                     // since we're a dir, keep going
582                     while (ftpm->isDir(current_index)) {
583                         children = ftpm->rowCount(current_index);
584                         current_index = ftpm->index(children - 1, 0, current_index);
585                     }
586 
587                     if (!ftpm->isDir(current_index)) {
588                         prev = current_index;
589                         break;
590                     }
591 
592                     continue;
593                 } else {
594                     // we're the previous file, set prev
595                     prev = current_index;
596                     break;
597                 }
598             } else { // found document item
599                 prev = current_index;
600                 break;
601             }
602         } else {
603             // just select the parent, the logic above will handle the rest
604             current_index = ftpm->parent(current_index);
605             if (!current_index.isValid()) {
606                 // paste the root node here, try and wrap around
607 
608                 int children = ftpm->rowCount(current_index);
609                 QModelIndex last_index = ftpm->index(children - 1, 0, current_index);
610                 if (!last_index.isValid()) {
611                     break;
612                 }
613 
614                 if (ftpm->isDir(last_index)) {
615                     // last node is a dir, select last child row
616                     int last_children = ftpm->rowCount(last_index);
617                     prev = ftpm->index(last_children - 1, 0, last_index);
618                     // bug here?
619                     break;
620                 } else {
621                     // got last file node
622                     prev = last_index;
623                     break;
624                 }
625             }
626         }
627     }
628 
629     if (prev.isValid()) {
630         KTextEditor::Document *doc = model()->data(prev, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
631         Q_EMIT activateDocument(doc);
632     }
633 }
634 
slotDocumentNext()635 void KateFileTree::slotDocumentNext()
636 {
637     KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
638 
639     QModelIndex current_index = currentIndex();
640     int parent_row_count = ftpm->rowCount(ftpm->parent(current_index));
641     QModelIndex next;
642 
643     // scan down the tree skipping any dir nodes
644     while (current_index.isValid()) {
645         if (current_index.row() < parent_row_count - 1) {
646             current_index = ftpm->sibling(current_index.row() + 1, current_index.column(), current_index);
647             if (!current_index.isValid()) {
648                 break;
649             }
650 
651             if (ftpm->isDir(current_index)) {
652                 // we have a dir node
653                 while (ftpm->isDir(current_index)) {
654                     current_index = ftpm->index(0, 0, current_index);
655                 }
656 
657                 parent_row_count = ftpm->rowCount(ftpm->parent(current_index));
658 
659                 if (!ftpm->isDir(current_index)) {
660                     next = current_index;
661                     break;
662                 }
663             } else { // found document item
664                 next = current_index;
665                 break;
666             }
667         } else {
668             // select the parent's next sibling
669             QModelIndex parent_index = ftpm->parent(current_index);
670             int grandparent_row_count = ftpm->rowCount(ftpm->parent(parent_index));
671 
672             current_index = parent_index;
673             parent_row_count = grandparent_row_count;
674 
675             // at least if we're not past the last node
676             if (!current_index.isValid()) {
677                 // paste the root node here, try and wrap around
678                 QModelIndex last_index = ftpm->index(0, 0, QModelIndex());
679                 if (!last_index.isValid()) {
680                     break;
681                 }
682 
683                 if (ftpm->isDir(last_index)) {
684                     // last node is a dir, select first child row
685                     while (ftpm->isDir(last_index)) {
686                         if (ftpm->rowCount(last_index)) {
687                             // has children, select first
688                             last_index = ftpm->index(0, 0, last_index);
689                         }
690                     }
691 
692                     next = last_index;
693                     break;
694                 } else {
695                     // got first file node
696                     next = last_index;
697                     break;
698                 }
699             }
700         }
701     }
702 
703     if (next.isValid()) {
704         KTextEditor::Document *doc = model()->data(next, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
705         Q_EMIT activateDocument(doc);
706     }
707 }
708 
slotPrintDocument()709 void KateFileTree::slotPrintDocument()
710 {
711     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
712 
713     if (!doc) {
714         return;
715     }
716 
717     doc->print();
718 }
719 
slotPrintDocumentPreview()720 void KateFileTree::slotPrintDocumentPreview()
721 {
722     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
723 
724     if (!doc) {
725         return;
726     }
727 
728     doc->printPreview();
729 }
730 
slotResetHistory()731 void KateFileTree::slotResetHistory()
732 {
733     KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
734     KateFileTreeModel *ftm = static_cast<KateFileTreeModel *>(ftpm->sourceModel());
735     ftm->resetHistory();
736 }
737 
slotDocumentDelete()738 void KateFileTree::slotDocumentDelete()
739 {
740     KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
741 
742     // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here
743 
744     if (!doc) {
745         return;
746     }
747 
748     QUrl url = doc->url();
749 
750     bool go = (KMessageBox::warningContinueCancel(this,
751                                                   i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()),
752                                                   i18n("Delete file?"),
753                                                   KStandardGuiItem::yes(),
754                                                   KStandardGuiItem::no(),
755                                                   QStringLiteral("filetreedeletefile"))
756                == KMessageBox::Continue);
757 
758     if (!go) {
759         return;
760     }
761 
762     if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) {
763         return; // no extra message, the internals of ktexteditor should take care of that.
764     }
765 
766     if (url.isValid()) {
767         KIO::DeleteJob *job = KIO::del(url);
768         if (!job->exec()) {
769             KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString()));
770         }
771     }
772 }
773 
774 // END KateFileTree
775