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
qiosfiledialog.mm
Go to the documentation of this file.
1// Copyright (C) 2016 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
5#import <UIKit/UIKit.h>
6
7#import <Photos/Photos.h>
8
9#include <QtCore/qstandardpaths.h>
10#include <QtGui/qwindow.h>
11#include <QDebug>
12
13#include <QtCore/private/qcore_mac_p.h>
14
15#include "qiosglobal.h"
16#include "qiosfiledialog.h"
20
21#include <QtCore/qpointer.h>
22
23using namespace Qt::StringLiterals;
24
25QIOSFileDialog::QIOSFileDialog()
26 : m_viewController(nullptr)
27{
28}
29
30QIOSFileDialog::~QIOSFileDialog()
31{
32 [m_viewController release];
33}
34
35void QIOSFileDialog::exec()
36{
37 m_eventLoop.exec(QEventLoop::DialogExec);
38}
39
40bool QIOSFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
41{
42 Q_UNUSED(windowFlags);
43 Q_UNUSED(windowModality);
44
45 const bool acceptOpen = options()->acceptMode() == QFileDialogOptions::AcceptOpen;
46 const auto initialDir = options()->initialDirectory();
47 const QString directory = initialDir.toLocalFile();
48 // We manually add assets-library:// to the list of paths,
49 // when converted to QUrl, it becames a scheme.
50 const QString scheme = initialDir.scheme();
51
52 if (acceptOpen && (directory.startsWith("assets-library:"_L1) || scheme == "assets-library"_L1))
53 return showImagePickerDialog(parent);
54
55 return showNativeDocumentPickerDialog(parent);
56}
57
58void QIOSFileDialog::showImagePickerDialog_helper(QWindow *parent)
59{
60 UIWindow *window = presentationWindow(parent);
61 [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
62}
63
64bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
65{
66 if (!m_viewController) {
67 QFactoryLoader *plugins = QIOSIntegration::instance()->optionalPlugins();
68 qsizetype size = QList<QPluginParsedMetaData>(plugins->metaData()).size();
69 for (qsizetype i = 0; i < size; ++i) {
70 QIosOptionalPluginInterface *plugin = qobject_cast<QIosOptionalPluginInterface *>(plugins->instance(i));
71 m_viewController = [plugin->createImagePickerController(this) retain];
72 if (m_viewController)
73 break;
74 }
75 }
76
77 if (!m_viewController) {
78 qWarning() << "QIOSFileDialog: Could not resolve Qt plugin that gives access to photos on iOS";
79 return false;
80 }
81
82 // "Old style" authorization (deprecated, but we have to work with AssetsLibrary anyway).
83 //
84 // From the documentation:
85 // "The authorizationStatus and requestAuthorization: methods aren’t compatible with the
86 // limited library and return PHAuthorizationStatusAuthorized when the user authorizes your
87 // app for limited access only."
88 //
89 // This is good enough for us.
90
91 const auto authStatus = [PHPhotoLibrary authorizationStatus];
92 if (authStatus == PHAuthorizationStatusAuthorized) {
93 showImagePickerDialog_helper(parent);
94 } else if (authStatus == PHAuthorizationStatusNotDetermined) {
95 QPointer<QWindow> winGuard(parent);
96 QPointer<QIOSFileDialog> thisGuard(this);
97 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
98 dispatch_async(dispatch_get_main_queue(), ^{
99 if (status == PHAuthorizationStatusAuthorized) {
100 if (thisGuard && (winGuard || !parent))
101 thisGuard->showImagePickerDialog_helper(winGuard);
102
103 } else if (thisGuard) {
104 emit thisGuard->reject();
105 }
106 });
107 }];
108 } else {
109 // Treat 'Limited' (we don't know how to deal with anyway) and 'Denied' as errors.
110 // FIXME: logging category?
111 qWarning() << "QIOSFileDialog: insufficient permission, cannot pick images";
112 return false;
113 }
114
115 return true;
116}
117
118bool QIOSFileDialog::showNativeDocumentPickerDialog(QWindow *parent)
119{
120#ifndef Q_OS_TVOS
121 m_viewController = [[QIOSDocumentPickerController alloc] initWithQIOSFileDialog:this];
122
123 UIWindow *window = presentationWindow(parent);
124 [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
125
126 return true;
127#else
128 return false;
129#endif
130}
131
132void QIOSFileDialog::hide()
133{
134 // QFileDialog will remember the last directory set, and open subsequent dialogs in the same
135 // directory for convenience. This works for normal file dialogs, but not when using native
136 // pickers. Those can only be used for picking specific types, without support for normal file
137 // system navigation. To avoid showing a native picker by accident, we change directory back
138 // before we return. More could have been done to preserve the "last directory" logic here, but
139 // navigating the file system on iOS is not recommended in the first place, so we keep it simple.
140 emit directoryEntered(QUrl::fromLocalFile(QDir::currentPath()));
141
142 [m_viewController dismissViewControllerAnimated:YES completion:nil];
143 [m_viewController release];
144 m_viewController = nullptr;
145 m_eventLoop.exit();
146}
147
148QList<QUrl> QIOSFileDialog::selectedFiles() const
149{
150 return m_selection;
151}
152
153void QIOSFileDialog::selectedFilesChanged(const QList<QUrl> &selection)
154{
155 m_selection = selection;
156 emit filesSelected(m_selection);
157 if (m_selection.count() == 1)
158 emit fileSelected(m_selection[0]);
159}
static QIOSIntegration * instance()