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 "qquickfiledialogdelegate_p.h" |
5 | |
6 | #include <QtCore/qfileinfo.h> |
7 | #include <QtGui/qpa/qplatformtheme.h> |
8 | #include <QtQml/QQmlFile> |
9 | #include <QtQml/qqmlexpression.h> |
10 | #include <QtQuick/private/qquicklistview_p.h> |
11 | #include <QtQuickTemplates2/private/qquickitemdelegate_p_p.h> |
12 | |
13 | #include "qquickfiledialogimpl_p.h" |
14 | #include "qquickfiledialogimpl_p_p.h" |
15 | #include "qquickfolderdialogimpl_p.h" |
16 | |
17 | QT_BEGIN_NAMESPACE |
18 | |
19 | class QQuickFileDialogDelegatePrivate : public QQuickItemDelegatePrivate |
20 | { |
21 | public: |
22 | Q_DECLARE_PUBLIC(QQuickFileDialogDelegate) |
23 | |
24 | void highlightFile(); |
25 | void chooseFile(); |
26 | |
27 | bool acceptKeyClick(Qt::Key key) const override; |
28 | |
29 | QQuickDialog *dialog = nullptr; |
30 | QQuickFileDialogImpl *fileDialog = nullptr; |
31 | QQuickFolderDialogImpl *folderDialog = nullptr; |
32 | QUrl file; |
33 | }; |
34 | |
35 | void QQuickFileDialogDelegatePrivate::highlightFile() |
36 | { |
37 | Q_Q(QQuickFileDialogDelegate); |
38 | QQuickListViewAttached *attached = static_cast<QQuickListViewAttached*>( |
39 | qmlAttachedPropertiesObject<QQuickListView>(obj: q)); |
40 | if (!attached) |
41 | return; |
42 | |
43 | QQmlContext *delegateContext = qmlContext(q); |
44 | if (!delegateContext) |
45 | return; |
46 | |
47 | bool converted = false; |
48 | const int index = q->property(name: "index" ).toInt(ok: &converted); |
49 | if (converted) { |
50 | attached->view()->setCurrentIndex(index); |
51 | if (fileDialog) |
52 | fileDialog->setSelectedFile(file); |
53 | else if (folderDialog) |
54 | folderDialog->setSelectedFolder(file); |
55 | } |
56 | } |
57 | |
58 | void QQuickFileDialogDelegatePrivate::chooseFile() |
59 | { |
60 | const QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(file)); |
61 | if (fileInfo.isDir()) { |
62 | // If it's a directory, navigate to it. |
63 | if (fileDialog) |
64 | fileDialog->setCurrentFolder(currentFolder: file); |
65 | else |
66 | folderDialog->setCurrentFolder(file); |
67 | } else { |
68 | Q_ASSERT(fileDialog); |
69 | // Otherwise it's a file, so select it and close the dialog. |
70 | fileDialog->setSelectedFile(file); |
71 | |
72 | // Prioritize closing the dialog with QQuickDialogPrivate::handleClick() over QQuickDialog::accept() |
73 | const QQuickFileDialogImplAttached *attached = QQuickFileDialogImplPrivate::get(dialog: fileDialog)->attachedOrWarn(); |
74 | if (Q_LIKELY(attached)) { |
75 | auto *openButton = attached->buttonBox()->standardButton(button: QPlatformDialogHelper::Open); |
76 | if (Q_LIKELY(openButton)) { |
77 | emit openButton->clicked(); |
78 | return; |
79 | } |
80 | } |
81 | fileDialog->accept(); |
82 | } |
83 | } |
84 | |
85 | bool QQuickFileDialogDelegatePrivate::acceptKeyClick(Qt::Key key) const |
86 | { |
87 | return key == Qt::Key_Return || key == Qt::Key_Enter; |
88 | } |
89 | |
90 | QQuickFileDialogDelegate::QQuickFileDialogDelegate(QQuickItem *parent) |
91 | : QQuickItemDelegate(*(new QQuickFileDialogDelegatePrivate), parent) |
92 | { |
93 | Q_D(QQuickFileDialogDelegate); |
94 | // Clicking and tabbing should result in it getting focus, |
95 | // as e.g. Ubuntu and Windows both allow tabbing through file dialogs. |
96 | setFocusPolicy(Qt::StrongFocus); |
97 | setCheckable(true); |
98 | QObjectPrivate::connect(sender: this, signal: &QQuickFileDialogDelegate::clicked, |
99 | receiverPrivate: d, slot: &QQuickFileDialogDelegatePrivate::highlightFile); |
100 | QObjectPrivate::connect(sender: this, signal: &QQuickFileDialogDelegate::doubleClicked, |
101 | receiverPrivate: d, slot: &QQuickFileDialogDelegatePrivate::chooseFile); |
102 | } |
103 | |
104 | QQuickDialog *QQuickFileDialogDelegate::dialog() const |
105 | { |
106 | Q_D(const QQuickFileDialogDelegate); |
107 | return d->dialog; |
108 | } |
109 | |
110 | void QQuickFileDialogDelegate::setDialog(QQuickDialog *dialog) |
111 | { |
112 | Q_D(QQuickFileDialogDelegate); |
113 | if (dialog == d->dialog) |
114 | return; |
115 | |
116 | d->dialog = dialog; |
117 | d->fileDialog = qobject_cast<QQuickFileDialogImpl*>(object: dialog); |
118 | d->folderDialog = qobject_cast<QQuickFolderDialogImpl*>(object: dialog); |
119 | emit dialogChanged(); |
120 | } |
121 | |
122 | QUrl QQuickFileDialogDelegate::file() const |
123 | { |
124 | Q_D(const QQuickFileDialogDelegate); |
125 | return d->file; |
126 | } |
127 | |
128 | void QQuickFileDialogDelegate::setFile(const QUrl &file) |
129 | { |
130 | Q_D(QQuickFileDialogDelegate); |
131 | QUrl adjustedFile = file; |
132 | #ifdef Q_OS_WIN32 |
133 | // Work around QTBUG-99105 (FolderListModel uses lowercase drive letter). |
134 | QString path = adjustedFile.path(); |
135 | const int driveColonIndex = path.indexOf(QLatin1Char(':')); |
136 | if (driveColonIndex == 2) { |
137 | path.replace(1, 1, path.at(1).toUpper()); |
138 | adjustedFile.setPath(path); |
139 | } |
140 | #endif |
141 | if (adjustedFile == d->file) |
142 | return; |
143 | |
144 | d->file = adjustedFile; |
145 | emit fileChanged(); |
146 | } |
147 | |
148 | void QQuickFileDialogDelegate::keyReleaseEvent(QKeyEvent *event) |
149 | { |
150 | Q_D(QQuickFileDialogDelegate); |
151 | // We need to respond to being triggered by enter being pressed, |
152 | // but we can't use event->isAccepted() to check, because events are pre-accepted. |
153 | auto connection = QObjectPrivate::connect(sender: this, signal: &QQuickFileDialogDelegate::clicked, |
154 | receiverPrivate: d, slot: &QQuickFileDialogDelegatePrivate::chooseFile); |
155 | |
156 | QQuickItemDelegate::keyReleaseEvent(event); |
157 | |
158 | disconnect(connection); |
159 | } |
160 | |
161 | QT_END_NAMESPACE |
162 | |
163 | #include "moc_qquickfiledialogdelegate_p.cpp" |
164 | |