katefiletree.cpp 28.5 KB
Newer Older
1
/* This file is part of the KDE project
2
3
4
5
   SPDX-FileCopyrightText: 2010 Thomas Fjellstrom <thomas@fjellstrom.ca>
   SPDX-FileCopyrightText: 2014 Joseph Wenninger <jowenn@kde.org>

   SPDX-License-Identifier: LGPL-2.0-or-later
6
7
*/

8
// BEGIN Includes
9
#include "katefiletree.h"
Christoph Cullmann's avatar
Christoph Cullmann committed
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

Christoph Cullmann's avatar
Christoph Cullmann committed
19
#include <KApplicationTrader>
Christoph Cullmann's avatar
Christoph Cullmann committed
20
#include <KIO/ApplicationLauncherJob>
21
22
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
Christoph Cullmann's avatar
Christoph Cullmann committed
23
#include <KIO/JobUiDelegate>
24
25
26
#include <KIO/OpenFileManagerWindowJob>
#include <KLocalizedString>
#include <KMessageBox>
27
#include <KStandardAction>
28

Volker Krause's avatar
Volker Krause committed
29
#include <QActionGroup>
30
#include <QApplication>
Michal Humpula's avatar
Michal Humpula committed
31
32
#include <QClipboard>
#include <QContextMenuEvent>
33
#include <QDir>
34
#include <QHeaderView>
35
36
#include <QInputDialog>
#include <QLineEdit>
37
38
#include <QMenu>
#include <QMimeDatabase>
39
#include <QStyledItemDelegate>
40
// END Includes
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static KTextEditor::Document *docFromIndex(const QModelIndex &index)
{
    return index.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
}

static QList<KTextEditor::Document *> docTreeFromIndex(const QModelIndex &index)
{
    return index.data(KateFileTreeModel::DocumentTreeRole).value<QList<KTextEditor::Document *>>();
}

static bool closeDocs(const QList<KTextEditor::Document *> &docs)
{
    return KTextEditor::Editor::instance()->application()->closeDocuments(docs);
}

57
58
59
60
61
62
63
64
class StyleDelegate : public QStyledItemDelegate
{
public:
    StyleDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
    {
    }

Waqar Ahmed's avatar
Waqar Ahmed committed
65
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
66
67
68
69
70
71
72
    {
        QStyledItemDelegate::paint(painter, option, index);

        if (!m_closeBtn) {
            return;
        }

73
        if (index.column() == 1 && option.state & QStyle::State_MouseOver) {
Waqar Ahmed's avatar
Waqar Ahmed committed
74
            const QIcon icon = QIcon::fromTheme(QStringLiteral("tab-close"));
75
            const int w = option.decorationSize.width();
Waqar Ahmed's avatar
Waqar Ahmed committed
76
            QRect iconRect(option.rect.right() - w, option.rect.top(), w, option.rect.height());
77
78
79
80
81
82
83
84
85
86
            icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter);
        }
    }

    void setShowCloseButton(bool s)
    {
        m_closeBtn = s;
    }

private:
Waqar Ahmed's avatar
Waqar Ahmed committed
87
    bool m_closeBtn = false;
88
89
};

90
// BEGIN KateFileTree
91

92
93
KateFileTree::KateFileTree(QWidget *parent)
    : QTreeView(parent)
94
{
Dominik Haumann's avatar
Dominik Haumann committed
95
96
97
    setAcceptDrops(false);
    setIndentation(12);
    setAllColumnsShowFocus(true);
98
    setFocusPolicy(Qt::NoFocus);
99
100
    setDragEnabled(true);
    setDragDropMode(QAbstractItemView::DragOnly);
101
    setSelectionBehavior(QAbstractItemView::SelectRows);
102
103
    // for hover close button
    viewport()->setAttribute(Qt::WA_Hover);
104
105

    setItemDelegate(new StyleDelegate(this));
106

107
    // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows)
108
109
    connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked);
    connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked);
110

Laurent Montel's avatar
Laurent Montel committed
111
    m_filelistReloadDocument = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action:inmenu", "Reloa&d"), this);
112
    connect(m_filelistReloadDocument, &QAction::triggered, this, &KateFileTree::slotDocumentReload);
Dominik Haumann's avatar
Dominik Haumann committed
113
    m_filelistReloadDocument->setWhatsThis(i18n("Reload selected document(s) from disk."));
114

