katefiletree.cpp 27.3 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

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

41
42
43
44
45
46
47
48
class StyleDelegate : public QStyledItemDelegate
{
public:
    StyleDelegate(QObject *parent = nullptr)
        : QStyledItemDelegate(parent)
    {
    }

Waqar Ahmed's avatar
Waqar Ahmed committed
49
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
50
51
52
53
54
55
56
57
58
    {
        QStyledItemDelegate::paint(painter, option, index);

        if (!m_closeBtn) {
            return;
        }

        auto doc = index.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
        if (doc && index.column() == 1 && option.state & QStyle::State_MouseOver) {
Waqar Ahmed's avatar
Waqar Ahmed committed
59
60
61
            const QIcon icon = QIcon::fromTheme(QStringLiteral("tab-close"));
            int w = option.decorationSize.width();
            QRect iconRect(option.rect.right() - w, option.rect.top(), w, option.rect.height());
62
63
64
65
66
67
68
69
70
71
            icon.paint(painter, iconRect, Qt::AlignRight | Qt::AlignVCenter);
        }
    }

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

private:
Waqar Ahmed's avatar
Waqar Ahmed committed
72
    bool m_closeBtn = false;
73
74
};

75
// BEGIN KateFileTree
76

77
78
KateFileTree::KateFileTree(QWidget *parent)
    : QTreeView(parent)
79
{
Dominik Haumann's avatar
Dominik Haumann committed
80
81
82
    setAcceptDrops(false);
    setIndentation(12);
    setAllColumnsShowFocus(true);
83
    setFocusPolicy(Qt::NoFocus);
84
85
    setDragEnabled(true);
    setDragDropMode(QAbstractItemView::DragOnly);
86
87
88
    setSelectionBehavior(QAbstractItemView::SelectRows);

    setItemDelegate(new StyleDelegate(this));
89

90
    // handle activated (e.g. for pressing enter) + clicked (to avoid to need to do double-click e.g. on Windows)
91
92
    connect(this, &KateFileTree::activated, this, &KateFileTree::mouseClicked);
    connect(this, &KateFileTree::clicked, this, &KateFileTree::mouseClicked);
93

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

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

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

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

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

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

Laurent Montel's avatar
Laurent Montel committed
119
    m_filelistCopyFilename = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy File Path"), this);
120
    connect(m_filelistCopyFilename, &QAction::triggered, this, &KateFileTree::slotCopyFilename);
121
    m_filelistCopyFilename->setWhatsThis(i18n("Copy path and filename to the clipboard."));
122

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

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

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

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

Dominik Haumann's avatar
Dominik Haumann committed
137
    QActionGroup *modeGroup = new QActionGroup(this);
138

Alexander Lohnau's avatar
Alexander Lohnau committed
139
140
141
142
143
144
    m_treeModeAction = setupOption(modeGroup,
                                   QIcon::fromTheme(QStringLiteral("view-list-tree")),
                                   i18nc("@action:inmenu", "Tree Mode"),
                                   i18n("Set view style to Tree Mode"),
                                   SLOT(slotTreeMode()),
                                   true);
145

Alexander Lohnau's avatar
Alexander Lohnau committed
146
147
148
149
150
151
    m_listModeAction = setupOption(modeGroup,
                                   QIcon::fromTheme(QStringLiteral("view-list-text")),
                                   i18nc("@action:inmenu", "List Mode"),
                                   i18n("Set view style to List Mode"),
                                   SLOT(slotListMode()),
                                   false);
152

Dominik Haumann's avatar
Dominik Haumann committed
153
    QActionGroup *sortGroup = new QActionGroup(this);
154

Alexander Lohnau's avatar
Alexander Lohnau committed
155
156
    m_sortByFile =
        setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Name"), i18n("Sort by Document Name"), SLOT(slotSortName()), true);
157

Alexander Lohnau's avatar
Alexander Lohnau committed
158
159
    m_sortByPath =
        setupOption(sortGroup, QIcon(), i18nc("@action:inmenu sorting option", "Document Path"), i18n("Sort by Document Path"), SLOT(slotSortPath()), false);
160

Alexander Lohnau's avatar
Alexander Lohnau committed
161
162
163
164
165
166
    m_sortByOpeningOrder = setupOption(sortGroup,
                                       QIcon(),
                                       i18nc("@action:inmenu sorting option", "Opening Order"),
                                       i18n("Sort by Opening Order"),
                                       SLOT(slotSortOpeningOrder()),
                                       false);
167

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

Dominik Haumann's avatar
Dominik Haumann committed
172
173
174
175
    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);
