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