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
4#include "qquickfolderbreadcrumbbar_p.h"
5#include "qquickfolderbreadcrumbbar_p_p.h"
6
7#include <QtCore/qdir.h>
8#include <QtCore/qloggingcategory.h>
9#if QT_CONFIG(shortcut)
10#include <QtGui/private/qshortcutmap_p.h>
11#endif
12#include <QtGui/private/qguiapplication_p.h>
13#include <QtQml/QQmlFile>
14#include <QtQuick/private/qquicktextinput_p.h>
15#include <QtQuickTemplates2/private/qquickabstractbutton_p.h>
16#include <QtQuickTemplates2/private/qquickpopupitem_p_p.h>
17#include <QtQuickTemplates2/private/qquickshortcutcontext_p_p.h>
18
19#include "qquickfiledialogimpl_p.h"
20#include "qquickfiledialogimpl_p_p.h"
21#include "qquickfolderdialogimpl_p.h"
22#include "qquickfolderdialogimpl_p_p.h"
23
24QT_BEGIN_NAMESPACE
25
26Q_LOGGING_CATEGORY(lcFolderBreadcrumbBar, "qt.quick.dialogs.folderbreadcrumbbar")
27Q_LOGGING_CATEGORY(lcContentSize, "qt.quick.dialogs.folderbreadcrumbbar.contentsize")
28Q_LOGGING_CATEGORY(lcDelegates, "qt.quick.dialogs.folderbreadcrumbbar.delegates")
29Q_LOGGING_CATEGORY(lcShortcuts, "qt.quick.dialogs.folderbreadcrumbbar.shortcuts")
30Q_LOGGING_CATEGORY(lcTextInput, "qt.quick.dialogs.folderbreadcrumbbar.textinput")
31Q_LOGGING_CATEGORY(lcCurrentItem, "qt.quick.dialogs.folderbreadcrumbbar.currentitem")
32
33QQuickItem *QQuickFolderBreadcrumbBarPrivate::createDelegateItem(QQmlComponent *component, const QVariantMap &initialProperties)
34{
35 Q_Q(QQuickFolderBreadcrumbBar);
36 // If we don't use the correct context, it won't be possible to refer to
37 // the control's id from within the delegates.
38 QQmlContext *context = component->creationContext();
39 // The component might not have been created in QML, in which case
40 // the creation context will be null and we have to create it ourselves.
41 if (!context)
42 context = qmlContext(q);
43
44 // If we have initial properties we assume that all necessary information is passed via
45 // initial properties.
46 if (!component->isBound() && initialProperties.isEmpty()) {
47 context = new QQmlContext(context, q);
48 context->setContextObject(q);
49 }
50
51 QQuickItem *item = qobject_cast<QQuickItem*>(o: component->createWithInitialProperties(initialProperties, context));
52 if (item)
53 QQml_setParent_noEvent(object: item, parent: q);
54 qCDebug(lcDelegates) << "- created delegate item" << item << "with initialProperties" << initialProperties;
55 return item;
56}
57
58QString QQuickFolderBreadcrumbBarPrivate::folderBaseName(const QString &folderPath)
59{
60 if (folderPath == QLatin1String("/")) {
61 // Unix root.
62 return folderPath;
63 } else if (folderPath.endsWith(s: QLatin1String(":/"))) {
64 // Windows drive.
65 return folderPath.mid(position: 0, n: folderPath.size() - 1);
66 }
67 const QString baseName = folderPath.mid(position: folderPath.lastIndexOf(c: QLatin1Char('/')) + 1);
68 return baseName;
69}
70
71/*!
72 \internal
73
74 Returns \c { "/foo", "/foo/bar", "/foo/bar/baz" } if \a folder is \c "/foo/bar/baz".
75*/
76QStringList QQuickFolderBreadcrumbBarPrivate::crumbPathsForFolder(const QUrl &folder)
77{
78 const QString folderPath = QDir::fromNativeSeparators(pathName: QQmlFile::urlToLocalFileOrQrc(folder));
79 QDir dir(folderPath);
80 // In order to collect the paths for each breadcrumb, we need to work backwards, so we prepend.
81 QStringList paths;
82 do {
83 paths.prepend(t: dir.absolutePath());
84 } while (dir.cdUp());
85 return paths;
86}
87
88void QQuickFolderBreadcrumbBarPrivate::repopulate()
89{
90 Q_Q(QQuickFolderBreadcrumbBar);
91 qCDebug(lcDelegates) << "attemping to repopulate breadcrumb bar using folder...";
92
93 if (repopulating)
94 return;
95
96 if (!buttonDelegate || !separatorDelegate || !q->contentItem()) {
97 qCWarning(lcDelegates) << "Both delegates and contentItem must be set before repopulating";
98 return;
99 }
100
101 QBoolBlocker repopulateGuard(repopulating);
102
103 auto failureCleanup = [this, q](){
104 folderPaths.clear();
105 while (q->count() > 0)
106 q->removeItem(item: q->itemAt(index: 0));
107 };
108
109 qCDebug(lcDelegates) << "- getting paths for directory" << dialogFolder();
110 folderPaths = crumbPathsForFolder(folder: dialogFolder());
111
112 while (q->count() > 0)
113 q->removeItem(item: q->itemAt(index: 0));
114
115 for (int i = 0; i < folderPaths.size(); ++i) {
116 const QString &folderPath = folderPaths.at(i);
117
118 QVariantMap initialProperties = {
119 { QStringLiteral("index"), QVariant::fromValue(value: i) },
120 { QStringLiteral("folderName"), QVariant::fromValue(value: folderBaseName(folderPath)) }
121 };
122 QQuickItem *buttonItem = createDelegateItem(component: buttonDelegate, initialProperties);
123 if (!buttonItem) {
124 qCWarning(lcDelegates) << "Failed creating breadcrumb buttonDelegate item:\n" << buttonDelegate->errorString();
125 failureCleanup();
126 break;
127 }
128 if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object: buttonItem)) {
129 QObjectPrivate::connect(sender: button, signal: &QQuickAbstractButton::clicked,
130 receiverPrivate: this, slot: &QQuickFolderBreadcrumbBarPrivate::crumbClicked);
131 }
132 insertItem(index: q->count(), item: buttonItem);
133
134 // Don't add a separator for the last button.
135 if (i < folderPaths.size() - 1) {
136 initialProperties = {};
137 QQuickItem *separatorItem = createDelegateItem(component: separatorDelegate, initialProperties);
138 if (!separatorItem) {
139 qCWarning(lcDelegates) << "Failed creating breadcrumb separatorDelegate item:\n" << buttonDelegate->errorString();
140 failureCleanup();
141 break;
142 }
143 insertItem(index: q->count(), item: separatorItem);
144 }
145 }
146
147 const int finalCount = q->count();
148 // We would do - 2, since separators are included in the count,
149 // but as we don't add a separator for the last button, we only need to subtract 1.
150 const int newCurrentIndex = finalCount > 2 ? finalCount - 1 : -1;
151 qCDebug(lcDelegates) << "- setting currentIndex to" << newCurrentIndex;
152 q->setCurrentIndex(newCurrentIndex);
153
154 updateImplicitContentSize();
155
156 qCDebug(lcDelegates) << "... bar now contains" << q->count()
157 << "buttons and separators in total, for the following paths:" << folderPaths;
158}
159
160void QQuickFolderBreadcrumbBarPrivate::crumbClicked()
161{
162 Q_Q(QQuickFolderBreadcrumbBar);
163 qCDebug(lcCurrentItem) << "updateCurrentIndex called by sender" << q->sender();
164 QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object: q->sender());
165 if (button) {
166 const int buttonIndex = contentModel->indexOf(object: button, objectContext: nullptr);
167 q->setCurrentIndex(buttonIndex);
168 const QUrl folderUrl = QUrl::fromLocalFile(localfile: folderPaths.at(i: buttonIndex / 2));
169 // TODO: don't repopulate the whole model when clicking on crumbs
170 qCDebug(lcCurrentItem) << "setting file dialog's folder to" << folderUrl;
171 setDialogFolder(folderUrl);
172 }
173}
174
175void QQuickFolderBreadcrumbBarPrivate::folderChanged()
176{
177 if (componentComplete)
178 repopulate();
179}
180
181static inline QString upButtonName()
182{
183 return QStringLiteral("upButton");
184}
185
186void QQuickFolderBreadcrumbBarPrivate::cancelUpButton()
187{
188 Q_Q(QQuickFolderBreadcrumbBar);
189 quickCancelDeferred(object: q, property: upButtonName());
190}
191
192void QQuickFolderBreadcrumbBarPrivate::executeUpButton(bool complete)
193{
194 Q_Q(QQuickFolderBreadcrumbBar);
195 if (upButton.wasExecuted())
196 return;
197
198 if (!upButton || complete)
199 quickBeginDeferred(object: q, property: upButtonName(), delegate&: upButton);
200 if (complete)
201 quickCompleteDeferred(object: q, property: upButtonName(), delegate&: upButton);
202}
203
204void QQuickFolderBreadcrumbBarPrivate::goUp()
205{
206 QDir dir(QQmlFile::urlToLocalFileOrQrc(dialogFolder()));
207 dir.cdUp();
208 setDialogFolder(QUrl::fromLocalFile(localfile: dir.absolutePath()));
209}
210
211static inline QString textFieldName()
212{
213 return QStringLiteral("textField");
214}
215
216void QQuickFolderBreadcrumbBarPrivate::cancelTextField()
217{
218 Q_Q(QQuickFolderBreadcrumbBar);
219 quickCancelDeferred(object: q, property: textFieldName());
220}
221
222void QQuickFolderBreadcrumbBarPrivate::executeTextField(bool complete)
223{
224 Q_Q(QQuickFolderBreadcrumbBar);
225 if (textField.wasExecuted())
226 return;
227
228 if (!textField || complete)
229 quickBeginDeferred(object: q, property: textFieldName(), delegate&: textField);
230 if (complete)
231 quickCompleteDeferred(object: q, property: textFieldName(), delegate&: textField);
232}
233
234void QQuickFolderBreadcrumbBarPrivate::toggleTextFieldVisibility()
235{
236 textField->setText(QQmlFile::urlToLocalFileOrQrc(dialogFolder()));
237
238 qCDebug(lcTextInput).nospace() << "text field visibility was " << textField->isVisible()
239 << "; setting it to " << !textField->isVisible();
240 textField->setVisible(!textField->isVisible());
241
242 if (textField->isVisible()) {
243 // The text field is now visible, so give it focus,
244 // select the text, and let it handle escape/back.
245 textField->forceActiveFocus(reason: Qt::ShortcutFocusReason);
246 textField->selectAll();
247 }
248
249 // We connect to the TextField's visibleChanged signal, so textFieldVisibleChanged()
250 // will get called automatically and we don't need to call it here.
251
252 contentItem->setVisible(!textField->isVisible());
253
254 // When the TextField is visible, certain items in the dialog need to be disabled.
255 if (auto fileDialog = asFileDialog()) {
256 auto fileDialogPrivate = QQuickFileDialogImplPrivate::get(dialog: fileDialog);
257 fileDialogPrivate->updateEnabled();
258 } else if (auto folderDialog = asFolderDialog()) {
259 auto folderDialogPrivate = QQuickFolderDialogImplPrivate::get(dialog: folderDialog);
260 folderDialogPrivate->updateEnabled();
261 }
262}
263
264void QQuickFolderBreadcrumbBarPrivate::textFieldAccepted()
265{
266 const QUrl fileUrl = QUrl::fromLocalFile(localfile: textField->text());
267 const auto fileDialog = asFileDialog();
268 const bool mustExist = fileDialog ? fileDialog->options()->acceptMode() != QFileDialogOptions::AcceptSave : true;
269 const bool enteredPathIsValidUrl = fileUrl.isValid();
270 bool enteredPathExists = false;
271 bool enteredPathIsDir = false;
272 if (enteredPathIsValidUrl) {
273 const QFileInfo fileInfo(textField->text());
274 enteredPathExists = fileInfo.exists();
275 if (enteredPathExists)
276 enteredPathIsDir = fileInfo.isDir();
277 }
278
279 qCDebug(lcTextInput).nospace() << "text field accepted -"
280 << " text=" << textField->text()
281 << " fileUrl=" << fileUrl
282 << " mustExist=" << mustExist
283 << " enteredPathIsValidUrl=" << enteredPathIsValidUrl
284 << " enteredPathExists=" << enteredPathExists
285 << " enteredPathIsDir=" << enteredPathIsDir;
286
287 if (enteredPathIsDir && (enteredPathExists || !mustExist)) {
288 qCDebug(lcTextInput) << "path entered is a folder; setting folder";
289 setDialogFolder(fileUrl);
290 } else if (!enteredPathIsDir && (enteredPathExists || !mustExist)) {
291 qCDebug(lcTextInput) << "path entered is a file; setting file and calling accept()";
292 if (isFileDialog()) {
293 auto fileDialog = asFileDialog();
294 fileDialog->setSelectedFile(fileUrl);
295 fileDialog->accept();
296 } else {
297 setDialogFolder(fileUrl);
298 }
299 } else {
300 qCDebug(lcTextInput) << "path entered is not valid; not setting file/folder";
301 }
302
303 // If the enter key was pressed and the dialog closed, the text input will lose
304 // active focus, and textFieldActiveFocusChanged() will toggle its visibility.
305 // We should only toggle visibility if the dialog is actually closed, otherwise
306 // we'll end up toggling twice, and the text input will be visible the next time
307 // the dialog is opened.
308 if (dialog->isVisible())
309 toggleTextFieldVisibility();
310}
311
312void QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged()
313{
314 qCDebug(lcShortcuts) << "text field was either hidden or shown";
315
316 if (textField && textField->isVisible())
317 handleTextFieldShown();
318 else
319 handleTextFieldHidden();
320}
321
322void QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged()
323{
324 qCDebug(lcTextInput) << "text field activeFocus changed to" << textField->hasActiveFocus();
325
326 // When the text field loses focus, it should be hidden.
327 if (!textField->hasActiveFocus() && textField->isVisible())
328 toggleTextFieldVisibility();
329}
330
331/*
332 When the text field is visible:
333
334 - Ctrl+L should do nothing (matches e.g. Ubuntu and Windows)
335 - Escape/back should hide it
336*/
337void QQuickFolderBreadcrumbBarPrivate::handleTextFieldShown()
338{
339#if QT_CONFIG(shortcut)
340 if (editPathToggleShortcutId == 0)
341 return;
342
343 qCDebug(lcShortcuts) << "text field was shown; ungrabbing edit path shortcut";
344 ungrabEditPathShortcut();
345#endif
346}
347
348/*
349 When the text field is not visible:
350
351 - Ctrl+L should make it visible
352 - Escape/back should close the dialog
353*/
354void QQuickFolderBreadcrumbBarPrivate::handleTextFieldHidden()
355{
356#if QT_CONFIG(shortcut)
357 Q_Q(QQuickFolderBreadcrumbBar);
358
359 QGuiApplicationPrivate *appPrivate = QGuiApplicationPrivate::instance();
360 qCDebug(lcShortcuts) << "text field was hidden; grabbing edit path shortcut";
361
362 if (editPathToggleShortcutId == 0) {
363 editPathToggleShortcutId = appPrivate->shortcutMap.addShortcut(
364 owner: q, key: Qt::CTRL | Qt::Key_L, context: Qt::WindowShortcut, matcher: QQuickShortcutContext::matcher);
365 }
366
367 qCDebug(lcShortcuts).nospace() << "... editPathToggleShortcutId=" << editPathToggleShortcutId;
368#endif
369}
370
371void QQuickFolderBreadcrumbBarPrivate::ungrabEditPathShortcut()
372{
373#if QT_CONFIG(shortcut)
374 Q_Q(QQuickFolderBreadcrumbBar);
375 QGuiApplicationPrivate *appPrivate = QGuiApplicationPrivate::instance();
376 if (editPathToggleShortcutId != 0) {
377 appPrivate->shortcutMap.removeShortcut(id: editPathToggleShortcutId, owner: q);
378 editPathToggleShortcutId = 0;
379 }
380#endif
381}
382
383QQuickFileDialogImpl *QQuickFolderBreadcrumbBarPrivate::asFileDialog() const
384{
385 return qobject_cast<QQuickFileDialogImpl*>(object: dialog);
386}
387
388QQuickFolderDialogImpl *QQuickFolderBreadcrumbBarPrivate::asFolderDialog() const
389{
390 return qobject_cast<QQuickFolderDialogImpl*>(object: dialog);
391}
392
393bool QQuickFolderBreadcrumbBarPrivate::isFileDialog() const
394{
395 return asFileDialog();
396}
397
398QUrl QQuickFolderBreadcrumbBarPrivate::dialogFolder() const
399{
400 return dialog->property(name: "currentFolder").toUrl();
401}
402
403void QQuickFolderBreadcrumbBarPrivate::setDialogFolder(const QUrl &folder)
404{
405 Q_Q(QQuickFolderBreadcrumbBar);
406 if (!dialog->setProperty(name: "currentFolder", value: folder))
407 qmlWarning(me: q) << "Failed to set currentFolder property of dialog" << dialog->objectName() << "to" << folder;
408}
409
410qreal QQuickFolderBreadcrumbBarPrivate::getContentWidth() const
411{
412 Q_Q(const QQuickFolderBreadcrumbBar);
413 const int count = contentModel->count();
414 qreal totalWidth = qMax(a: 0, b: count - 1) * spacing;
415 for (int i = 0; i < count; ++i) {
416 QQuickItem *item = q->itemAt(index: i);
417 if (item) {
418 QQuickItemPrivate *p = QQuickItemPrivate::get(item);
419 if (!p->widthValid())
420 totalWidth += item->implicitWidth();
421 else
422 totalWidth += item->width();
423 }
424 }
425 qCDebug(lcContentSize) << "content width:" << totalWidth;
426 return totalWidth;
427}
428
429qreal QQuickFolderBreadcrumbBarPrivate::getContentHeight() const
430{
431 Q_Q(const QQuickFolderBreadcrumbBar);
432 const int count = contentModel->count();
433 qreal maxHeight = 0;
434 for (int i = 0; i < count; ++i) {
435 QQuickItem *item = q->itemAt(index: i);
436 if (item)
437 maxHeight = qMax(a: maxHeight, b: item->implicitHeight());
438 }
439 qCDebug(lcContentSize) << "content height:" << maxHeight;
440 return maxHeight;
441}
442
443void QQuickFolderBreadcrumbBarPrivate::resizeContent()
444{
445 Q_Q(QQuickFolderBreadcrumbBar);
446 if (contentItem) {
447 const int upButtonSpace = q->upButton() ? q->upButton()->width() + upButtonSpacing : 0;
448 contentItem->setPosition(QPointF(q->leftPadding() + upButtonSpace, q->topPadding()));
449 contentItem->setSize(QSizeF(q->availableWidth() - upButtonSpace, q->availableHeight()));
450
451 if (textField) {
452 textField->setPosition(contentItem->position());
453 textField->setSize(contentItem->size());
454 }
455 }
456}
457
458void QQuickFolderBreadcrumbBarPrivate::itemGeometryChanged(QQuickItem *item, QQuickGeometryChange change, const QRectF &diff)
459{
460 QQuickContainerPrivate::itemGeometryChanged(item, change, diff);
461 if (change.sizeChange())
462 updateImplicitContentSize();
463}
464
465void QQuickFolderBreadcrumbBarPrivate::itemImplicitWidthChanged(QQuickItem *item)
466{
467 QQuickContainerPrivate::itemImplicitWidthChanged(item);
468 if (item != contentItem)
469 updateImplicitContentWidth();
470}
471
472void QQuickFolderBreadcrumbBarPrivate::itemImplicitHeightChanged(QQuickItem *item)
473{
474 QQuickContainerPrivate::itemImplicitHeightChanged(item);
475 if (item != contentItem)
476 updateImplicitContentHeight();
477}
478
479/*!
480 \internal
481
482 Private class for breadcrumb navigation of a directory.
483
484 Given a FileDialog, FolderBreadCrumbbar creates breadcrumb buttons and
485 separators from the specified delegate components.
486*/
487
488QQuickFolderBreadcrumbBar::QQuickFolderBreadcrumbBar(QQuickItem *parent)
489 : QQuickContainer(*(new QQuickFolderBreadcrumbBarPrivate), parent)
490{
491 Q_D(QQuickFolderBreadcrumbBar);
492 d->changeTypes |= QQuickItemPrivate::Geometry | QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
493}
494
495QQuickDialog *QQuickFolderBreadcrumbBar::dialog() const
496{
497 Q_D(const QQuickFolderBreadcrumbBar);
498 return d->dialog;
499}
500
501void QQuickFolderBreadcrumbBar::setDialog(QQuickDialog *dialog)
502{
503 Q_D(QQuickFolderBreadcrumbBar);
504 if (dialog == d->dialog)
505 return;
506
507 if (d->dialog) {
508 if (auto fileDialog = d->asFileDialog()) {
509 // TODO: rename impl's currentFolder too, when name is decided
510 QObjectPrivate::disconnect(sender: fileDialog, signal: &QQuickFileDialogImpl::currentFolderChanged,
511 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::folderChanged);
512 } else if (auto folderDialog = d->asFolderDialog()) {
513 QObjectPrivate::disconnect(sender: folderDialog, signal: &QQuickFolderDialogImpl::currentFolderChanged,
514 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::folderChanged);
515 }
516 }
517
518 d->dialog = dialog;
519
520 if (d->dialog) {
521 if (auto fileDialog = d->asFileDialog()) {
522 QObjectPrivate::connect(sender: fileDialog, signal: &QQuickFileDialogImpl::currentFolderChanged,
523 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::folderChanged);
524 } else if (auto folderDialog = d->asFolderDialog()) {
525 QObjectPrivate::connect(sender: folderDialog, signal: &QQuickFolderDialogImpl::currentFolderChanged,
526 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::folderChanged);
527 }
528 }
529
530 emit dialogChanged();
531}
532
533QQmlComponent *QQuickFolderBreadcrumbBar::buttonDelegate()
534{
535 Q_D(QQuickFolderBreadcrumbBar);
536 return d->buttonDelegate;
537}
538
539void QQuickFolderBreadcrumbBar::setButtonDelegate(QQmlComponent *delegate)
540{
541 Q_D(QQuickFolderBreadcrumbBar);
542 qCDebug(lcFolderBreadcrumbBar) << "setButtonDelegate called with" << delegate;
543 if (d->componentComplete) {
544 // Simplify the code by disallowing this.
545 qCWarning(lcFolderBreadcrumbBar) << "BreadcrumbBar does not support setting delegates after component completion";
546 return;
547 }
548
549 if (delegate == d->buttonDelegate)
550 return;
551
552 d->buttonDelegate = delegate;
553 emit buttonDelegateChanged();
554}
555
556QQmlComponent *QQuickFolderBreadcrumbBar::separatorDelegate()
557{
558 Q_D(QQuickFolderBreadcrumbBar);
559 return d->separatorDelegate;
560}
561
562void QQuickFolderBreadcrumbBar::setSeparatorDelegate(QQmlComponent *delegate)
563{
564 Q_D(QQuickFolderBreadcrumbBar);
565 qCDebug(lcFolderBreadcrumbBar) << "setSeparatorDelegate called with" << delegate;
566 if (d->componentComplete) {
567 qCWarning(lcFolderBreadcrumbBar) << "BreadcrumbBar does not support setting delegates after component completion";
568 return;
569 }
570
571 if (delegate == d->separatorDelegate)
572 return;
573
574 d->separatorDelegate = delegate;
575 emit separatorDelegateChanged();
576}
577
578QQuickAbstractButton *QQuickFolderBreadcrumbBar::upButton()
579{
580 Q_D(QQuickFolderBreadcrumbBar);
581 if (!d->upButton)
582 d->executeUpButton();
583 return d->upButton;
584}
585
586void QQuickFolderBreadcrumbBar::setUpButton(QQuickAbstractButton *upButton)
587{
588 Q_D(QQuickFolderBreadcrumbBar);
589 if (upButton == d->upButton)
590 return;
591
592 if (!d->upButton.isExecuting())
593 d->cancelUpButton();
594
595 if (d->upButton) {
596 QObjectPrivate::disconnect(sender: d->upButton.data(), signal: &QQuickAbstractButton::clicked,
597 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::goUp);
598 }
599
600 QQuickControlPrivate::hideOldItem(item: d->upButton);
601 d->upButton = upButton;
602 if (d->upButton) {
603 if (!d->upButton->parentItem())
604 d->upButton->setParentItem(this);
605
606 QObjectPrivate::connect(sender: d->upButton.data(), signal: &QQuickAbstractButton::clicked,
607 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::goUp);
608 }
609 if (!d->upButton.isExecuting())
610 emit upButtonChanged();
611}
612
613int QQuickFolderBreadcrumbBar::upButtonSpacing() const
614{
615 Q_D(const QQuickFolderBreadcrumbBar);
616 return d->upButtonSpacing;
617}
618
619void QQuickFolderBreadcrumbBar::setUpButtonSpacing(int upButtonSpacing)
620{
621 Q_D(QQuickFolderBreadcrumbBar);
622 if (upButtonSpacing == d->upButtonSpacing)
623 return;
624
625 d->upButtonSpacing = upButtonSpacing;
626 emit upButtonSpacingChanged();
627}
628
629QQuickTextField *QQuickFolderBreadcrumbBar::textField()
630{
631 Q_D(QQuickFolderBreadcrumbBar);
632 return d->textField;
633}
634
635void QQuickFolderBreadcrumbBar::setTextField(QQuickTextField *textField)
636{
637 Q_D(QQuickFolderBreadcrumbBar);
638 if (textField == d->textField)
639 return;
640
641 if (!d->textField.isExecuting())
642 d->cancelUpButton();
643
644 if (d->textField)
645 d->handleTextFieldHidden();
646
647 if (d->textField) {
648 QObjectPrivate::disconnect(sender: d->textField.data(), signal: &QQuickTextInput::visibleChanged,
649 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged);
650 QObjectPrivate::disconnect(sender: d->textField.data(), signal: &QQuickTextInput::activeFocusChanged,
651 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged);
652 QObjectPrivate::disconnect(sender: d->textField.data(), signal: &QQuickTextInput::accepted,
653 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldAccepted);
654 }
655
656 QQuickControlPrivate::hideOldItem(item: d->textField);
657 d->textField = textField;
658 if (d->textField) {
659 if (!d->textField->parentItem())
660 d->textField->setParentItem(this);
661
662 d->textField->setVisible(false);
663
664 QObjectPrivate::connect(sender: d->textField.data(), signal: &QQuickTextInput::visibleChanged,
665 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldVisibleChanged);
666 QObjectPrivate::connect(sender: d->textField.data(), signal: &QQuickTextInput::activeFocusChanged,
667 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldActiveFocusChanged);
668 QObjectPrivate::connect(sender: d->textField.data(), signal: &QQuickTextInput::accepted,
669 receiverPrivate: d, slot: &QQuickFolderBreadcrumbBarPrivate::textFieldAccepted);
670 }
671 if (!d->textField.isExecuting())
672 emit textFieldChanged();
673}
674
675bool QQuickFolderBreadcrumbBar::event(QEvent *event)
676{
677#if QT_CONFIG(shortcut)
678 Q_D(QQuickFolderBreadcrumbBar);
679 if (event->type() == QEvent::Shortcut) {
680 QShortcutEvent *shortcutEvent = static_cast<QShortcutEvent *>(event);
681 if (shortcutEvent->shortcutId() == d->editPathToggleShortcutId) {
682 d->toggleTextFieldVisibility();
683 return true;
684 } else if (shortcutEvent->shortcutId() == d->goUpShortcutId) {
685 d->goUp();
686 }
687 }
688#endif
689 return QQuickItem::event(event);
690}
691
692void QQuickFolderBreadcrumbBar::keyPressEvent(QKeyEvent *event)
693{
694#if QT_CONFIG(shortcut)
695 Q_D(QQuickFolderBreadcrumbBar);
696
697 if (event->matches(key: QKeySequence::Cancel) && d->textField->isVisible()) {
698 d->toggleTextFieldVisibility();
699 event->accept();
700 } else
701#endif
702 {
703 QQuickContainer::keyPressEvent(event);
704 }
705}
706
707void QQuickFolderBreadcrumbBar::componentComplete()
708{
709 Q_D(QQuickFolderBreadcrumbBar);
710 qCDebug(lcFolderBreadcrumbBar) << "componentComplete";
711 QQuickContainer::componentComplete();
712 d->repopulate();
713
714 if (d->textField) {
715 // Force it to be updated as setTextField() is too early to do it.
716 d->textFieldVisibleChanged();
717 }
718}
719
720void QQuickFolderBreadcrumbBar::itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData &data)
721{
722 Q_D(QQuickFolderBreadcrumbBar);
723 QQuickContainer::itemChange(change, data);
724
725 if (change == QQuickItem::ItemVisibleHasChanged && isComponentComplete()) {
726 if (data.boolValue && d->dialog->isVisible()) {
727 // It's visible.
728 d->handleTextFieldHidden();
729
730#if QT_CONFIG(shortcut)
731 d->goUpShortcutId = QGuiApplicationPrivate::instance()->shortcutMap.addShortcut(
732 owner: this, key: QKeySequence(Qt::ALT | Qt::Key_Up), context: Qt::WindowShortcut, matcher: QQuickShortcutContext::matcher);
733#endif
734 } else {
735 // It's hidden.
736 // Hide the text field so that when the dialog gets opened again, it's not still visible.
737 if (d->textField)
738 d->textField->setVisible(false);
739
740 // Make the ListView visible again.
741 if (d->contentItem)
742 d->contentItem->setVisible(true);
743
744 // We also need to ungrab the edit path shortcut when we're not visible.
745 d->ungrabEditPathShortcut();
746
747#if QT_CONFIG(shortcut)
748 if (d->goUpShortcutId != 0) {
749 QGuiApplicationPrivate::instance()->shortcutMap.removeShortcut(id: d->goUpShortcutId, owner: this);
750 d->goUpShortcutId = 0;
751 }
752#endif
753 }
754 }
755}
756
757bool QQuickFolderBreadcrumbBar::isContent(QQuickItem *item) const
758{
759 if (!qmlContext(item))
760 return false;
761
762 if (QQuickItemPrivate::get(item)->isTransparentForPositioner())
763 return false;
764
765 return true;
766}
767
768QFont QQuickFolderBreadcrumbBar::defaultFont() const
769{
770 // TODO
771 return QQuickTheme::font(scope: QQuickTheme::TabBar);
772}
773
774#if QT_CONFIG(accessibility)
775QAccessible::Role QQuickFolderBreadcrumbBar::accessibleRole() const
776{
777 // TODO
778 return QAccessible::PageTabList;
779}
780#endif
781
782QT_END_NAMESPACE
783
784#include "moc_qquickfolderbreadcrumbbar_p.cpp"
785

source code of qtdeclarative/src/quickdialogs/quickdialogsquickimpl/qquickfolderbreadcrumbbar.cpp