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 ¤t, 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