folderviewcontextmanageritem.cpp 8.99 KB
Newer Older
1
// vim: set tabstop=4 shiftwidth=4 expandtab:
2
3
/*
Gwenview: an image viewer
Aurélien Gâteau's avatar
Aurélien Gâteau committed
4
Copyright 2009 Aurélien Gâteau <agateau@kde.org>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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.

*/
// Self
#include "folderviewcontextmanageritem.h"

// Qt
25
#include <QDragEnterEvent>
26
#include <QHeaderView>
27
#include <QTreeView>
David Edmundson's avatar
David Edmundson committed
28
#include <QDir>
Laurent Montel's avatar
Laurent Montel committed
29
#include <QMimeData>
Steffen Hartleib's avatar
Steffen Hartleib committed
30
31
#include <QStyleHints>
#include <QApplication>
32

33
// KF
34
#include <KUrlMimeData>
35
36

// Local
37
#include "gwenview_app_debug.h"
38
#include <lib/contextmanager.h>
39
#include <lib/eventwatcher.h>
40
#include "sidebar.h"
41
#include "fileoperations.h"
Steffen Hartleib's avatar
Steffen Hartleib committed
42
43
#include <lib/scrollerutils.h>
#include "lib/touch/touch_helper.h"
44

45
46
namespace Gwenview
{
47
48
49
50

/**
 * This treeview accepts url drops
 */
51
52
class UrlDropTreeView : public QTreeView
{
53
public:
54
    explicit UrlDropTreeView(QWidget* parent = nullptr)
Aurélien Gâteau's avatar
Aurélien Gâteau committed
55
56
        : QTreeView(parent)
        {}
57
58

protected:
59
    void dragEnterEvent(QDragEnterEvent* event) override
60
61
62
63
64
65
66
67
    {
        QAbstractItemView::dragEnterEvent(event);
        setDirtyRegion(mDropRect);
        if (event->mimeData()->hasUrls()) {
            event->acceptProposedAction();
        }
    }

68
    void dragMoveEvent(QDragMoveEvent* event) override
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    {
        QAbstractItemView::dragMoveEvent(event);

        QModelIndex index = indexAt(event->pos());

        // This code has been copied from Dolphin
        // (panels/folders/paneltreeview.cpp)
        setDirtyRegion(mDropRect);
        mDropRect = visualRect(index);
        setDirtyRegion(mDropRect);

        if (index.isValid()) {
            event->acceptProposedAction();
        } else {
            event->ignore();
        }
    }

87
    void dropEvent(QDropEvent* event) override
88
    {
89
        const QList<QUrl> urlList = KUrlMimeData::urlsFromMimeData(event->mimeData());
90
91
        const QModelIndex index = indexAt(event->pos());
        if (!index.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
92
            qCWarning(GWENVIEW_APP_LOG) << "Invalid index!";
93
94
            return;
        }
David Edmundson's avatar
David Edmundson committed
95
        const QUrl destUrl = static_cast<MODEL_CLASS*>(model())->urlForIndex(index);
96
97
        FileOperations::showMenuForDroppedUrls(this, urlList, destUrl);
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
98

Steffen Hartleib's avatar
Steffen Hartleib committed
99
100
101
102
103
104
105
106
107
108
109
110
111
    bool viewportEvent(QEvent* event) override
    {
        if (event->type() == QEvent::TouchBegin) {
            return true;
        }
        const QPoint pos = Touch_Helper::simpleTapPosition(event);
        if (pos != QPoint(-1, -1)) {
            expand(indexAt(pos));
            emit activated(indexAt(pos));
        }

        return QTreeView::viewportEvent(event);
    }
Aurélien Gâteau's avatar
Aurélien Gâteau committed
112
private:
113
    QRect mDropRect;
114
115
};

116
117
118

FolderViewContextManagerItem::FolderViewContextManagerItem(ContextManager* manager)
: AbstractContextManagerItem(manager)
119
{
120
    mModel = nullptr;
121

122
    setupView();
123

Laurent Montel's avatar
Laurent Montel committed
124
125
    connect(contextManager(), &ContextManager::currentDirUrlChanged,
            this, &FolderViewContextManagerItem::slotCurrentDirUrlChanged);
126
127
}

David Edmundson's avatar
David Edmundson committed
128
void FolderViewContextManagerItem::slotCurrentDirUrlChanged(const QUrl &url)
129
{
130
131
132
    if (url.isValid() && mUrlToSelect != url) {
        mUrlToSelect = url.adjusted(QUrl::StripTrailingSlash | QUrl::NormalizePathSegments);
        mExpandingIndex = QModelIndex();
133
    }
134
    if (!mView->isVisible()) {
135
136
137
138
        return;
    }

    expandToSelectedUrl();
139
140
}

141
142
void FolderViewContextManagerItem::expandToSelectedUrl()
{
143
    if (!mUrlToSelect.isValid()) {
144
145
146
        return;
    }

147
148
    if (!mModel) {
        setupModel();
149
150
    }

151
    QModelIndex index = findClosestIndex(mExpandingIndex, mUrlToSelect);
152
153
154
    if (!index.isValid()) {
        return;
    }
155
    mExpandingIndex = index;
156

157
158
    QUrl url = mModel->urlForIndex(mExpandingIndex);
    if (mUrlToSelect == url) {
159
        // We found our url
160
161
162
163
164
        QItemSelectionModel* selModel = mView->selectionModel();
        selModel->setCurrentIndex(mExpandingIndex, QItemSelectionModel::ClearAndSelect);
        mView->scrollTo(mExpandingIndex);
        mUrlToSelect = QUrl();
        mExpandingIndex = QModelIndex();
165
166
    } else {
        // We found a parent of our url
167
        mView->setExpanded(mExpandingIndex, true);
168
    }
169
170
}

171
172
173
174
175
176
void FolderViewContextManagerItem::slotRowsInserted(const QModelIndex& parentIndex, int /*start*/, int /*end*/)
{
    // Can't trigger the case where parentIndex is invalid, but it most
    // probably happen when root items are created. In this case we trigger
    // expandToSelectedUrl without checking the url.
    // See bug #191771
177
178
    if (!parentIndex.isValid() || mModel->urlForIndex(parentIndex).isParentOf(mUrlToSelect)) {
        mExpandingIndex = parentIndex;
179
        // Hack because otherwise indexes are not in correct order!
180
        QMetaObject::invokeMethod(this, &FolderViewContextManagerItem::expandToSelectedUrl, Qt::QueuedConnection);
181
    }
182
183
}

184
185
186
187
188
void FolderViewContextManagerItem::slotActivated(const QModelIndex& index)
{
    if (!index.isValid()) {
        return;
    }
189

190
    QUrl url = mModel->urlForIndex(index);
191
    emit urlChanged(url);
192
193
}

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
void FolderViewContextManagerItem::setupModel()
{
    mModel = new MODEL_CLASS(this);
    mView->setModel(mModel);
#ifndef USE_PLACETREE
    for (int col = 1; col <= mModel->columnCount(); ++col) {
        mView->header()->setSectionHidden(col, true);
    }
    mModel->dirLister()->openUrl(QUrl("/"));
#endif
    QObject::connect(mModel, &MODEL_CLASS::rowsInserted, this, &FolderViewContextManagerItem::slotRowsInserted);
}

void FolderViewContextManagerItem::setupView()
{
    mView = new UrlDropTreeView;
    mView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    mView->setAcceptDrops(true);
    mView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
    mView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);

