Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsidebar.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsidebar_p.h"
5
6#include <qaction.h>
7#include <qurl.h>
8#if QT_CONFIG(menu)
9#include <qmenu.h>
10#endif
11#include <qmimedata.h>
12#include <qevent.h>
13#include <qdebug.h>
14#include <qfilesystemmodel.h>
15#include <qabstractfileiconprovider.h>
16#include <qfiledialog.h>
17
18QT_BEGIN_NAMESPACE
19
20using namespace Qt::StringLiterals;
21
22void QSideBarDelegate::initStyleOption(QStyleOptionViewItem *option,
23 const QModelIndex &index) const
24{
25 QStyledItemDelegate::initStyleOption(option,index);
26 QVariant value = index.data(QUrlModel::EnabledRole);
27 if (value.isValid()) {
28 //If the bookmark/entry is not enabled then we paint it in gray
29 if (!qvariant_cast<bool>(value))
30 option->state &= ~QStyle::State_Enabled;
31 }
32}
33
34/*!
35 \internal
36 \class QUrlModel
37 QUrlModel lets you have indexes from a QFileSystemModel to a list. When QFileSystemModel
38 changes them QUrlModel will automatically update.
39
40 Example usage: File dialog sidebar and combo box
41 */
42QUrlModel::QUrlModel(QObject *parent) : QStandardItemModel(parent), showFullPath(false), fileSystemModel(nullptr)
43{
44}
45
46QUrlModel::~QUrlModel()
47{
48 for (const auto &conn : std::as_const(modelConnections))
49 disconnect(conn);
50}
51
52constexpr char uriListMimeType[] = "text/uri-list";
53
54static bool hasSupportedFormat(const QMimeData *data)
55{
56 return data->hasFormat(QLatin1StringView(uriListMimeType));
57}
58
59/*!
60 \reimp
61*/
62QStringList QUrlModel::mimeTypes() const
63{
64 return QStringList(QLatin1StringView(uriListMimeType));
65}
66
67/*!
68 \reimp
69*/
70Qt::ItemFlags QUrlModel::flags(const QModelIndex &index) const
71{
72 Qt::ItemFlags flags = QStandardItemModel::flags(index);
73 if (index.isValid()) {
74 flags &= ~Qt::ItemIsEditable;
75 // ### some future version could support "moving" urls onto a folder
76 flags &= ~Qt::ItemIsDropEnabled;
77 }
78
79 if (index.data(Qt::DecorationRole).isNull())
80 flags &= ~Qt::ItemIsEnabled;
81
82 return flags;
83}
84
85/*!
86 \reimp
87*/
88QMimeData *QUrlModel::mimeData(const QModelIndexList &indexes) const
89{
90 QList<QUrl> list;
91 for (const auto &index : indexes) {
92 if (index.column() == 0)
93 list.append(index.data(UrlRole).toUrl());
94 }
95 QMimeData *data = new QMimeData();
96 data->setUrls(list);
97 return data;
98}
99
100#if QT_CONFIG(draganddrop)
101
102/*!
103 Decide based upon the data if it should be accepted or not
104
105 We only accept dirs and not files
106*/
107bool QUrlModel::canDrop(QDragEnterEvent *event)
108{
109 if (!hasSupportedFormat(event->mimeData()))
110 return false;
111
112 const QList<QUrl> list = event->mimeData()->urls();
113 for (const auto &url : list) {
114 const QModelIndex idx = fileSystemModel->index(url.toLocalFile());
115 if (!fileSystemModel->isDir(idx))
116 return false;
117 }
118 return true;
119}
120
121/*!
122 \reimp
123*/
124bool QUrlModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
125 int row, int column, const QModelIndex &parent)
126{
127 if (!hasSupportedFormat(data))
128 return false;
129 Q_UNUSED(action);
130 Q_UNUSED(column);
131 Q_UNUSED(parent);
132 addUrls(data->urls(), row);
133 return true;
134}
135
136#endif // QT_CONFIG(draganddrop)
137
138/*!
139 \reimp
140
141 If the role is the UrlRole then handle otherwise just pass to QStandardItemModel
142*/
143bool QUrlModel::setData(const QModelIndex &index, const QVariant &value, int role)
144{
145 if (value.userType() == QMetaType::QUrl) {
146 QUrl url = value.toUrl();
147 QModelIndex dirIndex = fileSystemModel->index(url.toLocalFile());
148 //On windows the popup display the "C:\", convert to nativeSeparators
149 if (showFullPath)
150 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()));
151 else {
152 QStandardItemModel::setData(index, QDir::toNativeSeparators(fileSystemModel->data(dirIndex, QFileSystemModel::FilePathRole).toString()), Qt::ToolTipRole);
153 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex).toString());
154 }
155 QStandardItemModel::setData(index, fileSystemModel->data(dirIndex, Qt::DecorationRole),
156 Qt::DecorationRole);
157 QStandardItemModel::setData(index, url, UrlRole);
158 return true;
159 }
160 return QStandardItemModel::setData(index, value, role);
161}
162
163void QUrlModel::setUrl(const QModelIndex &index, const QUrl &url, const QModelIndex &dirIndex)
164{
165 setData(index, url, UrlRole);
166 if (url.path().isEmpty()) {
167 setData(index, fileSystemModel->myComputer());
168 setData(index, fileSystemModel->myComputer(Qt::DecorationRole), Qt::DecorationRole);
169 } else {
170 QString newName;
171 if (showFullPath) {
172 //On windows the popup display the "C:\", convert to nativeSeparators
173 newName = QDir::toNativeSeparators(dirIndex.data(QFileSystemModel::FilePathRole).toString());
174 } else {
175 newName = dirIndex.data().toString();
176 }
177
178 QIcon newIcon = qvariant_cast<QIcon>(dirIndex.data(Qt::DecorationRole));
179 if (!dirIndex.isValid()) {
180 const QAbstractFileIconProvider *provider = fileSystemModel->iconProvider();
181 if (provider)
182 newIcon = provider->icon(QAbstractFileIconProvider::Folder);
183 newName = QFileInfo(url.toLocalFile()).fileName();
184 if (!invalidUrls.contains(url))
185 invalidUrls.append(url);
186 //The bookmark is invalid then we set to false the EnabledRole
187 setData(index, false, EnabledRole);
188 } else {
189 //The bookmark is valid then we set to true the EnabledRole
190 setData(index, true, EnabledRole);
191 }
192
193 // newIcon could be null if fileSystemModel->iconProvider() returns null
194 if (!newIcon.isNull()) {
195 // Make sure that we have at least 32x32 images
196 const QSize size = newIcon.actualSize(QSize(32,32));
197 if (size.width() < 32) {
198 QPixmap smallPixmap = newIcon.pixmap(QSize(32, 32));
199 newIcon.addPixmap(smallPixmap.scaledToWidth(32, Qt::SmoothTransformation));
200 }
201 }
202
203 if (index.data().toString() != newName)
204 setData(index, newName);
205 QIcon oldIcon = qvariant_cast<QIcon>(index.data(Qt::DecorationRole));
206 if (oldIcon.cacheKey() != newIcon.cacheKey())
207 setData(index, newIcon, Qt::DecorationRole);
208 }
209}
210
211void QUrlModel::setUrls(const QList<QUrl> &list)
212{
213 removeRows(0, rowCount());
214 invalidUrls.clear();
215 watching.clear();
216 addUrls(list, 0);
217}
218
219/*!
220 Add urls \a list into the list at \a row. If move then movie
221 existing ones to row.
222
223 \sa dropMimeData()
224*/
225void QUrlModel::addUrls(const QList<QUrl> &list, int row, bool move)
226{
227 if (row == -1)
228 row = rowCount();
229 row = qMin(row, rowCount());
230 const auto rend = list.crend();
231 for (auto it = list.crbegin(); it != rend; ++it) {
232 QUrl url = *it;
233 if (!url.isValid() || url.scheme() != "file"_L1)
234 continue;
235 //this makes sure the url is clean
236 const QString cleanUrl = QDir::cleanPath(url.toLocalFile());
237 if (!cleanUrl.isEmpty())
238 url = QUrl::fromLocalFile(cleanUrl);
239
240 for (int j = 0; move && j < rowCount(); ++j) {
241 QString local = index(j, 0).data(UrlRole).toUrl().toLocalFile();
242#if defined(Q_OS_WIN)
243 const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
244#else
245 const Qt::CaseSensitivity cs = Qt::CaseSensitive;
246#endif
247 if (!cleanUrl.compare(local, cs)) {
248 removeRow(j);
249 if (j <= row)
250 row--;
251 break;
252 }
253 }
254 row = qMax(row, 0);
255 QModelIndex idx = fileSystemModel->index(cleanUrl);
256 if (!fileSystemModel->isDir(idx))
257 continue;
258 insertRows(row, 1);
259 setUrl(index(row, 0), url, idx);
260 watching.append({idx, cleanUrl});
261 }
262}
263
264/*!
265 Return the complete list of urls in a QList.
266*/
267QList<QUrl> QUrlModel::urls() const
268{
269 QList<QUrl> list;
270 const int numRows = rowCount();
271 list.reserve(numRows);
272 for (int i = 0; i < numRows; ++i)
273 list.append(data(index(i, 0), UrlRole).toUrl());
274 return list;
275}
276
277/*!
278 QFileSystemModel to get index's from, clears existing rows
279*/
280void QUrlModel::setFileSystemModel(QFileSystemModel *model)
281{
282 if (model == fileSystemModel)
283 return;
284 if (fileSystemModel != nullptr) {
285 for (const auto &conn : std::as_const(modelConnections))
286 disconnect(conn);
287 }
288 fileSystemModel = model;
289 if (fileSystemModel != nullptr) {
290 modelConnections = {
291 connect(model, &QFileSystemModel::dataChanged,
292 this, &QUrlModel::dataChanged),
293 connect(model, &QFileSystemModel::layoutChanged,
294 this, &QUrlModel::layoutChanged),
295 connect(model, &QFileSystemModel::rowsRemoved,
296 this, &QUrlModel::layoutChanged),
297 };
298 }
299 clear();
300 insertColumns(0, 1);
301}
302
303/*
304 If one of the index's we are watching has changed update our internal data
305*/
306void QUrlModel::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
307{
308 QModelIndex parent = topLeft.parent();
309 for (int i = 0; i < watching.size(); ++i) {
310 QModelIndex index = watching.at(i).index;
311 if (index.model() && topLeft.model()) {
312 Q_ASSERT(index.model() == topLeft.model());
313 }
314 if ( index.row() >= topLeft.row()
315 && index.row() <= bottomRight.row()
316 && index.column() >= topLeft.column()
317 && index.column() <= bottomRight.column()
318 && index.parent() == parent) {
319 changed(watching.at(i).path);
320 }
321 }
322}
323
324/*!
325 Re-get all of our data, anything could have changed!
326 */
327void QUrlModel::layoutChanged()
328{
329 QStringList paths;
330 paths.reserve(watching.size());
331 for (const WatchItem &item : std::as_const(watching))
332 paths.append(item.path);
333 watching.clear();
334 for (const auto &path : paths) {
335 QModelIndex newIndex = fileSystemModel->index(path);
336 watching.append({newIndex, path});
337 if (newIndex.isValid())
338 changed(path);
339 }
340}
341
342/*!
343 The following path changed data update our copy of that data
344
345 \sa layoutChanged(), dataChanged()
346*/
347void QUrlModel::changed(const QString &path)
348{
349 for (int i = 0; i < rowCount(); ++i) {
350 QModelIndex idx = index(i, 0);
351 if (idx.data(UrlRole).toUrl().toLocalFile() == path) {
352 setData(idx, idx.data(UrlRole).toUrl());
353 }
354 }
355}
356
357QSidebar::QSidebar(QWidget *parent) : QListView(parent)
358{
359}
360
361void QSidebar::setModelAndUrls(QFileSystemModel *model, const QList<QUrl> &newUrls)
362{
363 setUniformItemSizes(true);
364 urlModel = new QUrlModel(this);
365 urlModel->setFileSystemModel(model);
366 setModel(urlModel);
367 setItemDelegate(new QSideBarDelegate(this));
368
369 connect(selectionModel(), &QItemSelectionModel::currentChanged,
370 this, &QSidebar::clicked);
371#if QT_CONFIG(draganddrop)
372 setDragDropMode(QAbstractItemView::DragDrop);
373#endif
374#if QT_CONFIG(menu)
375 setContextMenuPolicy(Qt::CustomContextMenu);
376 connect(this, &QSidebar::customContextMenuRequested,
377 this, &QSidebar::showContextMenu);
378#endif
379 urlModel->setUrls(newUrls);
380 setCurrentIndex(this->model()->index(0,0));
381}
382
383QSidebar::~QSidebar()
384{
385}
386
387#if QT_CONFIG(draganddrop)
388void QSidebar::dragEnterEvent(QDragEnterEvent *event)
389{
390 if (urlModel->canDrop(event))
391 QListView::dragEnterEvent(event);
392}
393#endif // QT_CONFIG(draganddrop)
394
395QSize QSidebar::sizeHint() const
396{
397 if (model())
398 return QListView::sizeHintForIndex(model()->index(0, 0)) + QSize(2 * frameWidth(), 2 * frameWidth());
399 return QListView::sizeHint();
400}
401
402void QSidebar::selectUrl(const QUrl &url)
403{
404 disconnect(selectionModel(), &QItemSelectionModel::currentChanged,
405 this, &QSidebar::clicked);
406
407 selectionModel()->clear();
408 for (int i = 0; i < model()->rowCount(); ++i) {
409 if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) {
410 selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select);
411 break;
412 }
413 }
414
415 connect(selectionModel(), &QItemSelectionModel::currentChanged,
416 this, &QSidebar::clicked);
417}
418
419#if QT_CONFIG(menu)
420/*!
421 \internal
422
423 \sa removeEntry()
424*/
425void QSidebar::showContextMenu(const QPoint &position)
426{
427 QList<QAction *> actions;
428 if (indexAt(position).isValid()) {
429 QAction *action = new QAction(QFileDialog::tr("Remove"), this);
430 if (indexAt(position).data(QUrlModel::UrlRole).toUrl().path().isEmpty())
431 action->setEnabled(false);
432 connect(action, &QAction::triggered, this, &QSidebar::removeEntry);
433 actions.append(action);
434 }
435 if (actions.size() > 0)
436 QMenu::exec(actions, mapToGlobal(position));
437}
438#endif // QT_CONFIG(menu)
439
440/*!
441 \internal
442
443 \sa showContextMenu()
444*/
445void QSidebar::removeEntry()
446{
447 const QList<QModelIndex> idxs = selectionModel()->selectedIndexes();
448 // Create a list of QPersistentModelIndex as the removeRow() calls below could
449 // invalidate the indexes in "idxs"
450 const QList<QPersistentModelIndex> persIndexes(idxs.cbegin(), idxs.cend());
451 for (const QPersistentModelIndex &persistent : persIndexes) {
452 if (!persistent.data(QUrlModel::UrlRole).toUrl().path().isEmpty())
453 model()->removeRow(persistent.row());
454 }
455}
456
457/*!
458 \internal
459
460 \sa goToUrl()
461*/
462void QSidebar::clicked(const QModelIndex &index)
463{
464 QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl();
465 emit goToUrl(url);
466 selectUrl(url);
467}
468
469/*!
470 \reimp
471 Don't automatically select something
472 */
473void QSidebar::focusInEvent(QFocusEvent *event)
474{
475 QAbstractScrollArea::focusInEvent(event);
476 viewport()->update();
477}
478
479/*!
480 \reimp
481 */
482bool QSidebar::event(QEvent * event)
483{
484 if (event->type() == QEvent::KeyRelease) {
485 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
486 if (ke->key() == Qt::Key_Delete) {
487 removeEntry();
488 return true;
489 }
490 }
491 return QListView::event(event);
492}
493
494QT_END_NAMESPACE
495
496#include "moc_qsidebar_p.cpp"
\inmodule QtCore
static bool hasSupportedFormat(const QMimeData *data)
Definition qsidebar.cpp:54
constexpr char uriListMimeType[]
Definition qsidebar.cpp:52