Laurent Montel's avatar
Laurent Montel committed
115
    m_filelistCloseDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close"), this);
116
    connect(m_filelistCloseDocument, &QAction::triggered, this, &KateFileTree::slotDocumentClose);
Dominik Haumann's avatar
Dominik Haumann committed
117
    m_filelistCloseDocument->setWhatsThis(i18n("Close the current document."));
118

Laurent Montel's avatar
Laurent Montel committed
119
    m_filelistExpandRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Expand recursively"), this);
120
    connect(m_filelistExpandRecursive, &QAction::triggered, this, &KateFileTree::slotExpandRecursive);
121
122
    m_filelistExpandRecursive->setWhatsThis(i18n("Expand the file list sub tree recursively."));

Laurent Montel's avatar
Laurent Montel committed
123
    m_filelistCollapseRecursive = new QAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18nc("@action:inmenu", "Collapse recursively"), this);
124
    connect(m_filelistCollapseRecursive, &QAction::triggered, this, &KateFileTree::slotCollapseRecursive);
125
126
    m_filelistCollapseRecursive->setWhatsThis(i18n("Collapse the file list sub tree recursively."));

Laurent Montel's avatar
Laurent Montel committed
127
    m_filelistCloseOtherDocument = new QAction(QIcon::fromTheme(QStringLiteral("document-close")), i18nc("@action:inmenu", "Close Other"), this);
128
    connect(m_filelistCloseOtherDocument, &QAction::triggered, this, &KateFileTree::slotDocumentCloseOther);
Dominik Haumann's avatar
Dominik Haumann committed
129
    m_filelistCloseOtherDocument->setWhatsThis(i18n("Close other documents in this folder."));
130

Alexander Lohnau's avatar
Alexander Lohnau committed
131
132
    m_filelistOpenContainingFolder =
        new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18nc("@action:inmenu", "Open Containing Folder"), this);
133
134
135
    connect(m_filelistOpenContainingFolder, &QAction::triggered, this, &KateFileTree::slotOpenContainingFolder);
    m_filelistOpenContainingFolder->setWhatsThis(i18n("Open the folder this file is located in."));

136
    m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy-path")), i18nc("@action:inmenu", "Copy Location"), this);
137
    connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename);
138
    m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard."));
139

140
    m_filelistRenameFile = new QAction(QIcon::fromTheme(QStringLiteral("edit-rename")), i18nc("@action:inmenu", "Rename..."), this);
141
    connect(m_filelistRenameFile, &QAction::triggered, this, &KateFileTree::slotRenameFile);
142
    m_filelistRenameFile->setWhatsThis(i18n("Rename the selected file."));
143

144
    m_filelistPrintDocument = KStandardAction::print(this, &KateFileTree::slotPrintDocument, this);
Dominik Haumann's avatar
Dominik Haumann committed
145
    m_filelistPrintDocument->setWhatsThis(i18n("Print selected document."));
146

147
    m_filelistPrintDocumentPreview = KStandardAction::printPreview(this, &KateFileTree::slotPrintDocumentPreview, this);
Dominik Haumann's avatar
Dominik Haumann committed
148
    m_filelistPrintDocumentPreview->setWhatsThis(i18n("Show print preview of current document"));
149

150
    m_filelistDeleteDocument = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action:inmenu", "Delete"), this);
151
    connect(m_filelistDeleteDocument, &QAction::triggered, this, &KateFileTree::slotDocumentDelete);
Dominik Haumann's avatar
Dominik Haumann committed
152
    m_filelistDeleteDocument->setWhatsThis(i18n("Close and delete selected file from storage."));
153

154
    setupContextMenuActionGroups();
155

Laurent Montel's avatar
Laurent Montel committed
156
    m_resetHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18nc("@action:inmenu", "Clear History"), this);
157
    connect(m_resetHistory, &QAction::triggered, this, &KateFileTree::slotResetHistory);
Dominik Haumann's avatar
Dominik Haumann committed
158
    m_resetHistory->setWhatsThis(i18n("Clear edit/view history."));
159

Dominik Haumann's avatar
Dominik Haumann committed
160
161
162
163
    QPalette p = palette();
    p.setColor(QPalette::Inactive, QPalette::Highlight, p.color(QPalette::Active, QPalette::Highlight));
    p.setColor(QPalette::Inactive, QPalette::HighlightedText, p.color(QPalette::Active, QPalette::HighlightedText));
    setPalette(p);
