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