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
qquickfolderdialogimpl.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qloggingcategory.h>
9#include <QtQuickTemplates2/private/qquickdialogbuttonbox_p_p.h>
10
13
15
16Q_STATIC_LOGGING_CATEGORY(lcFolderDialogCurrentFolder, "qt.quick.dialogs.quickfolderdialogimpl.currentFolder")
17Q_STATIC_LOGGING_CATEGORY(lcFolderDialogSelectedFolder, "qt.quick.dialogs.quickfolderdialogimpl.selectedFolder")
18Q_STATIC_LOGGING_CATEGORY(lcFolderDialogOptions, "qt.quick.dialogs.quickfolderdialogimpl.options")
19
20QQuickFolderDialogImplPrivate::QQuickFolderDialogImplPrivate()
21{
22}
23
25{
26 Q_Q(QQuickFolderDialogImpl);
27 if (!buttonBox)
28 return;
29
30 QQuickFolderDialogImplAttached *attached = attachedOrWarn();
31 if (!attached)
32 return;
33
34 auto openButton = buttonBox->standardButton(QPlatformDialogHelper::Open);
35 if (!openButton) {
36 qmlWarning(q).nospace() << "Can't update Open button's enabled state because it wasn't found";
37 return;
38 }
39
40 openButton->setEnabled(!selectedFolder.isEmpty() && attached->breadcrumbBar()
41 && !attached->breadcrumbBar()->textField()->isVisible());
42}
43
44/*!
45 \internal
46
47 Ensures that a folder is always selected after a change in \c currentFolder.
48
49 \a oldFolderPath is the previous value of \c currentFolder.
50*/
51void QQuickFolderDialogImplPrivate::updateSelectedFolder(const QString &oldFolderPath)
52{
53 Q_Q(QQuickFolderDialogImpl);
54 QQuickFolderDialogImplAttached *attached = attachedOrWarn();
55 if (!attached || !attached->folderDialogListView())
56 return;
57
58 QString newSelectedFolderPath;
59 int newSelectedFolderIndex = 0;
60 const QString newFolderPath = QQmlFile::urlToLocalFileOrQrc(currentFolder);
61 if (!oldFolderPath.isEmpty() && !newFolderPath.isEmpty()) {
62 // If the user went up a directory (or several), we should set
63 // selectedFolder to be the directory that we were in (or
64 // its closest ancestor that is a child of the new directory).
65 // E.g. if oldFolderPath is /foo/bar/baz/abc/xyz, and newFolderPath is /foo/bar,
66 // then we want to set selectedFolder to be /foo/bar/baz.
67 const int indexOfFolder = oldFolderPath.indexOf(newFolderPath);
68 if (indexOfFolder != -1) {
69 // [folder]
70 // [ oldFolderPath ]
71 // /foo/bar/baz/abc/xyz
72 // [rel...Paths]
73 QStringList relativePaths = oldFolderPath.mid(indexOfFolder + newFolderPath.size()).split(QLatin1Char('/'), Qt::SkipEmptyParts);
74 newSelectedFolderPath = newFolderPath + QLatin1Char('/') + relativePaths.first();
75
76 // Now find the index of that directory so that we can set the ListView's currentIndex to it.
77 const QDir newFolderDir(newFolderPath);
78 // Just to be safe...
79 if (!newFolderDir.exists()) {
80 qmlWarning(q) << "Directory" << newSelectedFolderPath << "doesn't exist; can't get a file entry list for it";
81 return;
82 }
83
84 const QFileInfoList dirs = newFolderDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst);
85 const QFileInfo newSelectedFileInfo(newSelectedFolderPath);
86 // The directory can contain files, but since we put dirs first, that should never affect the indices.
87 newSelectedFolderIndex = dirs.indexOf(newSelectedFileInfo);
88 }
89 }
90
91 if (newSelectedFolderPath.isEmpty()) {
92 // When entering into a directory that isn't a parent of the old one, the first
93 // file delegate should be selected.
94 // TODO: is there a cheaper way to do this? QDirIterator doesn't support sorting,
95 // so we can't use that. QQuickFolderListModel uses threads to fetch its data,
96 // so should be considered asynchronous. We might be able to use it, but it would
97 // complicate the code even more...
98 QDir newFolderDir(newFolderPath);
99 if (newFolderDir.exists()) {
100 const QFileInfoList files = newFolderDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot, QDir::DirsFirst);
101 if (!files.isEmpty())
102 newSelectedFolderPath = files.first().absoluteFilePath();
103 }
104 }
105
106 const bool folderSelected = !newSelectedFolderPath.isEmpty();
107 q->setSelectedFolder(folderSelected ? QUrl::fromLocalFile(newSelectedFolderPath) : QUrl());
108 {
109 // Set the appropriate currentIndex for the selected folder. We block signals from ListView
110 // because we don't want folderDialogListViewCurrentIndexChanged to be called, as the file
111 // it gets from the delegate will not be up-to-date (but most importantly because we already
112 // just set the selected folder).
113 QSignalBlocker blocker(attached->folderDialogListView());
114 attached->folderDialogListView()->setCurrentIndex(folderSelected ? newSelectedFolderIndex : -1);
115 }
116 if (folderSelected) {
117 if (QQuickItem *currentItem = attached->folderDialogListView()->currentItem())
118 currentItem->forceActiveFocus();
119 }
120}
121
123{
124 // Let handleClick take care of calling accept().
125}
126
127void QQuickFolderDialogImplPrivate::handleClick(QQuickAbstractButton *button)
128{
129 Q_Q(QQuickFolderDialogImpl);
130 if (buttonRole(button) == QPlatformDialogHelper::AcceptRole && selectedFolder.isValid()) {
131 q->setSelectedFolder(selectedFolder);
132 q->accept();
133 }
134}
135
136/*!
137 \class QQuickFolderDialogImpl
138 \internal
139
140 An interface that QQuickFolderDialog can use to access the non-native Qt Quick FolderDialog.
141
142 Both this and the native implementations are created in QQuickAbstractDialog::create().
143*/
144
145QQuickFolderDialogImpl::QQuickFolderDialogImpl(QObject *parent)
146 : QQuickDialog(*(new QQuickFolderDialogImplPrivate), parent)
147{
148}
149
150QQuickFolderDialogImplAttached *QQuickFolderDialogImpl::qmlAttachedProperties(QObject *object)
151{
152 return new QQuickFolderDialogImplAttached(object);
153}
154
155QUrl QQuickFolderDialogImpl::currentFolder() const
156{
157 Q_D(const QQuickFolderDialogImpl);
158 return d->currentFolder;
159}
160
161void QQuickFolderDialogImpl::setCurrentFolder(const QUrl &currentFolder)
162{
163 qCDebug(lcFolderDialogCurrentFolder) << "setCurrentFolder called with" << currentFolder;
164 Q_D(QQuickFolderDialogImpl);
165 if (currentFolder == d->currentFolder)
166 return;
167
168 const QString oldFolderPath = QQmlFile::urlToLocalFileOrQrc(d->currentFolder);
169
170 d->currentFolder = currentFolder;
171 d->updateSelectedFolder(oldFolderPath);
172 emit currentFolderChanged(d->currentFolder);
173}
174
175QUrl QQuickFolderDialogImpl::selectedFolder() const
176{
177 Q_D(const QQuickFolderDialogImpl);
178 return d->selectedFolder;
179}
180
181void QQuickFolderDialogImpl::setSelectedFolder(const QUrl &selectedFolder)
182{
183 Q_D(QQuickFolderDialogImpl);
184 qCDebug(lcFolderDialogSelectedFolder).nospace() << "setSelectedFolder called with selectedFolder "
185 << selectedFolder << " (d->selectedFolder is " << d->selectedFolder << ")";
186 if (selectedFolder == d->selectedFolder)
187 return;
188
189 d->selectedFolder = selectedFolder;
190 d->updateEnabled();
191 emit selectedFolderChanged(selectedFolder);
192}
193
194QSharedPointer<QFileDialogOptions> QQuickFolderDialogImpl::options() const
195{
196 Q_D(const QQuickFolderDialogImpl);
197 return d->options;
198}
199
200void QQuickFolderDialogImpl::setOptions(const QSharedPointer<QFileDialogOptions> &options)
201{
202 qCDebug(lcFolderDialogOptions).nospace() << "setOptions called with:"
203 << " acceptMode=" << options->acceptMode()
204 << " fileMode=" << options->fileMode()
205 << " initialDirectory=" << options->initialDirectory();
206
207 Q_D(QQuickFolderDialogImpl);
208 d->options = options;
209}
210
211/*!
212 \internal
213
214 These allow QQuickPlatformFileDialog::show() to set custom labels on the
215 dialog buttons without having to know about/go through QQuickFolderDialogImplAttached
216 and QQuickDialogButtonBox.
217*/
218void QQuickFolderDialogImpl::setAcceptLabel(const QString &label)
219{
220 Q_D(QQuickFolderDialogImpl);
221 d->acceptLabel = label;
222 QQuickFolderDialogImplAttached *attached = d->attachedOrWarn();
223 if (!attached)
224 return;
225
226 auto acceptButton = d->buttonBox->standardButton(QPlatformDialogHelper::Open);
227 if (!acceptButton) {
228 qmlWarning(this).nospace() << "Can't set accept label to " << label
229 << "; failed to find Open button in DialogButtonBox of " << this;
230 return;
231 }
232
233 acceptButton->setText(!label.isEmpty()
234 ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Open));
235}
236
237void QQuickFolderDialogImpl::setRejectLabel(const QString &label)
238{
239 Q_D(QQuickFolderDialogImpl);
240 d->rejectLabel = label;
241 if (!d->buttonBox)
242 return;
243
244 auto rejectButton = d->buttonBox->standardButton(QPlatformDialogHelper::Cancel);
245 if (!rejectButton) {
246 qmlWarning(this).nospace() << "Can't set reject label to " << label
247 << "; failed to find Open button in DialogButtonBox of " << this;
248 return;
249 }
250
251 rejectButton->setText(!label.isEmpty()
252 ? label : QQuickDialogButtonBoxPrivate::buttonText(QPlatformDialogHelper::Cancel));
253}
254
255void QQuickFolderDialogImpl::componentComplete()
256{
257 Q_D(QQuickFolderDialogImpl);
258 QQuickDialog::componentComplete();
259
260 // Find the right-most button and set its key navigation so that
261 // tab moves focus to the breadcrumb bar's up button. I tried
262 // doing this via KeyNavigation on the DialogButtonBox in QML,
263 // but it didn't work (probably because it's not the right item).
264 QQuickFolderDialogImplAttached *attached = d->attachedOrWarn();
265 if (!attached)
266 return;
267
268 Q_ASSERT(d->buttonBox);
269 const int buttonCount = d->buttonBox->count();
270 if (buttonCount == 0)
271 return;
272
273 QQuickAbstractButton *rightMostButton = qobject_cast<QQuickAbstractButton *>(
274 d->buttonBox->itemAt(buttonCount - 1));
275 if (!rightMostButton) {
276 qmlWarning(this) << "Can't find right-most button in DialogButtonBox";
277 return;
278 }
279
280 auto keyNavigationAttached = QQuickKeyNavigationAttached::qmlAttachedProperties(rightMostButton);
281 if (!keyNavigationAttached) {
282 qmlWarning(this) << "Can't create attached KeyNavigation object on" << QDebug::toString(rightMostButton);
283 return;
284 }
285
286 keyNavigationAttached->setTab(attached->breadcrumbBar()->upButton());
287}
288
289void QQuickFolderDialogImpl::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
290{
291 Q_D(QQuickFolderDialogImpl);
292 QQuickDialog::itemChange(change, data);
293
294 if (change != QQuickItem::ItemVisibleHasChanged || !isComponentComplete() || !data.boolValue)
295 return;
296
297 QQuickFolderDialogImplAttached *attached = d->attachedOrWarn();
298 if (!attached)
299 return;
300
301 attached->folderDialogListView()->forceActiveFocus();
302 d->updateEnabled();
303}
304
305QQuickFolderDialogImplAttached *QQuickFolderDialogImplPrivate::attachedOrWarn()
306{
307 Q_Q(QQuickFolderDialogImpl);
308 QQuickFolderDialogImplAttached *attached = static_cast<QQuickFolderDialogImplAttached*>(
309 qmlAttachedPropertiesObject<QQuickFolderDialogImpl>(q));
310 if (!attached)
311 qmlWarning(q) << "Expected FileDialogImpl attached object to be present on" << this;
312 return attached;
313}
314
315void QQuickFolderDialogImplAttachedPrivate::folderDialogListViewCurrentIndexChanged()
316{
317 auto folderDialogImpl = qobject_cast<QQuickFolderDialogImpl*>(parent);
318 if (!folderDialogImpl)
319 return;
320
321 auto folderDialogDelegate = qobject_cast<QQuickFileDialogDelegate*>(folderDialogListView->currentItem());
322 if (!folderDialogDelegate)
323 return;
324
325 folderDialogImpl->setSelectedFolder(folderDialogDelegate->file());
326}
327
328QQuickFolderDialogImplAttached::QQuickFolderDialogImplAttached(QObject *parent)
329 : QObject(*(new QQuickFolderDialogImplAttachedPrivate), parent)
330{
331 if (!qobject_cast<QQuickFolderDialogImpl*>(parent)) {
332 qmlWarning(this) << "FolderDialogImpl attached properties should only be "
333 << "accessed through the root FileDialogImpl instance";
334 }
335}
336
337QQuickListView *QQuickFolderDialogImplAttached::folderDialogListView() const
338{
339 Q_D(const QQuickFolderDialogImplAttached);
340 return d->folderDialogListView;
341}
342
343void QQuickFolderDialogImplAttached::setFolderDialogListView(QQuickListView *folderDialogListView)
344{
345 Q_D(QQuickFolderDialogImplAttached);
346 if (folderDialogListView == d->folderDialogListView)
347 return;
348
349 d->folderDialogListView = folderDialogListView;
350
351 QObjectPrivate::connect(d->folderDialogListView, &QQuickListView::currentIndexChanged,
352 d, &QQuickFolderDialogImplAttachedPrivate::folderDialogListViewCurrentIndexChanged);
353
354 emit folderDialogListViewChanged();
355}
356
357QQuickFolderBreadcrumbBar *QQuickFolderDialogImplAttached::breadcrumbBar() const
358{
359 Q_D(const QQuickFolderDialogImplAttached);
360 return d->breadcrumbBar;
361}
362
363void QQuickFolderDialogImplAttached::setBreadcrumbBar(QQuickFolderBreadcrumbBar *breadcrumbBar)
364{
365 Q_D(QQuickFolderDialogImplAttached);
366 if (breadcrumbBar == d->breadcrumbBar)
367 return;
368
369 d->breadcrumbBar = breadcrumbBar;
370 emit breadcrumbBarChanged();
371}
372
373QT_END_NAMESPACE
374
375#include "moc_qquickfolderdialogimpl_p.cpp"
QQuickFolderDialogImplAttached * attachedOrWarn()
void updateSelectedFolder(const QString &oldFolderPath)
void handleClick(QQuickAbstractButton *button) override