    // Necessary to get the drop target highlighted
    mView->viewport()->setAttribute(Qt::WA_Hover);

    mView->setHeaderHidden(true);

    // This is tricky: QTreeView header has stretchLastSection set to true.
    // In this configuration, the header gets quite wide and cause an
    // horizontal scrollbar to appear.
    // To avoid this, set stretchLastSection to false and resizeMode to
    // Stretch (we still want the column to take the full width of the
    // widget).
    mView->header()->setStretchLastSection(false);
227
    mView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
228

Steffen Hartleib's avatar
Steffen Hartleib committed
229
230
    ScrollerUtils::setQScroller(mView->viewport());

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
    setWidget(mView);
    QObject::connect(mView, &QTreeView::activated, this, &FolderViewContextManagerItem::slotActivated);
    EventWatcher::install(mView, QEvent::Show, this, SLOT(expandToSelectedUrl()));
}

QModelIndex FolderViewContextManagerItem::findClosestIndex(const QModelIndex& parent, const QUrl& wantedUrl)
{
    Q_ASSERT(mModel);
    QModelIndex index = parent;
    if (!index.isValid()) {
        index = findRootIndex(wantedUrl);
        if (!index.isValid()) {
            return QModelIndex();
        }
    }

    QUrl url = mModel->urlForIndex(index);
    if (!url.isParentOf(wantedUrl)) {
Laurent Montel's avatar
Laurent Montel committed
249
        qCWarning(GWENVIEW_APP_LOG) << url << "is not a parent of" << wantedUrl << "!";
250
251
252
253
254
        return QModelIndex();
    }

    QString relativePath = QDir(url.path()).relativeFilePath(wantedUrl.path());
    QModelIndex lastFoundIndex = index;
255
256
257
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
   const QStringList relativePathList = relativePath.split(QDir::separator(), Qt::SkipEmptyParts);
#else
258
    const QStringList relativePathList = relativePath.split(QDir::separator(), QString::SkipEmptyParts);
259
#endif
260
    for (const QString & pathPart : relativePathList) {
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
        bool found = false;
        for (int row = 0; row < mModel->rowCount(lastFoundIndex); ++row) {
            QModelIndex index = mModel->index(row, 0, lastFoundIndex);
            if (index.data().toString() == pathPart) {
                // FIXME: Check encoding
                found = true;
                lastFoundIndex = index;
                break;
            }
        }
        if (!found) {
            break;
        }
    }
    return lastFoundIndex;
}

QModelIndex FolderViewContextManagerItem::findRootIndex(const QUrl& wantedUrl)
{
    QModelIndex matchIndex;
    int matchUrlLength = 0;
    for (int row = 0; row < mModel->rowCount(); ++row) {
        QModelIndex index = mModel->index(row, 0);
        QUrl url = mModel->urlForIndex(index);
        int urlLength = url.url().length();
        if (url.isParentOf(wantedUrl) && urlLength > matchUrlLength) {
            matchIndex = index;
            matchUrlLength = urlLength;
        }
    }
    if (!matchIndex.isValid()) {
Laurent Montel's avatar
Laurent Montel committed
292
        qCWarning(GWENVIEW_APP_LOG) << "Found no root index for" << wantedUrl;
293
294
295
296
    }
    return matchIndex;
}

297
} // namespace