foldermodel.cpp 61.4 KB
Newer Older
1
/***************************************************************************
2
 *   Copyright (C) 2006 David Faure <faure@kde.org>                        *
3
 *   Copyright (C) 2008 Fredrik Höglund <fredrik@kde.org>                  *
4
 *   Copyright (C) 2008 Rafael Fernández López <ereslibre@kde.org>           *
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 *   Copyright (C) 2011 Marco Martin <mart@kde.org>                        *
 *   Copyright (C) 2014 by Eike Hein <hein@kde.org>                        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 ***************************************************************************/

#include "foldermodel.h"
25
26
#include "itemviewadapter.h"
#include "positioner.h"
Andras Mantia's avatar
Andras Mantia committed
27
#include "screenmapper.h"
28
29
30
31

#include <QApplication>
#include <QClipboard>
#include <QCollator>
32
33
34
#include <QDesktopWidget>
#include <QDrag>
#include <QImage>
35
#include <QItemSelectionModel>
36
#include <QMenu>
37
#include <QMimeData>
38
#include <QMimeDatabase>
39
40
41
42
#include <QPainter>
#include <QPixmap>
#include <QQuickItem>
#include <QQuickWindow>
43
44
#include <QTimer>
#include <QLoggingCategory>
45
46
#include <qplatformdefs.h>

47
#include <KDirWatch>
48
#include <KIO/DropJob>
49
50
#include <KAuthorized>
#include <KConfigGroup>
51
#include <KFileCopyToMenu>
52
53
#include <KFileItemActions>
#include <KFileItemListProperties>
54
#include <KNewFileMenu>
55
#include <KIO/DeleteJob>
56
#include <KIO/EmptyTrashJob>
57
#include <KIO/FileUndoManager>
58
#include <KIO/JobUiDelegate>
59
#include <KIO/Paste>
60
#include <KIO/PasteJob>
61
#include <KIO/RestoreJob>
62
#include <KLocalizedString>
63
#include <KPropertiesDialog>
64
#include <KSharedConfig>
65
#include <KShell>
66

67
68
#include <KCoreDirLister>
#include <KDirLister>
69
70
#include <KDesktopFile>
#include <KDirModel>
71
#include <KIO/CopyJob>
72
#include <KIO/Job>
73
#include <KProtocolInfo>
74
75
#include <KRun>

Andras Mantia's avatar
Andras Mantia committed
76
77
78
79
#include <Plasma/Applet>
#include <Plasma/Containment>
#include <Plasma/Corona>

80
81
82
83
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

84
85
Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel")

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
DirLister::DirLister(QObject *parent) : KDirLister(parent)
{
}

DirLister:: ~DirLister()
{
}

void DirLister::handleError(KIO::Job *job)
{
    if (!autoErrorHandlingEnabled()) {
        emit error(job->errorString());
        return;
    }

    KDirLister::handleError(job);
}

FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent),
105
    m_dirWatch(nullptr),
106
    m_dragInProgress(false),
107
    m_urlChangedWhileDragging(false),
108
    m_dropTargetPositionsCleanup(new QTimer(this)),
109
110
    m_previewGenerator(nullptr),
    m_viewAdapter(nullptr),
111
    m_actionCollection(this),
112
113
    m_newMenu(nullptr),
    m_fileItemActions(nullptr),
114
    m_usedByContainment(false),
115
    m_locked(true),
Eike Hein's avatar
Eike Hein committed
116
    m_sortMode(0),
117
118
119
120
121
    m_sortDesc(false),
    m_sortDirsFirst(true),
    m_parseDesktopFiles(false),
    m_previews(false),
    m_filterMode(NoFilter),
122
    m_filterPatternMatchAll(true),
123
124
    m_screenMapper(ScreenMapper::instance()),
    m_complete(false)