164
165
}

166
KateFileTree::~KateFileTree() = default;
167

168
169
void KateFileTree::setModel(QAbstractItemModel *model)
{
170
171
    m_proxyModel = static_cast<KateFileTreeProxyModel *>(model);
    Q_ASSERT(m_proxyModel); // we don't really work with anything else
Dominik Haumann's avatar
Dominik Haumann committed
172
    QTreeView::setModel(model);
173
    m_sourceModel = static_cast<KateFileTreeModel *>(m_proxyModel->sourceModel());
174
175
176
177

    header()->hide();
    header()->setStretchLastSection(false);
    header()->setSectionResizeMode(0, QHeaderView::Stretch);
178

179
    const int minSize = m_hasCloseButton ? 16 : 1;
180
    header()->setMinimumSectionSize(minSize);
181
    header()->setSectionResizeMode(1, QHeaderView::Fixed);
182
    header()->resizeSection(1, minSize);
183
184

    // proxy never emits rowsMoved
Waqar Ahmed's avatar
Waqar Ahmed committed
185
    connect(m_proxyModel->sourceModel(), &QAbstractItemModel::rowsMoved, this, &KateFileTree::onRowsMoved);
186
187
188
189
}

void KateFileTree::onRowsMoved(const QModelIndex &, int, int, const QModelIndex &destination, int row)
{
190
    QModelIndex movedIndex = m_proxyModel->mapFromSource(m_sourceModel->index(row, 0, destination));
191
192
193
194
195
196
197
198
    // We moved stuff, make sure if child was expanded, we expand all parents too.
    if (movedIndex.isValid() && isExpanded(movedIndex) && !isExpanded(movedIndex.parent())) {
        QModelIndex movedParent = movedIndex.parent();
        while (movedParent.isValid() && !isExpanded(movedParent)) {
            expand(movedParent);
            movedParent = movedParent.parent();
        }
    }
199
200
201
202
203
204
}

void KateFileTree::setShowCloseButton(bool show)
{
    m_hasCloseButton = show;
    static_cast<StyleDelegate *>(itemDelegate())->setShowCloseButton(show);
205
206
207
208

    if (!header())
        return;

209
    const int minSize = show ? 16 : 1;
210
211
212
    header()->setMinimumSectionSize(minSize);
    header()->resizeSection(1, minSize);
    header()->viewport()->update();
213
214
}

215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
void KateFileTree::setupContextMenuActionGroups()
{
    QActionGroup *modeGroup = new QActionGroup(this);

    m_treeModeAction = setupOption(modeGroup,
                                   QIcon::fromTheme(QStringLiteral("view-list-tree")),
                                   i18nc("@action:inmenu", "Tree Mode"),
                                   i18n("Set view style to Tree Mode"),
                                   &KateFileTree::slotTreeMode,
                                   Qt::Checked);

    m_listModeAction = setupOption(modeGroup,
                                   QIcon::fromTheme(QStringLiteral("view-list-text")),
                                   i18nc("@action:inmenu", "List Mode"),
                                   i18n("Set view style to List Mode"),
                                   &KateFileTree::slotListMode);

    QActionGroup *sortGroup = new QActionGroup(this);

    m_sortByFile = setupOption(sortGroup,
                               QIcon(),
                               i18nc("@action:inmenu sorting option", "Document Name"),
                               i18n("Sort by Document Name"),
                               &KateFileTree::slotSortName,
                               Qt::Checked);

    m_sortByPath =
        setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), &KateFileTree::slotSortPath);

    m_sortByOpeningOrder = setupOption(sortGroup,
                                       QIcon(),
                                       i18nc("@action:inmenu sorting option", "Opening Order"),
                                       i18n("Sort by Opening Order"),
                                       &KateFileTree::slotSortOpeningOrder);
}

QAction *KateFileTree::setupOption(QActionGroup *group,
                                   const QIcon &icon,
                                   const QString &text,
                                   const QString &whatsThis,
                                   const Func &slot,
                                   Qt::CheckState checked /* = Qt::Unchecked */)
257
{
258
    QAction *new_action = new QAction(icon, text, this);
Dominik Haumann's avatar
Dominik Haumann committed
259
260
261
    new_action->setWhatsThis(whatsThis);
    new_action->setActionGroup(group);
    new_action->setCheckable(true);
262
263
    new_action->setChecked(checked == Qt::Checked);
    connect(new_action, &QAction::triggered, this, slot);
Dominik Haumann's avatar
Dominik Haumann committed
264
    return new_action;
265
266
267
268
}

