Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoafiledialoghelper.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
4#include <QtCore/qglobal.h>
5
6#include <AppKit/AppKit.h>
7
9#include "qcocoahelpers.h"
11
12#include <QtCore/qbuffer.h>
13#include <QtCore/qdebug.h>
14#include <QtCore/qstringlist.h>
15#include <QtCore/qvarlengtharray.h>
16#include <QtCore/qabstracteventdispatcher.h>
17#include <QtCore/qsysinfo.h>
18#include <QtCore/qoperatingsystemversion.h>
19#include <QtCore/qdir.h>
20#include <QtCore/qregularexpression.h>
21#include <QtCore/qpointer.h>
22#include <QtCore/private/qcore_mac_p.h>
23
24#include <QtGui/qguiapplication.h>
25#include <QtGui/private/qguiapplication_p.h>
26
27#include <qpa/qplatformtheme.h>
28#include <qpa/qplatformnativeinterface.h>
29
30#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
31
33
34using namespace Qt::StringLiterals;
35
36static NSString *strippedText(QString s)
37{
38 s.remove("..."_L1);
39 return QPlatformTheme::removeMnemonics(s).trimmed().toNSString();
40}
41
42// NSOpenPanel extends NSSavePanel with some extra APIs
43static NSOpenPanel *openpanel_cast(NSSavePanel *panel)
44{
45 if ([panel isKindOfClass:NSOpenPanel.class])
46 return static_cast<NSOpenPanel*>(panel);
47 else
48 return nil;
49}
50
51typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
52
53@implementation QNSOpenSavePanelDelegate {
54 @public
55 NSSavePanel *m_panel;
57 NSPopUpButton *m_popupButton;
58 NSTextField *m_textField;
59 QPointer<QCocoaFileDialogHelper> m_helper;
60
65}
66
67- (instancetype)initWithAcceptMode:(const QString &)selectFile
68 options:(SharedPointerFileDialogOptions)options
69 helper:(QCocoaFileDialogHelper *)helper
70{
71 if ((self = [super init])) {
72 m_options = options;
73
75 m_panel = [[NSOpenPanel openPanel] retain];
76 else
77 m_panel = [[NSSavePanel savePanel] retain];
78
79 m_panel.canSelectHiddenExtension = YES;
80 m_panel.level = NSModalPanelWindowLevel;
81
82 m_helper = helper;
83
85 QString selectedVisualNameFilter = m_options->initiallySelectedNameFilter();
86 m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter];
87
88 m_panel.extensionHidden = [&]{
89 for (const auto &nameFilter : m_nameFilterDropDownList) {
90 const auto extensions = QPlatformFileDialogHelper::cleanFilterList(nameFilter);
91 for (const auto &extension : extensions) {
92 // Explicitly show extensions if we detect a filter
93 // of "all files", as clicking a single file with
94 // extensions hidden will then populate the name
95 // field with only the file name, without any
96 // extension.
97 if (extension == "*"_L1 || extension == "*.*"_L1)
98 return false;
99
100 // Explicitly show extensions if we detect a filter
101 // that has a multi-part extension. This prevents
102 // confusing situations where the user clicks e.g.
103 // 'foo.tar.gz' and 'foo.tar' is populated in the
104 // file name box, but when then clicking save macOS
105 // will warn that the file needs to end in .gz,
106 // due to thinking the user tried to save the file
107 // as a 'tar' file instead. Unfortunately this
108 // property can only be set before the panel is
109 // shown, so we can't toggle it on and off based
110 // on the active filter.
111 if (extension.count('.') > 1)
112 return false;
113 }
114 }
115 return true;
116 }();
117
118 const QFileInfo sel(selectFile);
119 if (sel.isDir() && !sel.isBundle()){
120 m_panel.directoryURL = [NSURL fileURLWithPath:sel.absoluteFilePath().toNSString()];
122 } else {
123 m_panel.directoryURL = [NSURL fileURLWithPath:sel.absolutePath().toNSString()];
124 m_currentSelection = sel.absoluteFilePath();
125 }
126
127 [self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)];
128 [self createTextField];
129 [self createAccessory];
130
131 m_panel.accessoryView = m_nameFilterDropDownList.size() > 1 ? m_accessoryView : nil;
132 // -setAccessoryView: can result in -panel:directoryDidChange:
133 // resetting our current directory. Set the delegate
134 // here to make sure it gets the correct value.
135 m_panel.delegate = self;
136
137 if (auto *openPanel = openpanel_cast(m_panel))
138 openPanel.accessoryViewDisclosed = YES;
139
140 [self updateProperties];
141 }
142 return self;
143}
144
145- (void)dealloc
146{
147 [m_panel orderOut:m_panel];
148 m_panel.accessoryView = nil;
149 [m_popupButton release];
150 [m_textField release];
151 [m_accessoryView release];
152 m_panel.delegate = nil;
153 [m_panel release];
154 [super dealloc];
155}
156
157- (bool)showPanel:(Qt::WindowModality) windowModality withParent:(QWindow *)parent
158{
160 NSString *filepath = info.filePath().toNSString();
161 NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()];
162 bool selectable = (m_options->acceptMode() == QFileDialogOptions::AcceptSave)
163 || [self panel:m_panel shouldEnableURL:url];
164
165 m_panel.nameFieldStringValue = selectable ? info.fileName().toNSString() : @"";
166
167 [self updateProperties];
168
169 auto completionHandler = ^(NSInteger result) { m_helper->panelClosed(result); };
170
171 if (windowModality == Qt::WindowModal && parent) {
172 NSView *view = reinterpret_cast<NSView*>(parent->winId());
173 [m_panel beginSheetModalForWindow:view.window completionHandler:completionHandler];
174 } else if (windowModality == Qt::ApplicationModal) {
175 return true; // Defer until exec()
176 } else {
177 [m_panel beginWithCompletionHandler:completionHandler];
178 }
179
180 return true;
181}
182
183-(void)runApplicationModalPanel
184{
185 // Note: If NSApp is not running (which is the case if e.g a top-most
186 // QEventLoop has been interrupted, and the second-most event loop has not
187 // yet been reactivated (regardless if [NSApp run] is still on the stack)),
188 // showing a native modal dialog will fail.
189 if (!m_helper)
190 return;
191
193
194 // Call processEvents in case the event dispatcher has been interrupted, and needs to do
195 // cleanup of modal sessions. Do this before showing the native dialog, otherwise it will
196 // close down during the cleanup.
198
199 // Make sure we don't interrupt the runModal call below.
201
202 auto result = [m_panel runModal];
203 m_helper->panelClosed(result);
204
205 // Wake up the event dispatcher so it can check whether the
206 // current event loop should continue spinning or not.
208}
209
210- (void)closePanel
211{
212 m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
213
214 if (m_panel.sheet)
215 [NSApp endSheet:m_panel];
216 else if (NSApp.modalWindow == m_panel)
217 [NSApp stopModal];
218 else
219 [m_panel close];
220}
221
222- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url
223{
224 Q_UNUSED(sender);
225
226 NSString *filename = url.path;
227 if (!filename.length)
228 return NO;
229
230 const QFileInfo fileInfo(QString::fromNSString(filename));
231
232 // Always accept directories regardless of their names.
233 // This also includes symlinks and aliases to directories.
234 if (fileInfo.isDir()) {
235 // Unless it's a bundle, and we should treat bundles as files.
236 // FIXME: We'd like to use QFileInfo::isBundle() here, but the
237 // detection in QFileInfo goes deeper than NSWorkspace does
238 // (likely a bug), and as a result causes TCC permission
239 // dialogs to pop up when used.
240 bool treatBundlesAsFiles = !m_panel.treatsFilePackagesAsDirectories;
241 if (!(treatBundlesAsFiles && [NSWorkspace.sharedWorkspace isFilePackageAtPath:filename]))
242 return YES;
243 }
244
245 if (![self fileInfoMatchesCurrentNameFilter:fileInfo])
246 return NO;
247
248 QDir::Filters filter = m_options->filter();
249 if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && fileInfo.isDir())
250 || (!(filter & QDir::Files) && (fileInfo.isFile() && !fileInfo.isSymLink()))
251 || ((filter & QDir::NoSymLinks) && fileInfo.isSymLink()))
252 return NO;
253
254 bool filterPermissions = ((filter & QDir::PermissionMask)
256 if (filterPermissions) {
257 if ((!(filter & QDir::Readable) && fileInfo.isReadable())
258 || (!(filter & QDir::Writable) && fileInfo.isWritable())
259 || (!(filter & QDir::Executable) && fileInfo.isExecutable()))
260 return NO;
261 }
262
263 // We control the visibility of hidden files via the showsHiddenFiles
264 // property on the panel, based on QDir::Hidden being set. But the user
265 // can also toggle this via the Command+Shift+. keyboard shortcut,
266 // in which case they have explicitly requested to show hidden files,
267 // and we should enable them even if QDir::Hidden was not set. In
268 // effect, we don't need to filter on QDir::Hidden here.
269
270 return YES;
271}
272
273- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError
274{
275 Q_ASSERT(sender == m_panel);
276
277 if (!m_panel.allowedFileTypes && !m_selectedNameFilter.isEmpty()) {
278 // The save panel hasn't done filtering on our behalf,
279 // either because we couldn't represent the filter via
280 // allowedFileTypes, or we opted out due to a multi part
281 // extension, so do the filtering/validation ourselves.
282 QFileInfo fileInfo(QString::fromNSString(url.path).normalized(QString::NormalizationForm_C));
283
284 if ([self fileInfoMatchesCurrentNameFilter:fileInfo])
285 return YES;
286
287 if (fileInfo.suffix().isEmpty()) {
288 // The filter requires a file name with an extension.
289 // We're going to add a default file name in selectedFiles,
290 // to match the native behavior. Check now that we can
291 // overwrite the file, if is already exists.
292 fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
293
295 return YES;
296
298 auto *alert = [[NSAlert new] autorelease];
299 alert.alertStyle = NSAlertStyleCritical;
300
301 alert.messageText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
302 @"\\U201c%@\\U201d already exists. Do you want to replace it?"),
303 fileInfo.fileName().toNSString()];
304 alert.informativeText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
305 @"A file or folder with the same name already exists in the folder %@. "
306 "Replacing it will overwrite its current contents."),
307 fileInfo.absoluteDir().dirName().toNSString()];
308
309 auto *replaceButton = [alert addButtonWithTitle:qt_mac_AppKitString(@"SavePanel", @"Replace")];
310 replaceButton.hasDestructiveAction = YES;
311 replaceButton.tag = 1337;
312 [alert addButtonWithTitle:qt_mac_AppKitString(@"Common", @"Cancel")];
313
314 [alert beginSheetModalForWindow:m_panel
315 completionHandler:^(NSModalResponse returnCode) {
316 [NSApp stopModalWithCode:returnCode];
317 }];
318 return [NSApp runModalForWindow:alert.window] == replaceButton.tag;
319 } else {
320 QFileInfo firstFilter(m_selectedNameFilter.first());
321 auto *domain = qGuiApp->organizationDomain().toNSString();
322 *outError = [NSError errorWithDomain:domain code:0 userInfo:@{
323 NSLocalizedDescriptionKey:[NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
324 @"You cannot save this document with extension \\U201c.%1$@\\U201d at the end "
325 "of the name. The required extension is \\U201c.%2$@\\U201d."),
326 fileInfo.completeSuffix().toNSString(), firstFilter.completeSuffix().toNSString()]
327 }];
328 return NO;
329 }
330 }
331
332 return YES;
333}
334
335- (QFileInfo)applyDefaultSuffixFromCurrentNameFilter:(const QFileInfo &)fileInfo
336{
337 QFileInfo filterInfo(m_selectedNameFilter.first());
338 return QFileInfo(fileInfo.absolutePath(),
339 fileInfo.baseName() + '.' + filterInfo.completeSuffix());
340}
341
342- (bool)fileInfoMatchesCurrentNameFilter:(const QFileInfo &)fileInfo
343{
344 // No filter means accept everything
345 if (m_selectedNameFilter.isEmpty())
346 return true;
347
348 // Check if the current file name filter accepts the file
349 for (const auto &filter : m_selectedNameFilter) {
350 if (QDir::match(filter, fileInfo.fileName()))
351 return true;
352 }
353
354 return false;
355}
356
357- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails
358{
359 [m_popupButton removeAllItems];
361 if (filters.size() > 0){
362 for (int i = 0; i < filters.size(); ++i) {
363 const QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
364 [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
365 }
366 [m_popupButton selectItemAtIndex:0];
367 m_panel.accessoryView = m_accessoryView;
368 } else {
369 m_panel.accessoryView = nil;
370 }
371
372 [self filterChanged:self];
373}
374
375- (void)filterChanged:(id)sender
376{
377 // This m_delegate function is called when the _name_ filter changes.
378 Q_UNUSED(sender);
379 if (!m_helper)
380 return;
381 const QString selection = m_nameFilterDropDownList.value([m_popupButton indexOfSelectedItem]);
382 m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
383 [m_panel validateVisibleColumns];
384 [self updateProperties];
385
387 const int menuIndex = m_popupButton.indexOfSelectedItem;
388 emit m_helper->filterSelected(menuIndex >= 0 && menuIndex < filters.size() ? filters.at(menuIndex) : QString());
389}
390
391- (QList<QUrl>)selectedFiles
392{
393 if (auto *openPanel = openpanel_cast(m_panel)) {
394 QList<QUrl> result;
395 for (NSURL *url in openPanel.URLs) {
398 }
399 return result;
400 } else {
401 QString filename = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
402 QFileInfo fileInfo(filename);
403
404 if (fileInfo.suffix().isEmpty() && ![self fileInfoMatchesCurrentNameFilter:fileInfo]) {
405 // We end up in this situation if we accept a file name without extension
406 // in panel:validateURL:error. If so, we match the behavior of the native
407 // save dialog and add the first of the accepted extension from the filter.
408 fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
409 }
410
411 // If neither the user or the NSSavePanel have provided a suffix, use
412 // the default suffix (if it exists).
413 const QString defaultSuffix = m_options->defaultSuffix();
414 if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) {
415 fileInfo.setFile(fileInfo.absolutePath(),
416 fileInfo.baseName() + '.' + defaultSuffix);
417 }
418
419 return { QUrl::fromLocalFile(fileInfo.filePath()) };
420 }
421}
422
423- (void)updateProperties
424{
426 bool chooseFilesOnly = fileMode == QFileDialogOptions::ExistingFile
428 bool chooseDirsOnly = fileMode == QFileDialogOptions::Directory
431
432 m_panel.title = m_options->windowTitle().toNSString();
433 m_panel.canCreateDirectories = !(m_options->testOption(QFileDialogOptions::ReadOnly));
434
439
440 if (auto *openPanel = openpanel_cast(m_panel)) {
441 openPanel.canChooseFiles = !chooseDirsOnly;
442 openPanel.canChooseDirectories = !chooseFilesOnly;
443 openPanel.allowsMultipleSelection = (fileMode == QFileDialogOptions::ExistingFiles);
444 openPanel.resolvesAliases = !(m_options->testOption(QFileDialogOptions::DontResolveSymlinks));
445 }
446
447 m_popupButton.hidden = chooseDirsOnly; // TODO hide the whole sunken pane instead?
448
449 m_panel.allowedFileTypes = [self computeAllowedFileTypes];
450
451 // Setting allowedFileTypes to nil is not enough to reset any
452 // automatically added extension based on a previous filter.
453 // This is problematic because extensions can in some cases
454 // be hidden from the user, resulting in confusion when the
455 // resulting file name doesn't match the current empty filter.
456 // We work around this by temporarily resetting the allowed
457 // content type to one without an extension, which forces
458 // the save panel to update and remove the extension.
459 const bool nameFieldHasExtension = m_panel.nameFieldStringValue.pathExtension.length > 0;
460 if (!m_panel.allowedFileTypes && !nameFieldHasExtension && !openpanel_cast(m_panel)) {
461 if (!UTTypeDirectory.preferredFilenameExtension) {
462 m_panel.allowedContentTypes = @[ UTTypeDirectory ];
463 m_panel.allowedFileTypes = nil;
464 } else {
465 qWarning() << "UTTypeDirectory unexpectedly reported an extension";
466 }
467 }
468
469 m_panel.showsHiddenFiles = m_options->filter().testFlag(QDir::Hidden);
470
471 if (m_panel.visible)
472 [m_panel validateVisibleColumns];
473}
474
475- (void)panelSelectionDidChange:(id)sender
476{
477 Q_UNUSED(sender);
478
479 if (!m_helper)
480 return;
481
482 // Save panels only allow you to select directories, which
483 // means currentChanged will only be emitted when selecting
484 // a directory, and if so, with the latest chosen file name,
485 // which is confusing and inconsistent. We choose to bail
486 // out entirely for save panels, to give consistent behavior.
487 if (!openpanel_cast(m_panel))
488 return;
489
490 if (m_panel.visible) {
491 const QString selection = QString::fromNSString(m_panel.URL.path);
494 emit m_helper->currentChanged(QUrl::fromLocalFile(selection));
495 }
496 }
497}
498
499- (void)panel:(id)sender directoryDidChange:(NSString *)path
500{
501 Q_UNUSED(sender);
502
503 if (!m_helper)
504 return;
505
506 m_helper->panelDirectoryDidChange(path);
507}
508
509/*
510 Computes a list of extensions (e.g. "png", "jpg", "gif")
511 for the current name filter, and updates the save panel.
512
513 If a filter do not conform to the format *.xyz or * or *.*,
514 or contains an extensions with more than one part (e.g. "tar.gz")
515 we treat that as allowing all file types, and do our own
516 validation in panel:validateURL:error.
517*/
518- (NSArray<NSString*>*)computeAllowedFileTypes
519{
521 return nil; // panel:shouldEnableURL: does the file filtering for NSOpenPanel
522
523 QStringList fileTypes;
524 for (const QString &filter : std::as_const(m_selectedNameFilter)) {
525 if (!filter.startsWith("*."_L1))
526 continue;
527
528 if (filter.contains(u'?'))
529 continue;
530
531 if (filter.count(u'*') != 1)
532 continue;
533
534 auto extensions = filter.split('.', Qt::SkipEmptyParts);
535 if (extensions.count() > 2)
536 return nil;
537
538 fileTypes += extensions.last();
539 }
540
541 return fileTypes.isEmpty() ? nil : qt_mac_QStringListToNSMutableArray(fileTypes);
542}
543
544- (QString)removeExtensions:(const QString &)filter
545{
547 QRegularExpressionMatch match = regExp.match(filter);
548 if (match.hasMatch())
549 return match.captured(1).trimmed();
550 return filter;
551}
552
553- (void)createTextField
554{
555 NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } };
556 m_textField = [[NSTextField alloc] initWithFrame:textRect];
557 m_textField.cell.font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeRegular]];
558 m_textField.alignment = NSTextAlignmentRight;
559 m_textField.editable = false;
560 m_textField.selectable = false;
561 m_textField.bordered = false;
562 m_textField.drawsBackground = false;
565}
566
567- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails
568{
569 NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } };
570 m_popupButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO];
571 m_popupButton.target = self;
572 m_popupButton.action = @selector(filterChanged:);
573
574 if (!m_nameFilterDropDownList.isEmpty()) {
575 int filterToUse = -1;
576 for (int i = 0; i < m_nameFilterDropDownList.size(); ++i) {
577 const QString currentFilter = m_nameFilterDropDownList.at(i);
578 if (selectedFilter == currentFilter ||
579 (filterToUse == -1 && currentFilter.startsWith(selectedFilter)))
580 filterToUse = i;
581 QString filter = hideDetails ? [self removeExtensions:currentFilter] : currentFilter;
582 [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
583 }
584 if (filterToUse != -1)
585 [m_popupButton selectItemAtIndex:filterToUse];
586 }
587}
588
589- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name
590{
591 for (const QString &currentFilter : std::as_const(m_nameFilterDropDownList)) {
592 if (currentFilter.startsWith(name))
593 return QPlatformFileDialogHelper::cleanFilterList(currentFilter);
594 }
595 return QStringList();
596}
597
598- (void)createAccessory
599{
600 NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } };
601 m_accessoryView = [[NSView alloc] initWithFrame:accessoryRect];
602 [m_accessoryView addSubview:m_textField];
603 [m_accessoryView addSubview:m_popupButton];
604}
605
606@end
607
609
613
615{
616 if (!m_delegate)
617 return;
618
620 [m_delegate release];
621 m_delegate = nil;
622}
623
625{
626 if (result == NSModalResponseOK)
627 emit accept();
628 else
629 emit reject();
630}
631
633{
634 m_directory = directory;
635
636 if (m_delegate)
637 m_delegate->m_panel.directoryURL = [NSURL fileURLWithPath:directory.toLocalFile().toNSString()];
638}
639
641{
642 return m_directory;
643}
644
646{
647 if (!path || [path isEqual:NSNull.null] || !path.length)
648 return;
649
650 const auto oldDirectory = m_directory;
651 m_directory = QUrl::fromLocalFile(
652 QString::fromNSString(path).normalized(QString::NormalizationForm_C));
653
654 if (m_directory != oldDirectory) {
655 // FIXME: Plumb old directory back to QFileDialog's lastVisitedDir?
656 emit directoryEntered(m_directory);
657 }
658}
659
661{
662 QString filePath = filename.toLocalFile();
663 if (QDir::isRelativePath(filePath))
664 filePath = QFileInfo(directory().toLocalFile(), filePath).filePath();
665
666 // There seems to no way to select a file once the dialog is running.
667 // So do the next best thing, set the file's directory:
669}
670
672{
673 if (m_delegate)
674 return [m_delegate selectedFiles];
675 return QList<QUrl>();
676}
677
679{
680 if (!m_delegate)
681 return;
682
683 [m_delegate updateProperties];
684}
685
687{
688 if (!options())
689 return;
690 const int index = options()->nameFilters().indexOf(filter);
691 if (index != -1) {
692 if (!m_delegate) {
694 return;
695 }
696 [m_delegate->m_popupButton selectItemAtIndex:index];
697 [m_delegate filterChanged:nil];
698 }
699}
700
702{
703 if (!m_delegate)
705 int index = [m_delegate->m_popupButton indexOfSelectedItem];
706 if (index >= options()->nameFilters().count())
707 return QString();
708 return index != -1 ? options()->nameFilters().at(index) : QString();
709}
710
712{
713 if (!m_delegate)
714 return;
715
716 [m_delegate closePanel];
717
718 if (m_eventLoop)
719 m_eventLoop->exit();
720}
721
722bool QCocoaFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
723{
724 if (windowFlags & Qt::WindowStaysOnTopHint) {
725 // The native file dialog tries all it can to stay
726 // on the NSModalPanel level. And it might also show
727 // its own "create directory" dialog that we cannot control.
728 // So we need to use the non-native version in this case...
729 return false;
730 }
731
732 createNSOpenSavePanelDelegate();
733
734 return [m_delegate showPanel:windowModality withParent:parent];
735}
736
737void QCocoaFileDialogHelper::createNSOpenSavePanelDelegate()
738{
740
742 const QList<QUrl> selectedFiles = opts->initiallySelectedFiles();
743 const QUrl directory = m_directory.isEmpty() ? opts->initialDirectory() : m_directory;
744 const bool selectDir = selectedFiles.isEmpty();
747 initWithAcceptMode:
748 selection
749 options:opts
750 helper:this];
751
752 [static_cast<QNSOpenSavePanelDelegate *>(m_delegate) release];
753 m_delegate = delegate;
754}
755
757{
758 Q_ASSERT(m_delegate);
759
760 if (m_delegate->m_panel.visible) {
761 // WindowModal or NonModal, so already shown above
762 QEventLoop eventLoop;
763 m_eventLoop = &eventLoop;
764 eventLoop.exec(QEventLoop::DialogExec);
765 m_eventLoop = nullptr;
766 } else {
767 // ApplicationModal, so show and block using native APIs
768 [m_delegate runApplicationModalPanel];
769 }
770}
771
773{
774 return true;
775}
776
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
static void clearCurrentThreadCocoaEventDispatcherInterruptFlag()
QList< QUrl > selectedFiles() const override
bool defaultNameFilterDisables() const override
QString selectedNameFilter() const override
void panelDirectoryDidChange(NSString *path)
QUrl directory() const override
void setDirectory(const QUrl &directory) override
bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override
void selectNameFilter(const QString &filter) override
void selectFile(const QUrl &filename) override
void panelClosed(NSInteger result)
static QAbstractEventDispatcher * eventDispatcher()
Returns a pointer to the event dispatcher object for the main thread.
static bool isRelativePath(const QString &path)
Returns true if path is relative; returns false if it is absolute.
Definition qdir.cpp:2412
@ Executable
Definition qdir.h:31
@ Files
Definition qdir.h:23
@ PermissionMask
Definition qdir.h:32
@ Hidden
Definition qdir.h:35
@ AllDirs
Definition qdir.h:40
@ NoSymLinks
Definition qdir.h:25
@ Readable
Definition qdir.h:29
@ Writable
Definition qdir.h:30
@ Dirs
Definition qdir.h:22
\inmodule QtCore
Definition qeventloop.h:16
int exec(ProcessEventsFlags flags=AllEvents)
Enters the main event loop and waits until exit() is called.
@ ExcludeSocketNotifiers
Definition qeventloop.h:28
@ ExcludeUserInputEvents
Definition qeventloop.h:27
void exit(int returnCode=0)
Tells the event loop to exit with a return code.
QList< QUrl > initiallySelectedFiles() const
bool isLabelExplicitlySet(DialogLabel label)
QDir::Filters filter() const
QString initiallySelectedNameFilter() const
QString labelText(DialogLabel label) const
AcceptMode acceptMode() const
void setInitiallySelectedNameFilter(const QString &)
QStringList nameFilters() const
bool testOption(FileDialogOption option) const
QString baseName() const
Returns the base name of the file without the path.
QString suffix() const
Returns the suffix (extension) of the file.
QString fileName() const
void setFile(const QString &file)
QString absolutePath() const
Returns the absolute path of the file system entry this QFileInfo refers to, excluding the entry's na...
QString filePath() const
Returns the path of the file system entry this QFileInfo refers to; the path may be absolute or relat...
Definition qlist.h:75
bool isEmpty() const noexcept
Definition qlist.h:401
void directoryEntered(const QUrl &directory)
static QStringList cleanFilterList(const QString &filter)
const QSharedPointer< QFileDialogOptions > & options() const
static QString removeMnemonics(const QString &original)
\inmodule QtCore \reentrant
\inmodule QtCore \reentrant
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
@ NormalizationForm_C
Definition qstring.h:619
QString normalized(NormalizationForm mode, QChar::UnicodeVersion version=QChar::Unicode_Unassigned) const
Returns the string in the given Unicode normalization mode, according to the given version of the Uni...
Definition qstring.cpp:8475
\inmodule QtCore
Definition qurl.h:94
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
Definition qurl.cpp:3368
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3425
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2468
\inmodule QtGui
Definition qwindow.h:63
void extension()
[6]
Definition dialogs.cpp:230
QRect textRect
Combined button and popup list for selecting options.
Definition qcompare.h:63
WindowModality
@ WindowModal
@ ApplicationModal
@ SkipEmptyParts
Definition qnamespace.h:128
@ WindowStaysOnTopHint
Definition qnamespace.h:233
QString self
Definition language.cpp:58
NSView * m_accessoryView
QSharedPointer< QFileDialogOptions > SharedPointerFileDialogOptions
QStringList m_selectedNameFilter
SharedPointerFileDialogOptions m_options
NSPopUpButton * m_popupButton
QString m_currentSelection
static NSString * strippedText(QString s)
static NSOpenPanel * openpanel_cast(NSSavePanel *panel)
QStringList m_nameFilterDropDownList
QPointer< QCocoaFileDialogHelper > m_helper
NSTextField * m_textField
NSMutableArray< NSString * > * qt_mac_QStringListToNSMutableArray(const QStringList &list)
long NSInteger
QList< QString > QStringList
Constructs a string list that contains the given string, str.
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define qGuiApp
#define qWarning
Definition qlogging.h:166
GLuint index
[2]
GLenum GLenum GLsizei count
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLuint name
GLdouble s
[6]
Definition qopenglext.h:235
GLuint in
GLsizei const GLchar *const * path
GLint GLenum GLboolean normalized
Definition qopenglext.h:752
GLuint64EXT * result
[6]
static QString toLocalFile(const QString &url)
Definition qqmlfile.cpp:708
static QString absolutePath(const QString &path)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
QWidget * panel
Definition settings.cpp:7
QUrl url("example.com")
[constructor-url-reference]
dialog setNameFilters(filters)
const QStringList filters({"Image files (*.png *.xpm *.jpg)", "Text files (*.txt)", "Any files (*)" })
[6]
QItemSelection * selection
[0]
QHostInfo info
[0]
QQuickView * view
[0]