125
{
126
127
    //needed to pass the job around with qml
    qmlRegisterType<KIO::DropJob>();
128
129
    DirLister *dirLister = new DirLister(this);
    dirLister->setDelayedMimeTypes(true);
130
    dirLister->setAutoErrorHandlingEnabled(false, nullptr);
131
132
    connect(dirLister, &DirLister::error, this, &FolderModel::dirListFailed);
    connect(dirLister, &KCoreDirLister::itemsDeleted, this, &FolderModel::evictFromIsDirCache);
133
134
135

    connect(dirLister, &KCoreDirLister::started, this, std::bind(&FolderModel::setStatus, this, Status::Listing));

136
    void (KCoreDirLister::*myCompletedSignal)() = &KCoreDirLister::completed;
137
138
139
140
141
    QObject::connect(dirLister, myCompletedSignal, this, [this] {
        setStatus(Status::Ready);
        emit listingCompleted();
    });

142
    void (KCoreDirLister::*myCanceledSignal)() = &KCoreDirLister::canceled;
143
144
145
146
    QObject::connect(dirLister, myCanceledSignal, this, [this] {
        setStatus(Status::Canceled);
        emit listingCanceled();
    });
147
148
149

    m_dirModel = new KDirModel(this);
    m_dirModel->setDirLister(dirLister);
Eike Hein's avatar
Eike Hein committed
150
    m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable);
151

152
153
154
155
156
    /*
     * position dropped items at the desired target position
     * delay this via queued connection, such that the row is available and can be mapped
     * when we emit the move request
     */
157
    connect(this, &QAbstractItemModel::rowsInserted,
158
159
            this, [this](const QModelIndex &parent, int first, int last) {
        for (int i = first; i <= last; ++i) {
160
161
            const auto idx = index(i, 0, parent);
            const auto url = itemForIndex(idx).url();
162
163
164
165
166
167
168
169
            auto it = m_dropTargetPositions.find(url.fileName());
            if (it != m_dropTargetPositions.end()) {
                const auto pos = it.value();
                m_dropTargetPositions.erase(it);
                setSortMode(-1);
                emit move(pos.x(), pos.y(), {url});
            }
        }
170
    });
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
    /*
     * Dropped files may not actually show up as new files, e.g. when we overwrite
     * an existing file. Or files that fail to be listed by the dirLister, or...
     * To ensure we don't grow the map indefinitely, clean it up periodically.
     * The cleanup timer is (re)started whenever we modify the map. We use a quite
     * high interval of 10s. This should ensure, that we don't accidentally wipe
     * the mapping when we actually still want to use it. Since the time between
     * adding an entry in the map and it showing up in the model should be
     * small, this should rarely, if ever happen.
     */
    m_dropTargetPositionsCleanup->setInterval(10000);
    m_dropTargetPositionsCleanup->setSingleShot(true);
    connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() {
        if (!m_dropTargetPositions.isEmpty()) {
            qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions;
            m_dropTargetPositions.clear();
        }
    });

190
    m_selectionModel = new QItemSelectionModel(this, this);
191
192
    connect(m_selectionModel, &QItemSelectionModel::selectionChanged,
            this, &FolderModel::selectionChanged);
193
194
195
196
197
198
199

    setSourceModel(m_dirModel);

    setSortLocaleAware(true);
    setFilterCaseSensitivity(Qt::CaseInsensitive);
    setDynamicSortFilter(true);

Eike Hein's avatar
Eike Hein committed
200
    sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
201

202
203
204
205
206
    createActions();
}

FolderModel::~FolderModel()
{
207
    if (m_usedByContainment) {
Andras Mantia's avatar
Andras Mantia committed
208
209
210
        // disconnect so we don't handle signals from the screen mapper when
        // removeScreen is called
        m_screenMapper->disconnect(this);
211
        m_screenMapper->removeScreen(m_screen, resolvedUrl());
Andras Mantia's avatar
Andras Mantia committed
212
    }
213
214
215
216
}

QHash< int, QByteArray > FolderModel::roleNames() const
{
217
218
219
220
221
222
    return staticRoleNames();
}

