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
qquickfiledialogdelegate.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/qfileinfo.h>
9#include <QtCore/qmimedata.h>
10#include <QtGui/qpa/qplatformtheme.h>
11#include <QtQml/QQmlFile>
12#include <QtQml/qqmlexpression.h>
13#if QT_CONFIG(accessibility)
14#include <QtQuick/private/qquickaccessibleattached_p.h>
15#endif
16#include <QtQuick/private/qquicklistview_p.h>
17#include <QtQuick/private/qquickitemview_p_p.h>
20
22
23using namespace Qt::Literals::StringLiterals;
24
26{
27 Q_Q(QQuickFileDialogDelegate);
28 QQuickListViewAttached *attached = static_cast<QQuickListViewAttached*>(
29 qmlAttachedPropertiesObject<QQuickListView>(q));
30 if (!attached)
31 return;
32
33 QQmlContext *delegateContext = qmlContext(q);
34 if (!delegateContext)
35 return;
36
37 bool converted = false;
38 const int index = q->property("index").toInt(&converted);
39 if (converted) {
40 attached->view()->setCurrentIndex(index);
41 if (fileDialog)
42 fileDialog->setSelectedFile(file);
43 else if (folderDialog)
44 folderDialog->setSelectedFolder(file);
45 }
46}
47
49{
50 const QFileInfo fileInfo(QQmlFile::urlToLocalFileOrQrc(file));
51 if (fileInfo.isDir()) {
52 // If it's a directory, navigate to it.
53 if (fileDialog)
54 fileDialog->setCurrentFolder(file);
55 else
56 folderDialog->setCurrentFolder(file);
57 } else {
58 Q_ASSERT(fileDialog);
59 // Otherwise it's a file, so select it and close the dialog.
60 fileDialog->setSelectedFile(file);
61
62 // Prioritize closing the dialog with QQuickDialogPrivate::handleClick() over QQuickDialog::accept()
63 const QQuickFileDialogImplAttached *attached = QQuickFileDialogImplPrivate::get(fileDialog)->attachedOrWarn();
64 if (Q_LIKELY(attached)) {
65 auto *openButton = attached->buttonBox()->standardButton(QPlatformDialogHelper::Open);
66 if (Q_LIKELY(openButton)) {
67 emit openButton->clicked();
68 return;
69 }
70 }
71 fileDialog->accept();
72 }
73}
74
76{
77 return key == Qt::Key_Return || key == Qt::Key_Enter;
78}
79
80QQuickFileDialogDelegate::QQuickFileDialogDelegate(QQuickItem *parent)
81 : QQuickItemDelegate(*(new QQuickFileDialogDelegatePrivate), parent)
82{
83 Q_D(QQuickFileDialogDelegate);
84 // Clicking and tabbing should result in it getting focus,
85 // as e.g. Ubuntu and Windows both allow tabbing through file dialogs.
86 setFocusPolicy(Qt::StrongFocus);
87 setCheckable(true);
88 QObjectPrivate::connect(this, &QQuickFileDialogDelegate::clicked,
89 d, &QQuickFileDialogDelegatePrivate::highlightFile);
90 QObjectPrivate::connect(this, &QQuickFileDialogDelegate::doubleClicked,
91 d, &QQuickFileDialogDelegatePrivate::chooseFile);
92}
93
94QQuickDialog *QQuickFileDialogDelegate::dialog() const
95{
96 Q_D(const QQuickFileDialogDelegate);
97 return d->dialog;
98}
99
100void QQuickFileDialogDelegate::setDialog(QQuickDialog *dialog)
101{
102 Q_D(QQuickFileDialogDelegate);
103 if (dialog == d->dialog)
104 return;
105
106 d->dialog = dialog;
107 d->fileDialog = qobject_cast<QQuickFileDialogImpl*>(dialog);
108 d->folderDialog = qobject_cast<QQuickFolderDialogImpl*>(dialog);
109 emit dialogChanged();
110
111 if (d->tapHandler)
112 d->destroyTapHandler();
113 if (d->fileDialog)
114 d->initTapHandler();
115
116
117}
118
119QUrl QQuickFileDialogDelegate::file() const
120{
121 Q_D(const QQuickFileDialogDelegate);
122 return d->file;
123}
124
125void QQuickFileDialogDelegate::setFile(const QUrl &file)
126{
127 Q_D(QQuickFileDialogDelegate);
128 QUrl adjustedFile = file;
129#ifdef Q_OS_WIN32
130 // Work around QTBUG-99105 (FolderListModel uses lowercase drive letter).
131 QString path = adjustedFile.path();
132 const int driveColonIndex = path.indexOf(QLatin1Char(':'));
133 if (driveColonIndex == 2) {
134 path.replace(1, 1, path.at(1).toUpper());
135 adjustedFile.setPath(path);
136 }
137#endif
138 if (adjustedFile == d->file)
139 return;
140
141 d->file = adjustedFile;
142 emit fileChanged();
143#if QT_CONFIG(accessibility)
144 if (QQuickAccessibleAttached *accessibleAttached = QQuickControlPrivate::accessibleAttached(this))
145 accessibleAttached->setName(file.fileName());
146#endif
147}
148
149void QQuickFileDialogDelegate::keyReleaseEvent(QKeyEvent *event)
150{
151 Q_D(QQuickFileDialogDelegate);
152 // We need to respond to being triggered by enter being pressed,
153 // but we can't use event->isAccepted() to check, because events are pre-accepted.
154 auto connection = QObjectPrivate::connect(this, &QQuickFileDialogDelegate::clicked,
155 d, &QQuickFileDialogDelegatePrivate::chooseFile);
156
157 QQuickItemDelegate::keyReleaseEvent(event);
158
159 disconnect(connection);
160}
161
163{
164 Q_Q(QQuickFileDialogDelegate);
165 if (!tapHandler) {
166 tapHandler = new QQuickFileDialogTapHandler(q);
167
168 connect(tapHandler, &QQuickTapHandler::longPressed, this,
170 }
171}
172
178
180{
181 if (!tapHandler)
182 return;
183
185 tapHandler->m_longPressed = true;
186}
187
188// ----------------------------------------------
189
190QQuickFileDialogTapHandler::QQuickFileDialogTapHandler(QQuickItem *parent)
191 : QQuickTapHandler(parent)
192{
193 // Set a grab permission that stops the flickable from stealing the drag.
194 setGrabPermissions(QQuickPointerHandler::CanTakeOverFromAnything);
195
196 // The drag threshold is used by the handler to know when to start a drag.
197 // We handle the drag inpendently, so set the threshold to a big number.
198 // This will guarantee that QQuickTapHandler::wantsEventPoint always returns
199 // true.
200 setDragThreshold(1000);
201}
202
204{
205 auto *delegate = qobject_cast<QQuickFileDialogDelegate*>(parent());
206 auto *delegatePrivate = QQuickFileDialogDelegatePrivate::get(delegate);
207 return delegatePrivate->fileDialog;
208}
209
211{
212 if (m_drag.isNull())
213 return;
214
215 QQuickPalette *palette = [this]() -> QQuickPalette* {
216 auto *delegate = qobject_cast<QQuickFileDialogDelegate*>(parent());
217 if (delegate) {
218 QQuickDialog *dlg = delegate->dialog();
219 if (dlg) {
220 QQuickDialogPrivate *priv = QQuickDialogPrivate::get(dlg);
221 if (priv)
222 return priv->palette();
223 }
224 }
225 return nullptr;
226 }();
227
228 // TODO: use proper @Nx scaling
229 const auto src = ":/qt-project.org/imports/QtQuick/Dialogs/quickimpl/images/sidebar-folder.png"_L1;
230 QImage img = QImage(src).convertToFormat(QImage::Format_ARGB32);
231
232 if (!img.isNull() && palette) {
233 const QColor iconColor = palette->buttonText();
234 if (iconColor.alpha() > 0) {
235 // similar to what QQuickIconImage does
236 QPainter painter(&img);
237 painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
238 painter.fillRect(img.rect(), iconColor);
239 }
240 }
241
242 QPixmap pixmap = QPixmap::fromImage(std::move(img));
243 auto *mimeData = new QMimeData();
244 mimeData->setImageData(pixmap);
245 m_drag->setMimeData(mimeData);
246 m_drag->setPixmap(pixmap);
247}
248
250{
251 auto *delegate = qobject_cast<QQuickFileDialogDelegate *>(parent());
252 return QUrl{delegate->file().toLocalFile()};
253}
254
255void QQuickFileDialogTapHandler::handleDrag(QQuickDragEvent *event)
256{
257 if (m_state == Dragging) {
258 auto *fileDialogImpl = getFileDialogImpl();
259 auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn();
260 if (Q_LIKELY(attached)) {
261 auto *sideBar = attached->sideBar();
262 if (Q_LIKELY(sideBar)) {
263 auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar);
264 const auto pos = QPoint(event->x(), event->y());
265 sideBarPrivate->setShowAddFavoriteDelegate(sideBar->contains(pos));
266 if (sideBarPrivate->showAddFavoriteDelegate()) {
267 const QList<QQuickItem *> items = sideBar->contentChildren().toList<QList<QQuickItem *>>();
268 // iterate through the children until we have counted all the folder paths. The next button will
269 // be the addFavoriteDelegate
270 const int addFavoritePos = sideBar->effectiveFolderPaths().size();
271 int currentPos = 0;
272 for (int i = 0; i < items.length(); i++) {
273 if (auto *button = qobject_cast<QQuickAbstractButton *>(items.at(i))) {
274 if (currentPos == addFavoritePos) {
275 // check if the pointer position is within the add favorite button
276 const QRectF bBox = button->mapRectToItem(sideBar, button->boundingRect());
277 sideBarPrivate->setAddFavoriteDelegateHovered(bBox.contains(pos));
278 break;
279 } else {
280 currentPos++;
281 }
282 }
283 }
284 }
285 }
286 }
287 }
288}
289
290void QQuickFileDialogTapHandler::handleDrop(QQuickDragEvent *event)
291{
292 Q_UNUSED(event);
293 if (m_state == Dragging) {
294 if (!m_sourceUrl.isEmpty()) {
295 auto *fileDialogImpl = getFileDialogImpl();
296 auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn();
297 if (Q_LIKELY(attached)) {
298 auto *sideBar = attached->sideBar();
299 if (Q_LIKELY(sideBar)) {
300 auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar);
301 // this cannot be handled in handleDrag because handleDrag is connected to the drop
302 // area, and so won't run when the cursor is outside of it
303 if (sideBarPrivate->addFavoriteDelegateHovered())
304 sideBarPrivate->addFavorite(m_sourceUrl);
305 sideBarPrivate->setShowAddFavoriteDelegate(false);
306 }
307 }
308 }
309 m_state = DraggingFinished;
310 }
311}
312
314{
315 if (m_state == Dragging) {
316 auto *fileDialogImpl = getFileDialogImpl();
317 auto *attached = QQuickFileDialogImplPrivate::get(fileDialogImpl)->attachedOrWarn();
318 if (Q_LIKELY(attached)) {
319 auto *sideBar = attached->sideBar();
320 if (Q_LIKELY(sideBar && m_dropArea)) {
321 auto *sideBarPrivate = QQuickSideBarPrivate::get(sideBar);
322 if (m_dropArea->containsDrag()) {
323 sideBarPrivate->setShowAddFavoriteDelegate(true);
324 } else {
325 sideBarPrivate->setShowAddFavoriteDelegate(false);
326 sideBarPrivate->setAddFavoriteDelegateHovered(false);
327 }
328 }
329 }
330 }
331}
332
333void QQuickFileDialogTapHandler::resetDragData()
334{
335 if (m_state != Listening) {
336 m_state = Listening;
337 m_sourceUrl = QUrl();
338 if (!m_drag.isNull()) {
339 m_drag->disconnect();
340 delete m_drag;
341 }
342 if (!m_dropArea.isNull()) {
343 m_dropArea->disconnect();
344 delete m_dropArea;
345 }
346
347 m_longPressed = false;
348 }
349}
350
351void QQuickFileDialogTapHandler::handleEventPoint(QPointerEvent *event, QEventPoint &point)
352{
353 QQuickTapHandler::handleEventPoint(event, point);
354
355 auto *fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate *>(parent());
356 auto *fileDialogImpl = getFileDialogImpl();
357 if (Q_UNLIKELY(!fileDialogImpl))
358 return;
359 auto *fileDialogImplPrivate = QQuickFileDialogImplPrivate::get(fileDialogImpl);
360 QQuickFileDialogImplAttached *attached = fileDialogImplPrivate->attachedOrWarn();
361 if (Q_UNLIKELY(!attached || !attached->sideBar()))
362 return;
363
364 if (m_state == DraggingFinished)
365 resetDragData();
366
367 if (point.state() == QEventPoint::Pressed) {
368 resetDragData();
369 // Activate the passive grab to get further move updates
370 setPassiveGrab(event, point, true);
371 } else if (point.state() == QEventPoint::Released) {
372 resetDragData();
373 } else if (point.state() == QEventPoint::Updated && m_longPressed) {
374 // Check to see that the movement can be considered as dragging
375 const qreal distX = point.position().x() - point.pressPosition().x();
376 const qreal distY = point.position().y() - point.pressPosition().y();
377 // consider the squared distance to be optimal
378 const qreal dragDistSq = distX * distX + distY * distY;
379 if (dragDistSq > qPow(qApp->styleHints()->startDragDistance(), 2)) {
380 switch (m_state) {
381 case Tracking: {
382 // set the drag
383 if (m_drag.isNull())
384 m_drag = new QDrag(fileDialogDelegate);
385 // Set the drop area
386 if (m_dropArea.isNull()) {
387 auto *sideBar = attached->sideBar();
388 m_dropArea = new QQuickDropArea(sideBar);
389 m_dropArea->setSize(sideBar->size());
390 connect(m_dropArea, &QQuickDropArea::positionChanged, this,
391 &QQuickFileDialogTapHandler::handleDrag);
392 connect(m_dropArea, &QQuickDropArea::dropped, this,
393 &QQuickFileDialogTapHandler::handleDrop);
394 connect(m_dropArea, &QQuickDropArea::containsDragChanged, this,
396 }
397
398 m_sourceUrl = fileDialogDelegate->file();
399 // set up the grab
401 m_state = DraggingStarted;
402 } break;
403
404 case DraggingStarted: {
405 if (m_drag && m_drag->mimeData()) {
406 if (auto *item = qobject_cast<QQuickItem *>(m_drag->source())) {
407 Q_UNUSED(item);
408 m_state = Dragging;
409 // start the drag
410 m_drag->exec();
411 // If the state still remains dragging, then the drop happened outside
412 // the drop area
413 if (m_state == Dragging)
414 resetDragData();
415 }
416 }
417 } break;
418
419 default:
420 break;
421 }
422 }
423 }
424}
425
426bool QQuickFileDialogTapHandler::wantsEventPoint(const QPointerEvent *event, const QEventPoint &point)
427{
428 if (!QQuickTapHandler::wantsEventPoint(event, point) || event->type() == QEvent::Type::Wheel)
429 return false;
430
431 // we only want the event point if the delegate contains a directory and not a file
432 auto *fileDialogDelegate = qobject_cast<QQuickFileDialogDelegate *>(parent());
433 QFileInfo info(fileDialogDelegate->file().toLocalFile());
434 if (info.isDir())
435 return true;
436
437 return false;
438}
439
440QT_END_NAMESPACE
441
442#include "moc_qquickfiledialogdelegate_p.cpp"
bool acceptKeyClick(Qt::Key key) const override
void handleEventPoint(QPointerEvent *event, QEventPoint &point) override
void handleDrag(QQuickDragEvent *event)
QQuickFileDialogImpl * getFileDialogImpl() const
bool wantsEventPoint(const QPointerEvent *event, const QEventPoint &point) override
Returns true if the given point (as part of event) could be relevant at all to this handler,...
void handleDrop(QQuickDragEvent *event)
Combined button and popup list for selecting options.