void KateFileTree::slotListMode()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
269
    Q_EMIT viewModeChanged(true);
270
271
272
273
}

void KateFileTree::slotTreeMode()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
274
    Q_EMIT viewModeChanged(false);
275
276
277
278
}

void KateFileTree::slotSortName()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
279
    Q_EMIT sortRoleChanged(Qt::DisplayRole);
280
281
282
283
}

void KateFileTree::slotSortPath()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
284
    Q_EMIT sortRoleChanged(KateFileTreeModel::PathRole);
285
286
287
288
}

void KateFileTree::slotSortOpeningOrder()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
289
    Q_EMIT sortRoleChanged(KateFileTreeModel::OpeningOrderRole);
290
291
}

Dominik Haumann's avatar
Dominik Haumann committed
292
void KateFileTree::slotCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
293
{
Dominik Haumann's avatar
Dominik Haumann committed
294
295
296
297
298
    Q_UNUSED(previous);
    if (!current.isValid()) {
        return;
    }

299
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(current);
Dominik Haumann's avatar
Dominik Haumann committed
300
301
302
    if (doc) {
        m_previouslySelected = current;
    }
303
304
}

Dominik Haumann's avatar
Dominik Haumann committed
305
void KateFileTree::mouseClicked(const QModelIndex &index)
306
{
307
308
309
310
311
312
313
314
315
316
317
318
    const bool closeButtonClicked = m_hasCloseButton && index.column() == 1;

    if (m_proxyModel->isDir(index)) {
        if (closeButtonClicked) {
            const QList<KTextEditor::Document *> list = m_proxyModel->docTreeFromIndex(index);
            closeDocs(list);
        }
        return;
    }

    if (auto *doc = m_proxyModel->docFromIndex(index)) {
        if (closeButtonClicked) {
319
            closeDocs({doc});
320
321
            return;
        }
Christoph Cullmann's avatar
Christoph Cullmann committed
322
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
323
    }
324
325
}

Dominik Haumann's avatar
Dominik Haumann committed
326
void KateFileTree::contextMenuEvent(QContextMenuEvent *event)
327
{
Dominik Haumann's avatar
Dominik Haumann committed
328
    m_indexContextMenu = selectionModel()->currentIndex();
329

330
    selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
331

332
    const bool listMode = m_sourceModel->listMode();
Dominik Haumann's avatar
Dominik Haumann committed
333
334
    m_treeModeAction->setChecked(!listMode);
    m_listModeAction->setChecked(listMode);
335

336
    const int sortRole = m_proxyModel->sortRole();
Dominik Haumann's avatar
Dominik Haumann committed
337
338
339
    m_sortByFile->setChecked(sortRole == Qt::DisplayRole);
    m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole);
    m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole);
340

341
    KTextEditor::Document *doc = docFromIndex(m_indexContextMenu);
342
    const bool isFile = (nullptr != doc);
343

Dominik Haumann's avatar
Dominik Haumann committed
344
345
346
    QMenu menu;
    if (isFile) {
        QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With"));
347
        openWithMenu->setIcon(QIcon::fromTheme(QStringLiteral("system-run")));
Ahmad Samir's avatar
Ahmad Samir committed
348
349
350
        connect(openWithMenu, &QMenu::aboutToShow, this, [this, openWithMenu]() {
            slotFixOpenWithMenu(openWithMenu);
        });
351
        connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction);
352

353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
        menu.addSeparator();
        menu.addAction(m_filelistCopyFilename);
        menu.addAction(m_filelistRenameFile);
        menu.addAction(m_filelistDeleteDocument);
        menu.addAction(m_filelistReloadDocument);

        menu.addSeparator();
        menu.addAction(m_filelistOpenContainingFolder);

        menu.addSeparator();
        menu.addAction(m_filelistCloseDocument);
        menu.addAction(m_filelistCloseOtherDocument);

        menu.addSeparator();
        menu.addAction(m_filelistPrintDocument);
        menu.addAction(m_filelistPrintDocumentPreview);

370
        const bool hasFileName = doc->url().isValid();
371
        m_filelistOpenContainingFolder->setEnabled(hasFileName);