QHash< int, QByteArray > FolderModel::staticRoleNames()
{
    QHash<int, QByteArray> roleNames;
223
224
    roleNames[Qt::DisplayRole] = "display";
    roleNames[Qt::DecorationRole] = "decoration";
225
    roleNames[BlankRole] = "blank";
226
    roleNames[OverlaysRole] = "overlays";
227
228
    roleNames[SelectedRole] = "selected";
    roleNames[IsDirRole] = "isDir";
229
    roleNames[IsLinkRole] = "isLink";
230
    roleNames[IsHiddenRole] = "isHidden";
231
    roleNames[UrlRole] = "url";
232
    roleNames[LinkDestinationUrl] = "linkDestinationUrl";
233
234
    roleNames[SizeRole] = "size";
    roleNames[TypeRole] = "type";
235
236
237
238

    return roleNames;
}

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
void FolderModel::classBegin()
{
}

void FolderModel::componentComplete()
{
    m_complete = true;
    invalidate();
}

void FolderModel::invalidateIfComplete()
{
    if (!m_complete) {
        return;
    }

    invalidate();
}

void FolderModel::invalidateFilterIfComplete()
{
    if (!m_complete) {
        return;
    }

    invalidateFilter();
}

267
268
void FolderModel::newFileMenuItemCreated(const QUrl &url)
{
269
    if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
270
        m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal);
271
272
273
        m_dropTargetPositions.insert(url.fileName(), m_menuPosition);
        m_menuPosition = {};
        m_dropTargetPositionsCleanup->start();
274
275
276
    }
}

277
278
QString FolderModel::url() const
{
279
    return m_url;
280
281
}

282
void FolderModel::setUrl(const QString& url)
283
{
284
    const QUrl &resolvedNewUrl = resolve(url);
285

286
    if (url == m_url) {
287
        m_dirModel->dirLister()->updateDirectory(resolvedNewUrl);
288
289
290
        return;
    }

291
    const auto oldUrl = resolvedUrl();
Andras Mantia's avatar
Andras Mantia committed
292

293
    beginResetModel();
294
    m_url = url;
295
    m_isDirCache.clear();
296
    m_dirModel->dirLister()->openUrl(resolvedNewUrl);
297
    clearDragImages();
298
    m_dragIndexes.clear();
299
300
301
    endResetModel();

    emit urlChanged();
302
    emit resolvedUrlChanged();
303
304
305

    m_errorString.clear();
    emit errorStringChanged();
306
307
308

    if (m_dirWatch) {
        delete m_dirWatch;
309
        m_dirWatch = nullptr;
310
311
    }

312
    if (resolvedNewUrl.isValid()) {
313
314
315
        m_dirWatch = new KDirWatch(this);
        connect(m_dirWatch, &KDirWatch::created, this, &FolderModel::iconNameChanged);
        connect(m_dirWatch, &KDirWatch::dirty, this, &FolderModel::iconNameChanged);
316
        m_dirWatch->addFile(resolvedNewUrl.toLocalFile() + QLatin1String("/.directory"));
317
318
    }

319
320
321
322
    if (m_dragInProgress) {
        m_urlChangedWhileDragging = true;
    }

323
    emit iconNameChanged();
Andras Mantia's avatar
Andras Mantia committed
324

325
    if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
Andras Mantia's avatar
Andras Mantia committed
326
        m_screenMapper->removeScreen(m_screen, oldUrl);
327
        m_screenMapper->addScreen(m_screen, resolvedUrl());
Andras Mantia's avatar
Andras Mantia committed
328
    }
329
330
}

331
332
333
334
335
QUrl FolderModel::resolvedUrl() const
{
    return m_dirModel->dirLister()->url();
}