176
177
178
}

KateFileTree::~KateFileTree()
179
180
{
}
181

182
183
void KateFileTree::setModel(QAbstractItemModel *model)
{
Yuri Chornoivan's avatar
Yuri Chornoivan committed
184
    Q_ASSERT(qobject_cast<KateFileTreeProxyModel *>(model)); // we don't really work with anything else
Dominik Haumann's avatar
Dominik Haumann committed
185
    QTreeView::setModel(model);
186
187
188
189

    header()->hide();
    header()->setStretchLastSection(false);
    header()->setSectionResizeMode(0, QHeaderView::Stretch);
Waqar Ahmed's avatar
Waqar Ahmed committed
190
    header()->setMinimumSectionSize(1);
191
    header()->setSectionResizeMode(1, QHeaderView::Fixed);
Waqar Ahmed's avatar
Waqar Ahmed committed
192
    header()->resizeSection(1, 1);
193
194
195
196
197
198
}

void KateFileTree::setShowCloseButton(bool show)
{
    m_hasCloseButton = show;
    static_cast<StyleDelegate *>(itemDelegate())->setShowCloseButton(show);
199
200
}

201
QAction *KateFileTree::setupOption(QActionGroup *group, const QIcon &icon, const QString &label, const QString &whatsThis, const char *slot, bool checked)
202
{
Dominik Haumann's avatar
Dominik Haumann committed
203
204
205
206
207
208
209
    QAction *new_action = new QAction(icon, label, this);
    new_action->setWhatsThis(whatsThis);
    new_action->setActionGroup(group);
    new_action->setCheckable(true);
    new_action->setChecked(checked);
    connect(new_action, SIGNAL(triggered()), this, slot);
    return new_action;
210
211
212
213
}

void KateFileTree::slotListMode()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
214
    Q_EMIT viewModeChanged(true);
215
216
217
218
}

void KateFileTree::slotTreeMode()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
219
    Q_EMIT viewModeChanged(false);
220
221
222
223
}

void KateFileTree::slotSortName()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
224
    Q_EMIT sortRoleChanged(Qt::DisplayRole);
225
226
227
228
}

void KateFileTree::slotSortPath()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
229
    Q_EMIT sortRoleChanged(KateFileTreeModel::PathRole);
230
231
232
233
}

void KateFileTree::slotSortOpeningOrder()
{
Christoph Cullmann's avatar
Christoph Cullmann committed
234
    Q_EMIT sortRoleChanged(KateFileTreeModel::OpeningOrderRole);
235
236
}