372
373
374
        m_filelistCopyFilename->setEnabled(hasFileName);
        m_filelistRenameFile->setEnabled(hasFileName);
        m_filelistDeleteDocument->setEnabled(hasFileName);
375
376
377
378
379
380
381
382
383
    } else {
        menu.addAction(m_filelistReloadDocument);

        menu.addSeparator();
        menu.addAction(m_filelistCloseDocument);

        menu.addSeparator();
        menu.addAction(m_filelistExpandRecursive);
        menu.addAction(m_filelistCollapseRecursive);
Dominik Haumann's avatar
Dominik Haumann committed
384
    }
385

Dominik Haumann's avatar
Dominik Haumann committed
386
387
388
389
    menu.addSeparator();
    QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode"));
    view_menu->addAction(m_treeModeAction);
    view_menu->addAction(m_listModeAction);
390

391
    QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By"));
Dominik Haumann's avatar
Dominik Haumann committed
392
393
394
    sort_menu->addAction(m_sortByFile);
    sort_menu->addAction(m_sortByPath);
    sort_menu->addAction(m_sortByOpeningOrder);
395

Dominik Haumann's avatar
Dominik Haumann committed
396
    menu.addAction(m_resetHistory);
397

Dominik Haumann's avatar
Dominik Haumann committed
398
    menu.exec(viewport()->mapToGlobal(event->pos()));
399

Dominik Haumann's avatar
Dominik Haumann committed
400
    if (m_previouslySelected.isValid()) {
401
        selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
Dominik Haumann's avatar
Dominik Haumann committed
402
    }
403

Dominik Haumann's avatar
Dominik Haumann committed
404
    event->accept();
405
406
}

Ahmad Samir's avatar
Ahmad Samir committed
407
void KateFileTree::slotFixOpenWithMenu(QMenu *menu)
408
{
Dominik Haumann's avatar
Dominik Haumann committed
409
410
    menu->clear();

411
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
Dominik Haumann's avatar
Dominik Haumann committed
412
413
414
415
416
417
418
419
    if (!doc) {
        return;
    }

    // get a list of appropriate services.
    QMimeDatabase db;
    QMimeType mime = db.mimeTypeForName(doc->mimeType());

Nicolas Fella's avatar
Nicolas Fella committed
420
    const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
Dominik Haumann's avatar
Dominik Haumann committed
421
    // for each one, insert a menu item...
422
    for (const auto &service : offers) {
Dominik Haumann's avatar
Dominik Haumann committed
423
424
425
        if (service->name() == QLatin1String("Kate")) {
            continue;
        }
426
        QAction *a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
Dominik Haumann's avatar
Dominik Haumann committed
427
428
429
        a->setData(service->entryPath());
    }
    // append "Other..." to call the KDE "open with" dialog.
430
431
    QAction *other = menu->addAction(i18n("&Other..."));
    other->setData(QString());
432
433
}

Dominik Haumann's avatar
Dominik Haumann committed
434
void KateFileTree::slotOpenWithMenuAction(QAction *a)
435
{
436
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
Dominik Haumann's avatar
Dominik Haumann committed
437
438
439
440
    if (!doc) {
        return;
    }

Ahmad Samir's avatar
Ahmad Samir committed
441
    const QList<QUrl> list({doc->url()});
Dominik Haumann's avatar
Dominik Haumann committed
442

Ahmad Samir's avatar
Ahmad Samir committed
443
444
445
446
447
448
    KService::Ptr app = KService::serviceByDesktopPath(a->data().toString());
    // If app is null, ApplicationLauncherJob will invoke the open-with dialog
    auto *job = new KIO::ApplicationLauncherJob(app);
    job->setUrls(list);
    job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
    job->start();
449
450
}

Christoph Cullmann's avatar
Christoph Cullmann committed
451
Q_DECLARE_METATYPE(QList<KTextEditor::Document *>)
452

453
454
void KateFileTree::slotDocumentClose()
{
Dominik Haumann's avatar
Dominik Haumann committed
455
    m_previouslySelected = QModelIndex();
456
457
458
459
460
461
462
463
464
465
    const QList<KTextEditor::Document *> closingDocuments = docTreeFromIndex(m_indexContextMenu);
    closeDocs(closingDocuments);
}