336
337
338
339
QUrl FolderModel::resolve(const QString& url)
{
    QUrl resolvedUrl;

340
    if (url.startsWith(QLatin1Char('~'))) {
341
342
343
344
345
346
347
348
        resolvedUrl = QUrl::fromLocalFile(KShell::tildeExpand(url));
    } else {
        resolvedUrl = QUrl::fromUserInput(url);
    }

    return resolvedUrl;
}

349
350
351
352
353
354
355
356
357
358
359
QString FolderModel::iconName() const
{
    const KFileItem rootItem(m_dirModel->dirLister()->url());

    if (!rootItem.isFinalIconKnown()) {
        rootItem.determineMimeType();
    }

    return rootItem.iconName();
}

360
361
362
363
364
365
366
367
368
369
370
371
372
FolderModel::Status FolderModel::status() const
{
    return m_status;
}

void FolderModel::setStatus(Status status)
{
    if (m_status != status) {
        m_status = status;
        emit statusChanged();
    }
}

373
374
375
376
377
QString FolderModel::errorString() const
{
    return m_errorString;
}

378
379
380
381
382
bool FolderModel::dragging() const
{
    return m_dragInProgress;
}

383
384
385
386
387
388
389
390
391
392
bool FolderModel::usedByContainment() const
{
    return m_usedByContainment;
}

void FolderModel::setUsedByContainment(bool used)
{
    if (m_usedByContainment != used) {
        m_usedByContainment = used;

393
        QAction *action = m_actionCollection.action(QStringLiteral("refresh"));
394
395
396

        if (action) {
            action->setText(m_usedByContainment ? i18n("&Refresh Desktop") : i18n("&Refresh View"));
397
            action->setIcon(m_usedByContainment ? QIcon::fromTheme(QStringLiteral("user-desktop")) : QIcon::fromTheme(QStringLiteral("view-refresh")));
398
399
        }

400
401
402
403
        m_screenMapper->disconnect(this);
        connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete);
        connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete);

404
405
406
407
        emit usedByContainmentChanged();
    }
}

408
409
410
411
412
413
414
415
416
417
418
419
420
421
bool FolderModel::locked() const
{
    return m_locked;
}

void FolderModel::setLocked(bool locked)
{
    if (m_locked != locked) {
        m_locked = locked;

        emit lockedChanged();
    }
}

422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
void FolderModel::dirListFailed(const QString& error)
{
    m_errorString = error;
    emit errorStringChanged();
}

int FolderModel::sortMode() const
{
    return m_sortMode;
}

void FolderModel::setSortMode(int mode)
{
    if (m_sortMode != mode) {
        m_sortMode = mode;

Eike Hein's avatar
Eike Hein committed
438
        if (mode == -1 /* Unsorted */) {
439
440
            setDynamicSortFilter(false);
        } else {
441
            invalidateIfComplete();
Eike Hein's avatar
Eike Hein committed
442
443
            sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
            setDynamicSortFilter(true);
444
        }
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

        emit sortModeChanged();
    }
}

bool FolderModel::sortDesc() const
{
    return m_sortDesc;
}

void FolderModel::setSortDesc(bool desc)
{
    if (m_sortDesc != desc) {
        m_sortDesc = desc;

Eike Hein's avatar
Eike Hein committed
460
        if (m_sortMode != -1 /* Unsorted */) {
461
            invalidateIfComplete();
Eike Hein's avatar
Eike Hein committed
462
            sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
463
        }
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478

        emit sortDescChanged();
    }
}

bool FolderModel::sortDirsFirst() const
{
    return m_sortDirsFirst;
}

void FolderModel::setSortDirsFirst(bool enable)
{
    if (m_sortDirsFirst != enable) {
        m_sortDirsFirst = enable;

Eike Hein's avatar
Eike Hein committed
479
        if (m_sortMode != -1 /* Unsorted */) {
480
            invalidateIfComplete();
Eike Hein's avatar
Eike Hein committed
481
            sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder);
482
        }
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570

        emit sortDirsFirstChanged();
    }
}

