4#include <QtCore/qglobal.h>
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>
24#include <QtGui/qguiapplication.h>
25#include <QtGui/private/qguiapplication_p.h>
27#include <qpa/qplatformtheme.h>
28#include <qpa/qplatformnativeinterface.h>
30#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
45 if ([
panel isKindOfClass:NSOpenPanel.class])
46 return static_cast<NSOpenPanel*
>(
panel);
67- (instancetype)initWithAcceptMode:(const
QString &)selectFile
75 m_panel = [[NSOpenPanel openPanel] retain];
77 m_panel = [[NSSavePanel savePanel] retain];
79 m_panel.canSelectHiddenExtension = YES;
80 m_panel.level = NSModalPanelWindowLevel;
88 m_panel.extensionHidden = [&]{
91 for (
const auto &
extension : extensions) {
119 if (sel.isDir() && !sel.isBundle()){
120 m_panel.directoryURL = [NSURL fileURLWithPath:sel.absoluteFilePath().toNSString()];
123 m_panel.directoryURL = [NSURL fileURLWithPath:sel.absolutePath().toNSString()];
127 [
self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)];
128 [
self createTextField];
129 [
self createAccessory];
135 m_panel.delegate =
self;
138 openPanel.accessoryViewDisclosed = YES;
140 [
self updateProperties];
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;
160 NSString *filepath =
info.filePath().toNSString();
161 NSURL *
url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()];
163 || [self
panel:m_panel shouldEnableURL:
url];
165 m_panel.nameFieldStringValue = selectable ?
info.fileName().toNSString() :
@"";
167 [
self updateProperties];
172 NSView *
view =
reinterpret_cast<NSView*
>(parent->winId());
173 [m_panel beginSheetModalForWindow:view.window completionHandler:completionHandler];
177 [m_panel beginWithCompletionHandler:completionHandler];
183-(
void)runApplicationModalPanel
202 auto result = [m_panel runModal];
215 [NSApp endSheet:m_panel];
216 else if (NSApp.modalWindow == m_panel)
222- (BOOL)
panel:(
id)sender shouldEnableURL:(NSURL *)url
227 if (!filename.length)
230 const QFileInfo fileInfo(QString::fromNSString(filename));
234 if (fileInfo.isDir()) {
240 bool treatBundlesAsFiles = !m_panel.treatsFilePackagesAsDirectories;
241 if (!(treatBundlesAsFiles && [NSWorkspace.sharedWorkspace isFilePackageAtPath:filename]))
245 if (![self fileInfoMatchesCurrentNameFilter:fileInfo])
256 if (filterPermissions) {
273- (BOOL)
panel:(
id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError
284 if ([self fileInfoMatchesCurrentNameFilter:fileInfo])
287 if (fileInfo.suffix().isEmpty()) {
292 fileInfo = [
self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
298 auto *alert = [[NSAlert new] autorelease];
299 alert.alertStyle = NSAlertStyleCritical;
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()];
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")];
314 [alert beginSheetModalForWindow:m_panel
315 completionHandler:^(NSModalResponse returnCode) {
316 [NSApp stopModalWithCode:returnCode];
318 return [NSApp runModalForWindow:alert.window] == replaceButton.tag;
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()]
339 fileInfo.
baseName() +
'.' + filterInfo.completeSuffix());
342- (bool)fileInfoMatchesCurrentNameFilter:(const
QFileInfo &)fileInfo
359 [m_popupButton removeAllItems];
364 [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
366 [m_popupButton selectItemAtIndex:0];
369 m_panel.accessoryView = nil;
372 [
self filterChanged:self];
375- (
void)filterChanged:(
id)sender
383 [m_panel validateVisibleColumns];
384 [
self updateProperties];
395 for (NSURL *
url in openPanel.URLs) {
404 if (fileInfo.
suffix().
isEmpty() && ![self fileInfoMatchesCurrentNameFilter:fileInfo]) {
408 fileInfo = [
self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
416 fileInfo.
baseName() +
'.' + defaultSuffix);
423- (
void)updateProperties
441 openPanel.canChooseFiles = !chooseDirsOnly;
442 openPanel.canChooseDirectories = !chooseFilesOnly;
449 m_panel.allowedFileTypes = [
self computeAllowedFileTypes];
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;
465 qWarning() <<
"UTTypeDirectory unexpectedly reported an extension";
472 [m_panel validateVisibleColumns];
475- (
void)panelSelectionDidChange:(
id)sender
490 if (m_panel.visible) {
499- (
void)
panel:(
id)sender directoryDidChange:(NSString *)path
518- (NSArray<NSString*>*)computeAllowedFileTypes
525 if (!
filter.startsWith(
"*."_L1))
528 if (
filter.contains(u
'?'))
531 if (
filter.count(u
'*') != 1)
535 if (extensions.count() > 2)
538 fileTypes += extensions.last();
548 if (
match.hasMatch())
549 return match.captured(1).trimmed();
553- (
void)createTextField
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]];
567- (
void)createPopUpButton:(const
QString &)selectedFilter hideDetails:(BOOL)hideDetails
569 NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } };
570 m_popupButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO];
575 int filterToUse = -1;
578 if (selectedFilter == currentFilter ||
579 (filterToUse == -1 && currentFilter.startsWith(selectedFilter)))
581 QString filter = hideDetails ? [
self removeExtensions:currentFilter] : currentFilter;
582 [m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
584 if (filterToUse != -1)
585 [m_popupButton selectItemAtIndex:filterToUse];
592 if (currentFilter.startsWith(
name))
598- (
void)createAccessory
600 NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } };
602 [m_accessoryView addSubview:m_textField];
603 [m_accessoryView addSubview:m_popupButton];
626 if (
result == NSModalResponseOK)
637 m_delegate->m_panel.directoryURL = [NSURL fileURLWithPath:directory.toLocalFile().toNSString()];
650 const auto oldDirectory = m_directory;
654 if (m_directory != oldDirectory) {
675 return QList<QUrl>();
696 [
m_delegate->m_popupButton selectItemAtIndex:index];
732 createNSOpenSavePanelDelegate();
734 return [
m_delegate showPanel:windowModality withParent:parent];
737void QCocoaFileDialogHelper::createNSOpenSavePanelDelegate()
752 [static_cast<QNSOpenSavePanelDelegate *>(m_delegate) release];
753 m_delegate = delegate;
760 if (m_delegate->m_panel.visible) {
763 m_eventLoop = &eventLoop;
765 m_eventLoop =
nullptr;
static bool isEqual(const aiUVTransform &a, const aiUVTransform &b)
static void clearCurrentThreadCocoaEventDispatcherInterruptFlag()
QList< QUrl > selectedFiles() const override
bool defaultNameFilterDisables() const override
void setFilter() override
QString selectedNameFilter() const override
void panelDirectoryDidChange(NSString *path)
QUrl directory() const override
void setDirectory(const QUrl &directory) override
virtual ~QCocoaFileDialogHelper()
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.
int exec(ProcessEventsFlags flags=AllEvents)
Enters the main event loop and waits until exit() is called.
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
QString defaultSuffix() const
void setInitiallySelectedNameFilter(const QString &)
QUrl initialDirectory() const
QStringList nameFilters() const
FileMode fileMode() const
QString windowTitle() 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.
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...
bool isEmpty() const noexcept
\inmodule QtCore \reentrant
\inmodule QtCore \reentrant
\macro QT_RESTRICTED_CAST_FROM_ASCII
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
void clear()
Clears the contents of the string and makes it null.
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...
static QUrl fromLocalFile(const QString &localfile)
Returns a QUrl representation of localFile, interpreted as a local file.
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Combined button and popup list for selecting options.
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)
QList< QString > QStringList
Constructs a string list that contains the given string, str.
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
GLenum GLenum GLsizei count
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLsizei const GLchar *const * path
GLint GLenum GLboolean normalized
static QString toLocalFile(const QString &url)
static QString absolutePath(const QString &path)
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
static bool match(const uchar *found, uint foundLen, const char *target, uint targetLen)
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]