void KateFileTree::addChildrenTolist(const QModelIndex &index, QList<QPersistentModelIndex> *worklist)
{
    const int count = m_proxyModel->rowCount(index);
    worklist->reserve(worklist->size() + count);
    for (int i = 0; i < count; ++i) {
        worklist->append(m_proxyModel->index(i, 0, index));
Dominik Haumann's avatar
Dominik Haumann committed
466
    }
467
468
}

469
470
void KateFileTree::slotExpandRecursive()
{
471
    if (!m_indexContextMenu.isValid()) {
472
473
474
475
        return;
    }

    // Work list for DFS walk over sub tree
476
    QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
477

478
    while (!worklist.isEmpty()) {
479
480
481
482
483
484
        QPersistentModelIndex index = worklist.takeLast();

        // Expand current item
        expand(index);

        // Append all children of current item
485
        addChildrenTolist(index, &worklist);
486
487
488
489
490
    }
}

void KateFileTree::slotCollapseRecursive()
{
491
    if (!m_indexContextMenu.isValid()) {
492
493
494
495
        return;
    }

    // Work list for DFS walk over sub tree
496
    QList<QPersistentModelIndex> worklist = {m_indexContextMenu};
497

498
    while (!worklist.isEmpty()) {
499
500
501
502
503
504
        QPersistentModelIndex index = worklist.takeLast();

        // Expand current item
        collapse(index);

        // Prepend all children of current item
505
        addChildrenTolist(index, &worklist);
506
507
508
    }
}

509
510
void KateFileTree::slotDocumentCloseOther()
{
511
512
    QList<KTextEditor::Document *> closingDocuments = m_proxyModel->docTreeFromIndex(m_indexContextMenu.parent());
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
Dominik Haumann's avatar
Dominik Haumann committed
513
    closingDocuments.removeOne(doc);
514
    closeDocs(closingDocuments);
515
516
517
518
}

void KateFileTree::slotDocumentReload()
{
519
520
    const QList<KTextEditor::Document *> docs = docTreeFromIndex(m_indexContextMenu);
    for (auto *doc : docs) {
Dominik Haumann's avatar
Dominik Haumann committed
521
522
        doc->documentReload();
    }
523
524
}

525
526
void KateFileTree::slotOpenContainingFolder()
{
527
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
528
529
530
531
532
    if (doc) {
        KIO::highlightInFileManager({doc->url()});
    }
}

533
534
void KateFileTree::slotCopyFilename()
{
535
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
536
537
538
539

    // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here
    //       (make sure that the mentioned bug 381052 does not reappear)

Dominik Haumann's avatar
Dominik Haumann committed
540
    if (doc) {
541
        const QUrl url = doc->url();
542
        // ensure we prefer native separators, bug 381052
543
        QApplication::clipboard()->setText(url.isLocalFile() ? QDir::toNativeSeparators(url.toLocalFile()) : url.url());
Dominik Haumann's avatar
Dominik Haumann committed
544
    }
545
546
}

547
548
void KateFileTree::slotRenameFile()
{
549
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
550
551
552

    // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here

553
554
555
556
557
558
559
    if (!doc) {
        return;
    }

    const QUrl oldFileUrl = doc->url();
    const QString oldFileName = doc->url().fileName();
    bool ok;
560

561
    QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok);
562
563
564
565
566
    if (!ok) {
        return;
    }

    QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
567
    newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName);
568
569
570
571
572
573
574
575
576
577
578

    if (!newFileUrl.isValid()) {
        return;
    }

    if (!doc->closeUrl()) {
        return;
    }

    doc->waitSaveComplete();

579
    KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl);
580
    QSharedPointer<QMetaObject::Connection> sc(new QMetaObject::Connection());
581
582
583
584
585
    auto success = [doc, sc](KIO::Job *, const QUrl &, const QUrl &realNewFileUrl, const QDateTime &, bool, bool) {
        doc->openUrl(realNewFileUrl);
        doc->documentSavedOrUploaded(doc, true);
        QObject::disconnect(*sc);
    };
586
587
588
589
590
    *sc = connect(job, &KIO::CopyJob::copyingDone, doc, success);

    if (!job->exec()) {
        KMessageBox::sorry(this, i18n("File \"%1\" could not be moved to \"%2\"", oldFileUrl.toDisplayString(), newFileUrl.toDisplayString()));
        doc->openUrl(oldFileUrl);
591
592
    }
}
593