bool FolderModel::parseDesktopFiles() const
{
    return m_parseDesktopFiles;
}

void FolderModel::setParseDesktopFiles(bool enable)
{
    if (m_parseDesktopFiles != enable) {
        m_parseDesktopFiles = enable;
        emit parseDesktopFilesChanged();
    }
}

QObject* FolderModel::viewAdapter() const
{
    return m_viewAdapter;
}

void FolderModel::setViewAdapter(QObject* adapter)
{
    if (m_viewAdapter != adapter) {
        KAbstractViewAdapter *abstractViewAdapter = dynamic_cast<KAbstractViewAdapter *>(adapter);

        m_viewAdapter = abstractViewAdapter;

        if (m_viewAdapter && !m_previewGenerator) {
            m_previewGenerator = new KFilePreviewGenerator(abstractViewAdapter, this);
            m_previewGenerator->setPreviewShown(m_previews);
            m_previewGenerator->setEnabledPlugins(m_previewPlugins);
        }

        emit viewAdapterChanged();
    }
}

bool FolderModel::previews() const
{
    return m_previews;
}

void FolderModel::setPreviews(bool previews)
{
    if (m_previews != previews) {
        m_previews = previews;

        if (m_previewGenerator) {
            m_previewGenerator->setPreviewShown(m_previews);
        }

        emit previewsChanged();
    }
}

QStringList FolderModel::previewPlugins() const
{
    return m_previewPlugins;
}

void FolderModel::setPreviewPlugins(const QStringList& previewPlugins)
{
    if (m_previewPlugins != previewPlugins) {
        m_previewPlugins = previewPlugins;

        if (m_previewGenerator) {
            m_previewGenerator->setPreviewShown(false);
            m_previewGenerator->setEnabledPlugins(m_previewPlugins);
            m_previewGenerator->setPreviewShown(true);
        }

        emit previewPluginsChanged();
    }
}

int FolderModel::filterMode() const
{
    return m_filterMode;
}

void FolderModel::setFilterMode(int filterMode)
{
    if (m_filterMode != (FilterMode)filterMode) {
        m_filterMode = (FilterMode)filterMode;

571
        invalidateFilterIfComplete();
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588

        emit filterModeChanged();
    }
}

QString FolderModel::filterPattern() const
{
    return m_filterPattern;
}

void FolderModel::setFilterPattern(const QString &pattern)
{
    if (m_filterPattern == pattern) {
        return;
    }

    m_filterPattern = pattern;
589
    m_filterPatternMatchAll = (pattern == QLatin1String("*"));
590

591
    const QStringList patterns = pattern.split(QLatin1Char(' '));
592
    m_regExps.clear();
Laurent Montel's avatar
Laurent Montel committed
593
    m_regExps.reserve(patterns.count());
594
595
596
597
598
599
600
601

    foreach (const QString &pattern, patterns) {
        QRegExp rx(pattern);
        rx.setPatternSyntax(QRegExp::Wildcard);
        rx.setCaseSensitivity(Qt::CaseInsensitive);
        m_regExps.append(rx);
    }

602
    invalidateFilterIfComplete();
603

604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
    emit filterPatternChanged();
}

QStringList FolderModel::filterMimeTypes() const
{
    return m_mimeSet.toList();
}

void FolderModel::setFilterMimeTypes(const QStringList &mimeList)
{
    const QSet<QString> &set = QSet<QString>::fromList(mimeList);

    if (m_mimeSet != set) {

        m_mimeSet = set;

620
        invalidateFilterIfComplete();
621
622
623
624
625

        emit filterMimeTypesChanged();
    }
}

Andras Mantia's avatar
Andras Mantia committed
626
627
628
629
630
631
void FolderModel::setScreen(int screen)
{
    if (m_screen == screen)
        return;

    m_screen = screen;
632
    if (m_usedByContainment && !m_screenMapper->sharedDesktops()) {
633
        m_screenMapper->addScreen(screen, resolvedUrl());
Andras Mantia's avatar
Andras Mantia committed
634
635
636
637
    }
    emit screenChanged();
}

