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) {
53 if (directory.startsWith("assets-library:"_L1) || scheme == "assets-library"_L1)
54 return showImagePickerDialog(parent);
55 else
56 return showNativeDocumentPickerDialog(parent);
57 }
58
59 return false;
60}
61
62void QIOSFileDialog::showImagePickerDialog_helper(QWindow *parent)
63{
64 UIWindow *window = presentationWindow(parent);
65 [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
66}
67
68bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
69{
70 if (!m_viewController) {
71 QFactoryLoader *plugins = QIOSIntegration::instance()->optionalPlugins();
72 qsizetype size = QList<QPluginParsedMetaData>(plugins->metaData()).size();
73 for (qsizetype i = 0; i < size; ++i) {
74 QIosOptionalPluginInterface *plugin = qobject_cast<QIosOptionalPluginInterface *>(plugins->instance(i));
75 m_viewController = [plugin->createImagePickerController(this) retain];
76 if (m_viewController)
77 break;
78 }
79 }
80
81 if (!m_viewController) {
82 qWarning() << "QIOSFileDialog: Could not resolve Qt plugin that gives access to photos on iOS";
83 return false;
84 }
85
86 // "Old style" authorization (deprecated, but we have to work with AssetsLibrary anyway).
87 //
88 // From the documentation:
89 // "The authorizationStatus and requestAuthorization: methods aren’t compatible with the
90 // limited library and return PHAuthorizationStatusAuthorized when the user authorizes your
91 // app for limited access only."
92 //
93 // This is good enough for us.
94
95 const auto authStatus = [PHPhotoLibrary authorizationStatus];
96 if (authStatus == PHAuthorizationStatusAuthorized) {
97 showImagePickerDialog_helper(parent);
98 } else if (authStatus == PHAuthorizationStatusNotDetermined) {
99 QPointer<QWindow> winGuard(parent);
100 QPointer<QIOSFileDialog> thisGuard(this);
101 [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
102 dispatch_async(dispatch_get_main_queue(), ^{
103 if (status == PHAuthorizationStatusAuthorized) {
104 if (thisGuard && (winGuard || !parent))
105 thisGuard->showImagePickerDialog_helper(winGuard);
106
107 } else if (thisGuard) {
108 emit thisGuard->reject();
109 }
110 });
111 }];
112 } else {
113 // Treat 'Limited' (we don't know how to deal with anyway) and 'Denied' as errors.
114 // FIXME: logging category?
115 qWarning() << "QIOSFileDialog: insufficient permission, cannot pick images";
116 return false;
117 }
118
119 return true;
120}
121
122bool QIOSFileDialog::showNativeDocumentPickerDialog(QWindow *parent)
123{
124#ifndef Q_OS_TVOS
125 m_viewController = [[QIOSDocumentPickerController alloc] initWithQIOSFileDialog:this];
126
127 UIWindow *window = presentationWindow(parent);
128 [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
129
130 return true;
131#else
132 return false;
133#endif
134}
135
136void QIOSFileDialog::hide()
137{
138 // QFileDialog will remember the last directory set, and open subsequent dialogs in the same
139 // directory for convenience. This works for normal file dialogs, but not when using native
140 // pickers. Those can only be used for picking specific types, without support for normal file
141 // system navigation. To avoid showing a native picker by accident, we change directory back
142 // before we return. More could have been done to preserve the "last directory" logic here, but
143 // navigating the file system on iOS is not recommended in the first place, so we keep it simple.
144 emit directoryEntered(QUrl::fromLocalFile(QDir::currentPath()));
145
146 [m_viewController dismissViewControllerAnimated:YES completion:nil];
147 [m_viewController release];
148 m_viewController = nullptr;
149 m_eventLoop.exit();
150}
151
152QList<QUrl> QIOSFileDialog::selectedFiles() const
153{
154 return m_selection;
155}
156
157void QIOSFileDialog::selectedFilesChanged(const QList<QUrl> &selection)
158{
159 m_selection = selection;
160 emit filesSelected(m_selection);
161 if (m_selection.count() == 1)
162 emit fileSelected(m_selection[0]);
163}
static QIOSIntegration * instance()