594
595
void KateFileTree::slotDocumentFirst()
{
596
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(0, 0));
Dominik Haumann's avatar
Dominik Haumann committed
597
    if (doc) {
Christoph Cullmann's avatar
Christoph Cullmann committed
598
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
599
    }
600
601
602
603
}

void KateFileTree::slotDocumentLast()
{
604
605
    int count = m_proxyModel->rowCount(m_proxyModel->parent(currentIndex()));
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_proxyModel->index(count - 1, 0));
Dominik Haumann's avatar
Dominik Haumann committed
606
    if (doc) {
Christoph Cullmann's avatar
Christoph Cullmann committed
607
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
608
    }
609
610
}

611
612
void KateFileTree::slotDocumentPrev()
{
Dominik Haumann's avatar
Dominik Haumann committed
613
614
615
616
617
618
    QModelIndex current_index = currentIndex();
    QModelIndex prev;

    // scan up the tree skipping any dir nodes
    while (current_index.isValid()) {
        if (current_index.row() > 0) {
619
            current_index = m_proxyModel->sibling(current_index.row() - 1, current_index.column(), current_index);
Dominik Haumann's avatar
Dominik Haumann committed
620
621
622
623
            if (!current_index.isValid()) {
                break;
            }

624
            if (m_proxyModel->isDir(current_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
625
                // try and select the last child in this parent
626
627
628
                int children = m_proxyModel->rowCount(current_index);
                current_index = m_proxyModel->index(children - 1, 0, current_index);
                if (m_proxyModel->isDir(current_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
629
                    // since we're a dir, keep going
630
631
632
                    while (m_proxyModel->isDir(current_index)) {
                        children = m_proxyModel->rowCount(current_index);
                        current_index = m_proxyModel->index(children - 1, 0, current_index);
Dominik Haumann's avatar
Dominik Haumann committed
633
634
                    }

635
                    if (!m_proxyModel->isDir(current_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
636
637
638
639
640
641
642
643
644
645
646
647
648
649
                        prev = current_index;
                        break;
                    }

                    continue;
                } else {
                    // we're the previous file, set prev
                    prev = current_index;
                    break;
                }
            } else { // found document item
                prev = current_index;
                break;
            }
650
        } else {
Dominik Haumann's avatar
Dominik Haumann committed
651
            // just select the parent, the logic above will handle the rest
652
            current_index = m_proxyModel->parent(current_index);
Dominik Haumann's avatar
Dominik Haumann committed
653
654
655
            if (!current_index.isValid()) {
                // paste the root node here, try and wrap around

656
657
                int children = m_proxyModel->rowCount(current_index);
                QModelIndex last_index = m_proxyModel->index(children - 1, 0, current_index);
Dominik Haumann's avatar
Dominik Haumann committed
658
659
660
661
                if (!last_index.isValid()) {
                    break;
                }

662
                if (m_proxyModel->isDir(last_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
663
                    // last node is a dir, select last child row
664
665
                    int last_children = m_proxyModel->rowCount(last_index);
                    prev = m_proxyModel->index(last_children - 1, 0, last_index);
Dominik Haumann's avatar
Dominik Haumann committed
666
667
668
669
670
671
672
673
                    // bug here?
                    break;
                } else {
                    // got last file node
                    prev = last_index;
                    break;
                }
            }
674
675
676
        }
    }

Dominik Haumann's avatar
Dominik Haumann committed
677
    if (prev.isValid()) {
678
        KTextEditor::Document *doc = m_proxyModel->docFromIndex(prev);
Christoph Cullmann's avatar
Christoph Cullmann committed
679
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
680
    }
681
682
683
684
}

void KateFileTree::slotDocumentNext()
{
Dominik Haumann's avatar
Dominik Haumann committed
685
    QModelIndex current_index = currentIndex();
686
    int parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
Dominik Haumann's avatar
Dominik Haumann committed
687
688
689
690
691
    QModelIndex next;

    // scan down the tree skipping any dir nodes
    while (current_index.isValid()) {
        if (current_index.row() < parent_row_count - 1) {
692
            current_index = m_proxyModel->sibling(current_index.row() + 1, current_index.column(), current_index);
Dominik Haumann's avatar
Dominik Haumann committed
693
694
            if (!current_index.isValid()) {
                break;
695
            }
696

697
            if (m_proxyModel->isDir(current_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
698
                // we have a dir node
699
700
                while (m_proxyModel->isDir(current_index)) {
                    current_index = m_proxyModel->index(0, 0, current_index);
Dominik Haumann's avatar
Dominik Haumann committed
701
702
                }

703
                parent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(current_index));
Dominik Haumann's avatar
Dominik Haumann committed
704

705
                if (!m_proxyModel->isDir(current_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
706
707
708
709
710
711
712
713
714
                    next = current_index;
                    break;
                }
            } else { // found document item
                next = current_index;
                break;
            }
        } else {
            // select the parent's next sibling
715
716
            QModelIndex parent_index = m_proxyModel->parent(current_index);
            int grandparent_row_count = m_proxyModel->rowCount(m_proxyModel->parent(parent_index));
Dominik Haumann's avatar
Dominik Haumann committed
717
718
719
720
721
722
723

            current_index = parent_index;
            parent_row_count = grandparent_row_count;

            // at least if we're not past the last node
            if (!current_index.isValid()) {
                // paste the root node here, try and wrap around
724
                QModelIndex last_index = m_proxyModel->index(0, 0, QModelIndex());
Dominik Haumann's avatar
Dominik Haumann committed
725
726
727
728
                if (!last_index.isValid()) {
                    break;
                }

729
                if (m_proxyModel->isDir(last_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
730
                    // last node is a dir, select first child row
731
732
                    while (m_proxyModel->isDir(last_index)) {
                        if (m_proxyModel->rowCount(last_index)) {
Dominik Haumann's avatar
Dominik Haumann committed
733
                            // has children, select first
734
                            last_index = m_proxyModel->index(0, 0, last_index);
Dominik Haumann's avatar
Dominik Haumann committed
735
736
737
738
739
740
741
742
743
744
745
                        }
                    }

                    next = last_index;
                    break;
                } else {
                    // got first file node
                    next = last_index;
                    break;
                }
            }
746
747
748
        }
    }

Dominik Haumann's avatar
Dominik Haumann committed
749
    if (next.isValid()) {
750
        KTextEditor::Document *doc = m_proxyModel->docFromIndex(next);
Christoph Cullmann's avatar
Christoph Cullmann committed
751
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
752
    }
753
}
754
755
756

void KateFileTree::slotPrintDocument()
{
757
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
758

Dominik Haumann's avatar
Dominik Haumann committed
759
760
761
    if (!doc) {
        return;
    }
762

Dominik Haumann's avatar
Dominik Haumann committed
763
    doc->print();
764
765
766
767
}

void KateFileTree::slotPrintDocumentPreview()
{
768
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
769

Dominik Haumann's avatar
Dominik Haumann committed
770
771
772
    if (!doc) {
        return;
    }
773

Dominik Haumann's avatar
Dominik Haumann committed
774
    doc->printPreview();
775
}
776
777
778

void KateFileTree::slotResetHistory()
{
779
    m_sourceModel->resetHistory();
780
781
}

782
783
void KateFileTree::slotDocumentDelete()
{
784
    KTextEditor::Document *doc = m_proxyModel->docFromIndex(m_indexContextMenu);
785

786
787
    // TODO: the following code was improved in kate/katefileactions.cpp and should be reused here

Dominik Haumann's avatar
Dominik Haumann committed
788
789
790
    if (!doc) {
        return;
    }
791

Dominik Haumann's avatar
Dominik Haumann committed
792
    QUrl url = doc->url();
793

Alexander Lohnau's avatar
Alexander Lohnau committed
794
795
796
    bool go = (KMessageBox::warningContinueCancel(this,
                                                  i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()),
                                                  i18n("Delete file?"),
797
798
                                                  KStandardGuiItem::del(),
                                                  KStandardGuiItem::cancel(),
Alexander Lohnau's avatar
Alexander Lohnau committed
799
800
                                                  QStringLiteral("filetreedeletefile"))
               == KMessageBox::Continue);
801

Dominik Haumann's avatar
Dominik Haumann committed
802
803
804
    if (!go) {
        return;
    }
805

806
    if (!closeDocs({doc})) {
Dominik Haumann's avatar
Dominik Haumann committed
807
808
        return; // no extra message, the internals of ktexteditor should take care of that.
    }
809

Dominik Haumann's avatar
Dominik Haumann committed
810
811
812
813
814
    if (url.isValid()) {
        KIO::DeleteJob *job = KIO::del(url);
        if (!job->exec()) {
            KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString()));
        }
815
816
817
    }
}

818
// END KateFileTree