Dominik Haumann's avatar
Dominik Haumann committed
237
void KateFileTree::slotCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
238
{
Dominik Haumann's avatar
Dominik Haumann committed
239
240
241
242
243
244
245
246
247
    Q_UNUSED(previous);
    if (!current.isValid()) {
        return;
    }

    KTextEditor::Document *doc = model()->data(current, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
    if (doc) {
        m_previouslySelected = current;
    }
248
249
}

Dominik Haumann's avatar
Dominik Haumann committed
250
void KateFileTree::mouseClicked(const QModelIndex &index)
251
{
252
    if (auto doc = model()->data(index, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>()) {
253
254
255
256
        if (m_hasCloseButton && index.column() == 1) {
            KTextEditor::Editor::instance()->application()->closeDocuments({doc});
            return;
        }
Christoph Cullmann's avatar
Christoph Cullmann committed
257
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
258
    }
259
260
}

Dominik Haumann's avatar
Dominik Haumann committed
261
void KateFileTree::contextMenuEvent(QContextMenuEvent *event)
262
{
Dominik Haumann's avatar
Dominik Haumann committed
263
    m_indexContextMenu = selectionModel()->currentIndex();
264

265
    selectionModel()->setCurrentIndex(m_indexContextMenu, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
266

Dominik Haumann's avatar
Dominik Haumann committed
267
268
    KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
    KateFileTreeModel *ftm = static_cast<KateFileTreeModel *>(ftpm->sourceModel());
269

Dominik Haumann's avatar
Dominik Haumann committed
270
271
272
    bool listMode = ftm->listMode();
    m_treeModeAction->setChecked(!listMode);
    m_listModeAction->setChecked(listMode);
273

Dominik Haumann's avatar
Dominik Haumann committed
274
275
276
277
    int sortRole = ftpm->sortRole();
    m_sortByFile->setChecked(sortRole == Qt::DisplayRole);
    m_sortByPath->setChecked(sortRole == KateFileTreeModel::PathRole);
    m_sortByOpeningOrder->setChecked(sortRole == KateFileTreeModel::OpeningOrderRole);
278

Dominik Haumann's avatar
Dominik Haumann committed
279
    KTextEditor::Document *doc = m_indexContextMenu.data(KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
280
    const bool isFile = (nullptr != doc);
281

Dominik Haumann's avatar
Dominik Haumann committed
282
283
284
    QMenu menu;
    menu.addAction(m_filelistReloadDocument);
    menu.addAction(m_filelistCloseDocument);
285
286
    menu.addAction(m_filelistExpandRecursive);
    menu.addAction(m_filelistCollapseRecursive);
287

Dominik Haumann's avatar
Dominik Haumann committed
288
289
290
    if (isFile) {
        menu.addAction(m_filelistCloseOtherDocument);
        menu.addSeparator();
291
        menu.addAction(m_filelistOpenContainingFolder);
Dominik Haumann's avatar
Dominik Haumann committed
292
        menu.addAction(m_filelistCopyFilename);
293
        menu.addAction(m_filelistRenameFile);
Dominik Haumann's avatar
Dominik Haumann committed
294
295
296
        menu.addAction(m_filelistPrintDocument);
        menu.addAction(m_filelistPrintDocumentPreview);
        QMenu *openWithMenu = menu.addMenu(i18nc("@action:inmenu", "Open With"));
297
298
        connect(openWithMenu, &QMenu::aboutToShow, this, &KateFileTree::slotFixOpenWithMenu);
        connect(openWithMenu, &QMenu::triggered, this, &KateFileTree::slotOpenWithMenuAction);
299

300
        const bool hasFileName = doc->url().isValid();
301
        m_filelistOpenContainingFolder->setEnabled(hasFileName);
302
303
304
        m_filelistCopyFilename->setEnabled(hasFileName);
        m_filelistRenameFile->setEnabled(hasFileName);
        m_filelistDeleteDocument->setEnabled(hasFileName);
Dominik Haumann's avatar
Dominik Haumann committed
305
306
        menu.addAction(m_filelistDeleteDocument);
    }
307

Dominik Haumann's avatar
Dominik Haumann committed
308
309
310
311
    menu.addSeparator();
    QMenu *view_menu = menu.addMenu(i18nc("@action:inmenu", "View Mode"));
    view_menu->addAction(m_treeModeAction);
    view_menu->addAction(m_listModeAction);
312

313
    QMenu *sort_menu = menu.addMenu(QIcon::fromTheme(QStringLiteral("view-sort")), i18nc("@action:inmenu", "Sort By"));
Dominik Haumann's avatar
Dominik Haumann committed
314
315
316
    sort_menu->addAction(m_sortByFile);
    sort_menu->addAction(m_sortByPath);
    sort_menu->addAction(m_sortByOpeningOrder);
317

Dominik Haumann's avatar
Dominik Haumann committed
318
    menu.addAction(m_resetHistory);
319

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

Dominik Haumann's avatar
Dominik Haumann committed
322
    if (m_previouslySelected.isValid()) {
323
        selectionModel()->setCurrentIndex(m_previouslySelected, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
Dominik Haumann's avatar
Dominik Haumann committed
324
    }
325

Dominik Haumann's avatar
Dominik Haumann committed
326
    event->accept();
327
328
}

329
330
void KateFileTree::slotFixOpenWithMenu()
{
331
    QMenu *menu = static_cast<QMenu *>(sender());
Dominik Haumann's avatar
Dominik Haumann committed
332
333
334
335
336
337
338
339
340
341
342
    menu->clear();

    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
    if (!doc) {
        return;
    }

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

343
    QAction *a = nullptr;
Nicolas Fella's avatar
Nicolas Fella committed
344
    const KService::List offers = KApplicationTrader::queryByMimeType(mime.name());
Dominik Haumann's avatar
Dominik Haumann committed
345
    // for each one, insert a menu item...
346
    for (const auto &service : offers) {
Dominik Haumann's avatar
Dominik Haumann committed
347
348
349
350
351
352
353
354
355
        if (service->name() == QLatin1String("Kate")) {
            continue;
        }
        a = menu->addAction(QIcon::fromTheme(service->icon()), service->name());
        a->setData(service->entryPath());
    }
    // append "Other..." to call the KDE "open with" dialog.
    a = menu->addAction(i18n("&Other..."));
    a->setData(QString());
356
357
}

Dominik Haumann's avatar
Dominik Haumann committed
358
void KateFileTree::slotOpenWithMenuAction(QAction *a)
359
{
Dominik Haumann's avatar
Dominik Haumann committed
360
361
362
363
364
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
    if (!doc) {
        return;
    }

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

Ahmad Samir's avatar
Ahmad Samir committed
367
368
369
370
371
372
    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();
373
374
}

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

377
378
void KateFileTree::slotDocumentClose()
{
Dominik Haumann's avatar
Dominik Haumann committed
379
380
381
382
383
    m_previouslySelected = QModelIndex();
    QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole);
    if (!v.isValid()) {
        return;
    }
384
    QList<KTextEditor::Document *> closingDocuments = v.value<QList<KTextEditor::Document *>>();
Dominik Haumann's avatar
Dominik Haumann committed
385
    KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments);
386
387
}

388
389
void KateFileTree::slotExpandRecursive()
{
390
    if (!m_indexContextMenu.isValid()) {
391
392
393
394
        return;
    }

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

397
    while (!worklist.isEmpty()) {
398
399
400
401
402
403
        QPersistentModelIndex index = worklist.takeLast();

        // Expand current item
        expand(index);

        // Append all children of current item
404
        for (int i = 0; i < model()->rowCount(index); ++i) {
405
            worklist.append(model()->index(i, 0, index));
406
407
408
409
410
411
        }
    }
}

void KateFileTree::slotCollapseRecursive()
{
412
    if (!m_indexContextMenu.isValid()) {
413
414
415
416
        return;
    }

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

419
    while (!worklist.isEmpty()) {
420
421
422
423
424
425
        QPersistentModelIndex index = worklist.takeLast();

        // Expand current item
        collapse(index);

        // Prepend all children of current item
426
        for (int i = 0; i < model()->rowCount(index); ++i) {
427
            worklist.append(model()->index(i, 0, index));
428
429
430
431
        }
    }
}

432
433
void KateFileTree::slotDocumentCloseOther()
{
Dominik Haumann's avatar
Dominik Haumann committed
434
435
436
437
    QVariant v = model()->data(m_indexContextMenu.parent(), KateFileTreeModel::DocumentTreeRole);
    if (!v.isValid()) {
        return;
    }
438

439
    QList<KTextEditor::Document *> closingDocuments = v.value<QList<KTextEditor::Document *>>();
Dominik Haumann's avatar
Dominik Haumann committed
440
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
441

Dominik Haumann's avatar
Dominik Haumann committed
442
    closingDocuments.removeOne(doc);
443

Dominik Haumann's avatar
Dominik Haumann committed
444
    KTextEditor::Editor::instance()->application()->closeDocuments(closingDocuments);
445
446
447
448
}

void KateFileTree::slotDocumentReload()
{
Dominik Haumann's avatar
Dominik Haumann committed
449
450
451
452
453
    QVariant v = m_indexContextMenu.data(KateFileTreeModel::DocumentTreeRole);
    if (!v.isValid()) {
        return;
    }

454
455
    const QList<KTextEditor::Document *> docs = v.value<QList<KTextEditor::Document *>>();
    for (KTextEditor::Document *doc : docs) {
Dominik Haumann's avatar
Dominik Haumann committed
456
457
        doc->documentReload();
    }
458
459
}

460
461
462
463
464
465
466
467
void KateFileTree::slotOpenContainingFolder()
{
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
    if (doc) {
        KIO::highlightInFileManager({doc->url()});
    }
}

468
469
void KateFileTree::slotCopyFilename()
{
Dominik Haumann's avatar
Dominik Haumann committed
470
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
471
472
473
474

    // 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
475
    if (doc) {
476
477
478
479
480
481
        // ensure we prefer native separators, bug 381052
        if (doc->url().isLocalFile()) {
            QApplication::clipboard()->setText(QDir::toNativeSeparators(doc->url().toLocalFile()));
        } else {
            QApplication::clipboard()->setText(doc->url().url());
        }
Dominik Haumann's avatar
Dominik Haumann committed
482
    }
483
484
}

485
486
void KateFileTree::slotRenameFile()
{
487
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
488
489
490

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

491
492
493
494
495
496
497
    if (!doc) {
        return;
    }

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

499
    QString newFileName = QInputDialog::getText(this, i18n("Rename file"), i18n("New file name"), QLineEdit::Normal, oldFileName, &ok);
500
501
502
503
504
    if (!ok) {
        return;
    }

    QUrl newFileUrl = oldFileUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
505
    newFileUrl.setPath(newFileUrl.path() + QLatin1Char('/') + newFileName);
506
507
508
509
510
511
512
513
514
515
516

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

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

    doc->waitSaveComplete();

517
    KIO::CopyJob *job = KIO::move(oldFileUrl, newFileUrl);
518
    QSharedPointer<QMetaObject::Connection> sc(new QMetaObject::Connection());
519
520
521
522
523
    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);
    };
524
525
526
527
528
    *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);
529
530
    }
}
531