638
639
640
641
642
KFileItem FolderModel::rootItem() const
{
    return m_dirModel->dirLister()->rootItem();
}

643
644
645
646
647
648
649
650
651
void FolderModel::up()
{
    const QUrl &up = KIO::upUrl(resolvedUrl());

    if (up.isValid()) {
        setUrl(up.toString());
    }
}

652
void FolderModel::cd(int row)
653
{
654
655
656
657
    if (row < 0) {
        return;
    }

658
659
660
661
662
663
664
    const QModelIndex idx = index(row, 0);
    bool isDir = data(idx, IsDirRole).toBool();

    if (isDir) {
        const KFileItem  item = itemForIndex(idx);
        if (m_parseDesktopFiles && item.isDesktopFile()) {
            const KDesktopFile file(item.targetUrl().path());
Laurent Montel's avatar
Laurent Montel committed
665
            if (file.hasLinkType()) {
666
667
                setUrl(file.readUrl());
            }
668
669
        } else {
            setUrl(item.targetUrl().toString());
670
        }
671
672
    }
}
673

674
675
676
677
678
679
680
void FolderModel::run(int row)
{
    if (row < 0) {
        return;
    }

    KFileItem item = itemForIndex(index(row, 0));
681

682
683
684
685
    QUrl url(item.targetUrl());

    // FIXME TODO: This can go once we depend on a KIO w/ fe1f50caaf2.
    if (url.scheme().isEmpty()) {
686
        url.setScheme(QStringLiteral("file"));
687
    }
688

689
    KRun *run = new KRun(url, nullptr);
690
691
692
693
694
    // On desktop:/ we want to be able to run .desktop files right away,
    // otherwise ask for security reasons. We also don't use the targetUrl()
    // from above since we don't want the resolved /home/foo/Desktop URL.
    run->setShowScriptExecutionPrompt(item.url().scheme() != QLatin1String("desktop")
                                   || item.url().adjusted(QUrl::RemoveFilename).path() != QLatin1String("/"));
695
696
}

697
698
699
700
701
702
void FolderModel::runSelected()
{
    if (!m_selectionModel->hasSelection()) {
        return;
    }

703
704
705
706
707
708
709
    if (m_selectionModel->selectedIndexes().count() == 1) {
        run(m_selectionModel->selectedIndexes().constFirst().row());
        return;
    }

    KFileItemActions fileItemActions(this);
    KFileItemList items;
710

711
    foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) {
712
        // Skip over directories.
713
714
        if (!index.data(IsDirRole).toBool()) {
            items << itemForIndex(index);
715
        }
716
    }
717
718

    fileItemActions.runPreferredApplications(items, QString());
719
720
}

721
722
void FolderModel::rename(int row, const QString& name)
{
723
724
725
726
    if (row < 0) {
        return;
    }

727
728
729
730
    QModelIndex idx = index(row, 0);
    m_dirModel->setData(mapToSource(idx), name, Qt::EditRole);
}

731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
int FolderModel::fileExtensionBoundary(int row)
{
    const QModelIndex idx = index(row, 0);
    const QString &name = data(idx, Qt::DisplayRole).toString();

    int boundary = name.length();

    if (data(idx, IsDirRole).toBool()) {
        return boundary;
    }

    QMimeDatabase db;
    const QString &ext = db.suffixForFileName(name);

    if (ext.isEmpty()) {
        boundary = name.lastIndexOf(QLatin1Char('.'));

        if (boundary < 1) {
            boundary = name.length();
        }
    } else {
        boundary -= ext.length() + 1;
    }

    return boundary;
}

Laurent Montel's avatar
Laurent Montel committed
758
bool FolderModel::hasSelection() const
759
760
761
762
{
    return m_selectionModel->hasSelection();
}