532
533
void KateFileTree::slotDocumentFirst()
{
534
    KTextEditor::Document *doc = model()->data(model()->index(0, 0), KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
Dominik Haumann's avatar
Dominik Haumann committed
535
    if (doc) {
Christoph Cullmann's avatar
Christoph Cullmann committed
536
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
537
    }
538
539
540
541
}

void KateFileTree::slotDocumentLast()
{
Dominik Haumann's avatar
Dominik Haumann committed
542
    int count = model()->rowCount(model()->parent(currentIndex()));
543
    KTextEditor::Document *doc = model()->data(model()->index(count - 1, 0), KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
Dominik Haumann's avatar
Dominik Haumann committed
544
    if (doc) {
Christoph Cullmann's avatar
Christoph Cullmann committed
545
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
546
    }
547
548
}

549
550
void KateFileTree::slotDocumentPrev()
{
Dominik Haumann's avatar
Dominik Haumann committed
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
    KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());

    QModelIndex current_index = currentIndex();
    QModelIndex prev;

    // scan up the tree skipping any dir nodes
    while (current_index.isValid()) {
        if (current_index.row() > 0) {
            current_index = ftpm->sibling(current_index.row() - 1, current_index.column(), current_index);
            if (!current_index.isValid()) {
                break;
            }

            if (ftpm->isDir(current_index)) {
                // try and select the last child in this parent
                int children = ftpm->rowCount(current_index);
                current_index = ftpm->index(children - 1, 0, current_index);
                if (ftpm->isDir(current_index)) {
                    // since we're a dir, keep going
                    while (ftpm->isDir(current_index)) {
                        children = ftpm->rowCount(current_index);
                        current_index = ftpm->index(children - 1, 0, current_index);
                    }

                    if (!ftpm->isDir(current_index)) {
                        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;
            }
590
        } else {
Dominik Haumann's avatar
Dominik Haumann committed
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
            // just select the parent, the logic above will handle the rest
            current_index = ftpm->parent(current_index);
            if (!current_index.isValid()) {
                // paste the root node here, try and wrap around

                int children = ftpm->rowCount(current_index);
                QModelIndex last_index = ftpm->index(children - 1, 0, current_index);
                if (!last_index.isValid()) {
                    break;
                }

                if (ftpm->isDir(last_index)) {
                    // last node is a dir, select last child row
                    int last_children = ftpm->rowCount(last_index);
                    prev = ftpm->index(last_children - 1, 0, last_index);
                    // bug here?
                    break;
                } else {
                    // got last file node
                    prev = last_index;
                    break;
                }
            }
614
615
616
        }
    }

Dominik Haumann's avatar
Dominik Haumann committed
617
618
    if (prev.isValid()) {
        KTextEditor::Document *doc = model()->data(prev, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
Christoph Cullmann's avatar
Christoph Cullmann committed
619
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
620
    }
621
622
623
624
}

void KateFileTree::slotDocumentNext()
{
Dominik Haumann's avatar
Dominik Haumann committed
625
626
627
628
629
630
631
632
633
634
635
636
    KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());

    QModelIndex current_index = currentIndex();
    int parent_row_count = ftpm->rowCount(ftpm->parent(current_index));
    QModelIndex next;

    // scan down the tree skipping any dir nodes
    while (current_index.isValid()) {
        if (current_index.row() < parent_row_count - 1) {
            current_index = ftpm->sibling(current_index.row() + 1, current_index.column(), current_index);
            if (!current_index.isValid()) {
                break;
637
            }
638

Dominik Haumann's avatar
Dominik Haumann committed
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
            if (ftpm->isDir(current_index)) {
                // we have a dir node
                while (ftpm->isDir(current_index)) {
                    current_index = ftpm->index(0, 0, current_index);
                }

                parent_row_count = ftpm->rowCount(ftpm->parent(current_index));

                if (!ftpm->isDir(current_index)) {
                    next = current_index;
                    break;
                }
            } else { // found document item
                next = current_index;
                break;
            }
        } else {
            // select the parent's next sibling
            QModelIndex parent_index = ftpm->parent(current_index);
            int grandparent_row_count = ftpm->rowCount(ftpm->parent(parent_index));

            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
                QModelIndex last_index = ftpm->index(0, 0, QModelIndex());
                if (!last_index.isValid()) {
                    break;
                }

                if (ftpm->isDir(last_index)) {
                    // last node is a dir, select first child row
                    while (ftpm->isDir(last_index)) {
                        if (ftpm->rowCount(last_index)) {
                            // has children, select first
                            last_index = ftpm->index(0, 0, last_index);
                        }
                    }

                    next = last_index;
                    break;
                } else {
                    // got first file node
                    next = last_index;
                    break;
                }
            }
688
689
690
        }
    }

Dominik Haumann's avatar
Dominik Haumann committed
691
692
    if (next.isValid()) {
        KTextEditor::Document *doc = model()->data(next, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
Christoph Cullmann's avatar
Christoph Cullmann committed
693
        Q_EMIT activateDocument(doc);
Dominik Haumann's avatar
Dominik Haumann committed
694
    }
695
}
696
697
698

void KateFileTree::slotPrintDocument()
{
Dominik Haumann's avatar
Dominik Haumann committed
699
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
700

Dominik Haumann's avatar
Dominik Haumann committed
701
702
703
    if (!doc) {
        return;
    }
704

Dominik Haumann's avatar
Dominik Haumann committed
705
    doc->print();
706
707
708
709
}

void KateFileTree::slotPrintDocumentPreview()
{
Dominik Haumann's avatar
Dominik Haumann committed
710
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
711

Dominik Haumann's avatar
Dominik Haumann committed
712
713
714
    if (!doc) {
        return;
    }
715

Dominik Haumann's avatar
Dominik Haumann committed
716
    doc->printPreview();
717
}
718
719
720

void KateFileTree::slotResetHistory()
{
Dominik Haumann's avatar
Dominik Haumann committed
721
722
723
    KateFileTreeProxyModel *ftpm = static_cast<KateFileTreeProxyModel *>(model());
    KateFileTreeModel *ftm = static_cast<KateFileTreeModel *>(ftpm->sourceModel());
    ftm->resetHistory();
724
725
}

726
727
void KateFileTree::slotDocumentDelete()
{
Dominik Haumann's avatar
Dominik Haumann committed
728
    KTextEditor::Document *doc = model()->data(m_indexContextMenu, KateFileTreeModel::DocumentRole).value<KTextEditor::Document *>();
729

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

Dominik Haumann's avatar
Dominik Haumann committed
732
733
734
    if (!doc) {
        return;
    }
735

Dominik Haumann's avatar
Dominik Haumann committed
736
    QUrl url = doc->url();
737

Alexander Lohnau's avatar
Alexander Lohnau committed
738
739
740
741
742
743
744
    bool go = (KMessageBox::warningContinueCancel(this,
                                                  i18n("Do you really want to delete file \"%1\" from storage?", url.toDisplayString()),
                                                  i18n("Delete file?"),
                                                  KStandardGuiItem::yes(),
                                                  KStandardGuiItem::no(),
                                                  QStringLiteral("filetreedeletefile"))
               == KMessageBox::Continue);
745

Dominik Haumann's avatar
Dominik Haumann committed
746
747
748
    if (!go) {
        return;
    }
749

Dominik Haumann's avatar
Dominik Haumann committed
750
751
752
    if (!KTextEditor::Editor::instance()->application()->closeDocument(doc)) {
        return; // no extra message, the internals of ktexteditor should take care of that.
    }
753

Dominik Haumann's avatar
Dominik Haumann committed
754
755
756
757
758
    if (url.isValid()) {
        KIO::DeleteJob *job = KIO::del(url);
        if (!job->exec()) {
            KMessageBox::sorry(this, i18n("File \"%1\" could not be deleted.", url.toDisplayString()));
        }
759
760
761
    }
}

762
// END KateFileTree