763
764
bool FolderModel::isSelected(int row)
{
765
766
767
768
    if (row < 0) {
        return false;
    }

769
770
771
    return m_selectionModel->isSelected(index(row, 0));
}

772
773
void FolderModel::setSelected(int row)
{
774
775
776
777
    if (row < 0) {
        return;
    }

778
779
780
    m_selectionModel->select(index(row, 0), QItemSelectionModel::Select);
}

781
782
783
784
785
786
787
788
789
void FolderModel::toggleSelected(int row)
{
    if (row < 0) {
        return;
    }

    m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle);
}

790
void FolderModel::setRangeSelected(int anchor, int to)
791
{
792
    if (anchor < 0 || to < 0) {
793
794
795
        return;
    }

796
    QItemSelection selection(index(anchor, 0), index(to, 0));
797
798
799
    m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect);
}

800
void FolderModel::updateSelection(const QVariantList &rows, bool toggle)
801
{
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
    QItemSelection newSelection;

    int iRow = -1;

    foreach (const QVariant &row, rows) {
        iRow = row.toInt();

        if (iRow < 0) {
            return;
        }

        const QModelIndex &idx = index(iRow, 0);
        newSelection.select(idx, idx);
    }

    if (toggle) {
        QItemSelection pinnedSelection = m_pinnedSelection;
        pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle);
        m_selectionModel->select(pinnedSelection, QItemSelectionModel::ClearAndSelect);
    } else {
        m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect);
    }
824
825
826
827
}

void FolderModel::clearSelection()
{
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
    if (m_selectionModel->hasSelection()) {
        m_selectionModel->clear();
    }
}

void FolderModel::pinSelection()
{
    m_pinnedSelection = m_selectionModel->selection();
}

void FolderModel::unpinSelection()
{
    m_pinnedSelection = QItemSelection();
}

void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image)
{
    if (row < 0) {
        return;
    }

849
    delete m_dragImages.take(row);
850

851
    DragImage *dragImage = new DragImage();
852
853
854
855
856
857
858
859
860
861
    dragImage->row = row;
    dragImage->rect = QRect(x, y, width, height);
    dragImage->image = image.value<QImage>();
    dragImage->blank = false;

    m_dragImages.insert(row, dragImage);
}

void FolderModel::clearDragImages()
{
862
863
    qDeleteAll(m_dragImages);
    m_dragImages.clear();
864
865
866
867
868
869
870
871
872
873
}

void FolderModel::setDragHotSpotScrollOffset(int x, int y)
{
    m_dragHotSpotScrollOffset.setX(x);
    m_dragHotSpotScrollOffset.setY(y);
}

QPoint FolderModel::dragCursorOffset(int row)
{
Laurent Montel's avatar
Laurent Montel committed
874
875
    DragImage *image = m_dragImages.value(row);
    if (!image) {
876
        return QPoint(0, 0);
877
878
    }

Laurent Montel's avatar
Laurent Montel committed
879
    return image->cursorOffset;
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
}

void FolderModel::addDragImage(QDrag *drag, int x, int y)
{
    if (!drag || m_dragImages.isEmpty()) {
        return;
    }

    QRegion region;

    foreach (DragImage *image, m_dragImages) {
        image->blank = isBlank(image->row);
        image->rect.translate(-m_dragHotSpotScrollOffset.x(), -m_dragHotSpotScrollOffset.y());
        if (!image->blank && !image->image.isNull()) {
            region = region.united(image->rect);
        }
    }

    QRect rect = region.boundingRect();
    QPoint offset = rect.topLeft();
    rect.translate(-offset.x(), -offset.y());

    QImage dragImage(rect.size(), QImage::Format_RGBA8888);
    dragImage.fill(Qt::transparent);

    QPainter painter(&dragImage);

    QPoint pos;

    foreach (DragImage *image, m_dragImages) {
        if (!image->blank && !image->image.isNull()) {
            pos = image->rect.translated(-offset.x(), -offset.y()).topLeft();
Eike Hein's avatar
Eike Hein committed
912
913
            image->cursorOffset.setX(pos.x() - (x - offset.x()));
            image->cursorOffset.setY(pos.y() - (y - offset.y()));
914
915
916
917
918
919
920
921
922
923
924
925
926

            painter.drawImage(pos, image->image);
        }

        // FIXME HACK: Operate on copy.
        image->rect.translate(m_dragHotSpotScrollOffset.x(), m_dragHotSpotScrollOffset.y());
    }

    drag->setPixmap(QPixmap::fromImage(dragImage));
    drag->setHotSpot(QPoint(x - offset.x(), y - offset.y()));
}

void FolderModel::dragSelected(int x, int y)
927
{
Eike Hein's avatar
Eike Hein committed
928
929
930
931
932
    if (m_dragInProgress) {
        return;
    }

    m_dragInProgress = true;
933
934
    emit draggingChanged();
    m_urlChangedWhileDragging = false;
Eike Hein's avatar
Eike Hein committed
935

936
937
938
939
940
941
942
943
    // Avoid starting a drag synchronously in a mouse handler or interferes with
    // child event filtering in parent items (and thus e.g. press-and-hold hand-
    // ling in a containment).
    QMetaObject::invokeMethod(this, "dragSelectedInternal", Qt::QueuedConnection,
        Q_ARG(int, x),
        Q_ARG(int, y));
}

Bhushan Shah's avatar
Bhushan Shah committed
944
void FolderModel::dragSelectedInternal(int x, int y)
945
946
{
    if (!m_viewAdapter || !m_selectionModel->hasSelection()) {
Eike Hein's avatar
Eike Hein committed
947
        m_dragInProgress = false;
948
        emit draggingChanged();
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
        return;
    }

    ItemViewAdapter *adapter = qobject_cast<ItemViewAdapter *>(m_viewAdapter);
    QQuickItem *item = qobject_cast<QQuickItem *>(adapter->adapterView());

    QDrag *drag = new QDrag(item);

    addDragImage(drag, x, y);

    m_dragIndexes = m_selectionModel->selectedIndexes();

    qSort(m_dragIndexes.begin(), m_dragIndexes.end());

    // TODO: Optimize to emit contiguous groups.
    emit dataChanged(m_dragIndexes.first(), m_dragIndexes.last(), QVector<int>() << BlankRole);

    QModelIndexList sourceDragIndexes;
Laurent Montel's avatar
Laurent Montel committed
967
    sourceDragIndexes.reserve(m_dragIndexes.count());
968
969
970
971
972
973
    foreach (const QModelIndex &index, m_dragIndexes) {
        sourceDragIndexes.append(mapToSource(index));
    }

    drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes));

974
975
976
977
978
979
    // Due to spring-loading (aka auto-expand), the URL might change
    // while the drag is in-flight - in that case we don't want to
    // unnecessarily emit dataChanged() for (possibly invalid) indices
    // after it ends.
    const QUrl currentUrl(m_dirModel->dirLister()->url());

980
981
    item->grabMouse();
    drag->exec(supportedDragActions());
982

983
984
    item->ungrabMouse();

985
986
987
988
989
990
991
992
993
994
995
    m_dragInProgress = false;
    emit draggingChanged();
    m_urlChangedWhileDragging = false;

    if (m_dirModel->dirLister()->url() == currentUrl) {
        const QModelIndex first(m_dragIndexes.first());
        const QModelIndex last(m_dragIndexes.last());
        m_dragIndexes.clear();
        // TODO: Optimize to emit contiguous groups.
        emit dataChanged(first, last, QVector<int>() << BlankRole);
    }
996
997
}

998
999
1000
static bool isDropBetweenSharedViews(const QList<QUrl> &urls, const QUrl &folderUrl)
{
    for (const auto &url : urls) {
For faster browsing, not all history is shown. View entire blame