1/****************************************************************************
2**
3** Copyright (C) 2015 The Qt Company Ltd.
4** Contact: http://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 2.1 or version 3 as published by the Free
20** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22** following information to ensure the GNU Lesser General Public License
23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25**
26** As a special exception, The Qt Company gives you certain additional
27** rights. These rights are described in The Qt Company LGPL Exception
28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29**
30** GNU General Public License Usage
31** Alternatively, this file may be used under the terms of the GNU
32** General Public License version 3.0 as published by the Free Software
33** Foundation and appearing in the file LICENSE.GPL included in the
34** packaging of this file.  Please review the following information to
35** ensure the GNU General Public License version 3.0 requirements will be
36** met: http://www.gnu.org/copyleft/gpl.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qfiledialog.h"
43
44#ifndef QT_NO_FILEDIALOG
45
46/*****************************************************************************
47  QFileDialog debug facilities
48 *****************************************************************************/
49//#define DEBUG_FILEDIALOG_FILTERS
50
51#include <qapplication.h>
52#include <private/qapplication_p.h>
53#include <private/qfiledialog_p.h>
54#include <private/qt_mac_p.h>
55#include <private/qt_cocoa_helpers_mac_p.h>
56#include <qregexp.h>
57#include <qbuffer.h>
58#include <qdebug.h>
59#include <qstringlist.h>
60#include <qaction.h>
61#include <qtextcodec.h>
62#include <qvarlengtharray.h>
63#include <qdesktopwidget.h>
64#include <stdlib.h>
65#include <qabstracteventdispatcher.h>
66#import <AppKit/NSSavePanel.h>
67#include "ui_qfiledialog.h"
68
69QT_BEGIN_NAMESPACE
70
71extern QStringList qt_make_filter_list(const QString &filter); // qfiledialog.cpp
72extern QStringList qt_clean_filter_list(const QString &filter); // qfiledialog.cpp
73extern const char *qt_file_dialog_filter_reg_exp; // qfiledialog.cpp
74extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm
75
76QT_END_NAMESPACE
77
78QT_FORWARD_DECLARE_CLASS(QFileDialogPrivate)
79QT_FORWARD_DECLARE_CLASS(QString)
80QT_FORWARD_DECLARE_CLASS(QStringList)
81QT_FORWARD_DECLARE_CLASS(QWidget)
82QT_FORWARD_DECLARE_CLASS(QAction)
83QT_FORWARD_DECLARE_CLASS(QFileInfo)
84QT_USE_NAMESPACE
85
86@class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate);
87
88@interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate)
89#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
90    : NSObject<NSOpenSavePanelDelegate>
91#else
92    : NSObject
93#endif
94{
95    @public
96    NSOpenPanel *mOpenPanel;
97    NSSavePanel *mSavePanel;
98    NSView *mAccessoryView;
99    NSPopUpButton *mPopUpButton;
100    NSTextField *mTextField;
101    QFileDialogPrivate *mPriv;
102    NSString *mCurrentDir;
103    bool mConfirmOverwrite;
104    int mReturnCode;
105
106    QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode) mAcceptMode;
107    QT_PREPEND_NAMESPACE(QDir::Filters) *mQDirFilter;
108    QT_PREPEND_NAMESPACE(QFileDialog::FileMode) mFileMode;
109    QT_PREPEND_NAMESPACE(QFileDialog::Options) *mFileOptions;
110
111    QString *mLastFilterCheckPath;
112    QString *mCurrentSelection;
113    QStringList *mQDirFilterEntryList;
114    QStringList *mNameFilterDropDownList;
115    QStringList *mSelectedNameFilter;
116}
117
118- (NSString *)strip:(const QString &)label;
119- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
120- (void)filterChanged:(id)sender;
121- (void)showModelessPanel;
122- (BOOL)runApplicationModalPanel;
123- (void)showWindowModalSheet:(QWidget *)docWidget;
124- (void)updateProperties;
125- (QStringList)acceptableExtensionsForSave;
126- (QString)removeExtensions:(const QString &)filter;
127- (void)createTextField;
128- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails;
129- (QStringList)findStrippedFilterWithVisualFilterName:(QString)name;
130- (void)createAccessory;
131
132@end
133
134@implementation QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate)
135
136- (id)initWithAcceptMode:(QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode))acceptMode
137    title:(const QString &)title
138    hideNameFilterDetails:(bool)hideNameFilterDetails
139    qDirFilter:(QT_PREPEND_NAMESPACE(QDir::Filters))qDirFilter
140    fileOptions:(QT_PREPEND_NAMESPACE(QFileDialog::Options))fileOptions
141    fileMode:(QT_PREPEND_NAMESPACE(QFileDialog::FileMode))fileMode
142    selectFile:(const QString &)selectFile
143    confirmOverwrite:(bool)confirm
144    priv:(QFileDialogPrivate *)priv
145{
146    self = [super init];
147
148    mAcceptMode = acceptMode;
149    if (mAcceptMode == QT_PREPEND_NAMESPACE(QFileDialog::AcceptOpen)){
150        mOpenPanel = [NSOpenPanel openPanel];
151        mSavePanel = mOpenPanel;
152    } else {
153        mSavePanel = [NSSavePanel savePanel];
154        mOpenPanel = 0;
155    }
156
157    if ([mSavePanel respondsToSelector:@selector(setLevel:)])
158        [mSavePanel setLevel:NSModalPanelWindowLevel];
159    [mSavePanel setDelegate:self];
160    mQDirFilter = new QT_PREPEND_NAMESPACE(QDir::Filters)(qDirFilter);
161    mFileOptions = new QT_PREPEND_NAMESPACE(QFileDialog::Options)(fileOptions);
162    mFileMode = fileMode;
163    mConfirmOverwrite = confirm;
164    mReturnCode = -1;
165    mPriv = priv;
166    mLastFilterCheckPath = new QString;
167    mQDirFilterEntryList = new QStringList;
168    mNameFilterDropDownList = new QStringList(priv->nameFilters);
169    QString selectedVisualNameFilter = priv->qFileDialogUi->fileTypeCombo->currentText();
170    mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
171
172    QFileInfo sel(selectFile);
173    if (sel.isDir() && !sel.isBundle()){
174        mCurrentDir = [qt_mac_QStringToNSString(sel.absoluteFilePath()) retain];
175        mCurrentSelection = new QString;
176    } else {
177        mCurrentDir = [qt_mac_QStringToNSString(sel.absolutePath()) retain];
178        mCurrentSelection = new QString(sel.absoluteFilePath());
179    }
180
181    [mSavePanel setTitle:qt_mac_QStringToNSString(title)];
182    [self createPopUpButton:selectedVisualNameFilter hideDetails:hideNameFilterDetails];
183    [self createTextField];
184    [self createAccessory];
185    [mSavePanel setAccessoryView:mNameFilterDropDownList->size() > 1 ? mAccessoryView : nil];
186
187    if (mPriv){
188        [mSavePanel setPrompt:[self strip:mPriv->acceptLabel]];
189        if (mPriv->fileNameLabelExplicitlySat)
190            [mSavePanel setNameFieldLabel:[self strip:mPriv->qFileDialogUi->fileNameLabel->text()]];
191    }
192
193    [self updateProperties];
194    [mSavePanel retain];
195    return self;
196}
197
198- (void)dealloc
199{
200    delete mQDirFilter;
201    delete mFileOptions;
202    delete mLastFilterCheckPath;
203    delete mQDirFilterEntryList;
204    delete mNameFilterDropDownList;
205    delete mSelectedNameFilter;
206    delete mCurrentSelection;
207
208    if ([mSavePanel respondsToSelector:@selector(orderOut:)])
209        [mSavePanel orderOut:mSavePanel];
210    [mSavePanel setAccessoryView:nil];
211    [mPopUpButton release];
212    [mTextField release];
213    [mAccessoryView release];
214    [mSavePanel setDelegate:nil];
215    [mSavePanel release];
216    [mCurrentDir release];
217    [super dealloc];
218}
219
220- (NSString *)strip:(const QString &)label
221{
222    QAction a(label, 0);
223    return qt_mac_QStringToNSString(a.iconText());
224}
225
226- (void)closePanel
227{
228    *mCurrentSelection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]);
229    if ([mSavePanel respondsToSelector:@selector(close)])
230        [mSavePanel close];
231    if ([mSavePanel isSheet])
232        [[NSApplication sharedApplication] endSheet: mSavePanel];
233}
234
235- (void)showModelessPanel
236{
237    if (mOpenPanel){
238        QFileInfo info(*mCurrentSelection);
239        NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
240        NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
241        bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
242            || [self panel:nil shouldShowFilename:filepath];
243        [mOpenPanel
244            beginForDirectory:mCurrentDir
245            file:selectable ? filename : nil
246            types:nil
247            modelessDelegate:self
248            didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
249            contextInfo:nil];
250    }
251}
252
253- (BOOL)runApplicationModalPanel
254{
255    QFileInfo info(*mCurrentSelection);
256    NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
257    NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
258    bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
259        || [self panel:nil shouldShowFilename:filepath];
260    mReturnCode = [mSavePanel
261        runModalForDirectory:mCurrentDir
262        file:selectable ? filename : @"untitled"];
263
264    QAbstractEventDispatcher::instance()->interrupt();
265    return (mReturnCode == NSOKButton);
266}
267
268- (QT_PREPEND_NAMESPACE(QDialog::DialogCode))dialogResultCode
269{
270    return (mReturnCode == NSOKButton) ? QT_PREPEND_NAMESPACE(QDialog::Accepted) : QT_PREPEND_NAMESPACE(QDialog::Rejected);
271}
272
273- (void)showWindowModalSheet:(QWidget *)docWidget
274{
275    Q_UNUSED(docWidget);
276    QFileInfo info(*mCurrentSelection);
277    NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
278    NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
279    bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
280        || [self panel:nil shouldShowFilename:filepath];
281    [mSavePanel
282        beginSheetForDirectory:mCurrentDir
283        file:selectable ? filename : nil
284#ifdef QT_MAC_USE_COCOA
285        modalForWindow:QT_PREPEND_NAMESPACE(qt_mac_window_for)(docWidget)
286#else
287        modalForWindow:nil
288#endif
289        modalDelegate:self
290        didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
291        contextInfo:nil];
292}
293
294- (BOOL)isHiddenFile:(NSString *)filename isDir:(BOOL)isDir
295{
296#ifdef QT_MAC_USE_COCOA
297    CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, (CFStringRef)filename, kCFURLPOSIXPathStyle, isDir);
298    CFBooleanRef isHidden;
299    Boolean errorOrHidden = false;
300    if (!CFURLCopyResourcePropertyForKey(url, kCFURLIsHiddenKey, &isHidden, NULL)) {
301        errorOrHidden = true;
302    } else {
303        if (CFBooleanGetValue(isHidden))
304            errorOrHidden = true;
305        CFRelease(isHidden);
306    }
307    CFRelease(url);
308    return errorOrHidden;
309#else
310    Q_UNUSED(filename);
311    Q_UNUSED(isDir);
312    return false;
313#endif
314}
315
316- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
317{
318    Q_UNUSED(sender);
319
320    if ([filename length] == 0)
321        return NO;
322
323    // Always accept directories regardless of their names (unless it is a bundle):
324    NSFileManager *fm = [NSFileManager defaultManager];
325    NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil];
326    if (!fileAttrs)
327        return NO; // Error accessing the file means 'no'.
328    NSString *fileType = [fileAttrs fileType];
329    bool isDir = [fileType isEqualToString:NSFileTypeDirectory];
330    if (isDir) {
331        if ([mSavePanel treatsFilePackagesAsDirectories] == NO) {
332            if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO)
333                return YES;
334        }
335    }
336
337    QString qtFileName
338        = QFileInfo(QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)(filename)).fileName();
339    // No filter means accept everything
340    bool nameMatches = mSelectedNameFilter->isEmpty();
341    // Check if the current file name filter accepts the file:
342    for (int i = 0; !nameMatches && i < mSelectedNameFilter->size(); ++i) {
343        if (QDir::match(mSelectedNameFilter->at(i), qtFileName))
344            nameMatches = true;
345    }
346    if (!nameMatches)
347        return NO;
348
349    QDir::Filters filter = *mQDirFilter;
350    if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir)
351        || (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular])
352        || ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink]))
353        return NO;
354
355    bool filterPermissions = ((filter & QDir::PermissionMask)
356                              && (filter & QDir::PermissionMask) != QDir::PermissionMask);
357    if (filterPermissions) {
358        if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename])
359            || (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename])
360            || (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename]))
361            return NO;
362    }
363    if (!(filter & QDir::Hidden)
364        && (qtFileName.startsWith(QLatin1Char('.')) || [self isHiddenFile:filename isDir:isDir]))
365            return NO;
366
367    return YES;
368}
369
370- (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
371{
372    Q_UNUSED(sender);
373    if (!okFlag)
374        return filename;
375    if (mConfirmOverwrite)
376        return filename;
377
378    // User has clicked save, and no overwrite confirmation should occur.
379    // To get the latter, we need to change the name we return (hence the prefix):
380    return [@"___qt_very_unlikely_prefix_" stringByAppendingString:filename];
381}
382
383- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails
384{
385    [mPopUpButton removeAllItems];
386    *mNameFilterDropDownList = filters;
387    if (filters.size() > 0){
388        for (int i=0; i<filters.size(); ++i) {
389            QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
390            [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)];
391        }
392        [mPopUpButton selectItemAtIndex:0];
393        [mSavePanel setAccessoryView:mAccessoryView];
394    } else
395        [mSavePanel setAccessoryView:nil];
396
397    [self filterChanged:self];
398}
399
400- (void)filterChanged:(id)sender
401{
402    // This mDelegate function is called when the _name_ filter changes.
403    Q_UNUSED(sender);
404    QString selection = mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]);
405    *mSelectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
406    if ([mSavePanel respondsToSelector:@selector(validateVisibleColumns)])
407        [mSavePanel validateVisibleColumns];
408    [self updateProperties];
409    if (mPriv)
410        mPriv->QNSOpenSavePanelDelegate_filterSelected([mPopUpButton indexOfSelectedItem]);
411}
412
413- (QString)currentNameFilter
414{
415    return mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]);
416}
417
418- (QStringList)selectedFiles
419{
420    if (mOpenPanel)
421        return QT_PREPEND_NAMESPACE(qt_mac_NSArrayToQStringList)([mOpenPanel filenames]);
422    else{
423        QStringList result;
424        QString filename = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]);
425        result << filename.remove(QLatin1String("___qt_very_unlikely_prefix_"));
426        return result;
427    }
428}
429
430- (void)updateProperties
431{
432    // Call this functions if mFileMode, mFileOptions,
433    // mNameFilterDropDownList or mQDirFilter changes.
434    // The savepanel does not contain the neccessary functions for this.
435    bool chooseFilesOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFile)
436        || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles);
437    bool chooseDirsOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::Directory)
438        || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::DirectoryOnly)
439        || *mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ShowDirsOnly);
440
441    [mOpenPanel setCanChooseFiles:!chooseDirsOnly];
442    [mOpenPanel setCanChooseDirectories:!chooseFilesOnly];
443    [mSavePanel setCanCreateDirectories:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ReadOnly))];
444    [mOpenPanel setAllowsMultipleSelection:(mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles))];
445    [mOpenPanel setResolvesAliases:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::DontResolveSymlinks))];
446
447    QStringList ext = [self acceptableExtensionsForSave];
448    if (mPriv && !ext.isEmpty() && !mPriv->defaultSuffix.isEmpty())
449        ext.prepend(mPriv->defaultSuffix);
450    [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : QT_PREPEND_NAMESPACE(qt_mac_QStringListToNSMutableArray(ext))];
451
452    if ([mSavePanel respondsToSelector:@selector(isVisible)] && [mSavePanel isVisible])
453    {
454        if ([mOpenPanel respondsToSelector:@selector(validateVisibleColumns)])
455            [mOpenPanel validateVisibleColumns];
456    }
457}
458
459- (void)panelSelectionDidChange:(id)sender
460{
461    Q_UNUSED(sender);
462    if (mPriv && [mSavePanel isVisible]) {
463        QString selection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString([mSavePanel filename]));
464        if (selection != mCurrentSelection) {
465            *mCurrentSelection = selection;
466            mPriv->QNSOpenSavePanelDelegate_selectionChanged(selection);
467        }
468    }
469}
470
471- (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode  contextInfo:(void *)contextInfo
472{
473    Q_UNUSED(panel);
474    Q_UNUSED(contextInfo);
475    mReturnCode = returnCode;
476    if (mPriv)
477        mPriv->QNSOpenSavePanelDelegate_panelClosed(returnCode == NSOKButton);
478}
479
480- (void)panel:(id)sender directoryDidChange:(NSString *)path
481{
482    Q_UNUSED(sender);
483    if (!mPriv)
484        return;
485    if ([path isEqualToString:mCurrentDir])
486        return;
487
488    if ([mSavePanel respondsToSelector:@selector(isVisible)] && ![mSavePanel isVisible])
489        return;
490    [mCurrentDir release];
491    mCurrentDir = [path retain];
492    mPriv->QNSOpenSavePanelDelegate_directoryEntered(QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString(mCurrentDir)));
493}
494
495/*
496    Returns a list of extensions (e.g. "png", "jpg", "gif")
497    for the current name filter. If a filter do not conform
498    to the format *.xyz or * or *.*, an empty list
499    is returned meaning accept everything.
500*/
501- (QStringList)acceptableExtensionsForSave
502{
503    QStringList result;
504    for (int i=0; i<mSelectedNameFilter->count(); ++i) {
505        const QString &filter = mSelectedNameFilter->at(i);
506        if (filter.startsWith(QLatin1String("*."))
507                && !filter.contains(QLatin1Char('?'))
508                && filter.count(QLatin1Char('*')) == 1) {
509            result += filter.mid(2);
510        } else {
511            return QStringList(); // Accept everything
512        }
513    }
514    return result;
515}
516
517- (QString)removeExtensions:(const QString &)filter
518{
519    QRegExp regExp(QT_PREPEND_NAMESPACE(QString::fromLatin1)(QT_PREPEND_NAMESPACE(qt_file_dialog_filter_reg_exp)));
520    if (regExp.indexIn(filter) != -1)
521        return regExp.cap(1).trimmed();
522    return filter;
523}
524
525- (void)createTextField
526{
527    NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } };
528    mTextField = [[NSTextField alloc] initWithFrame:textRect];
529    [[mTextField cell] setFont:[NSFont systemFontOfSize:
530            [NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
531    [mTextField setAlignment:NSRightTextAlignment];
532    [mTextField setEditable:false];
533    [mTextField setSelectable:false];
534    [mTextField setBordered:false];
535    [mTextField setDrawsBackground:false];
536    if (mPriv){
537        [mTextField setStringValue:[self strip:mPriv->qFileDialogUi->fileTypeLabel->text()]];
538    } else
539        [mTextField setStringValue:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(QT_PREPEND_NAMESPACE(QFileDialog::tr)("Files of type:"))];
540}
541
542- (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails
543{
544    NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } };
545    mPopUpButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO];
546    [mPopUpButton setTarget:self];
547    [mPopUpButton setAction:@selector(filterChanged:)];
548
549    if (mNameFilterDropDownList->size() > 0) {
550        int filterToUse = -1;
551        for (int i=0; i<mNameFilterDropDownList->size(); ++i) {
552            QString currentFilter = mNameFilterDropDownList->at(i);
553            if (selectedFilter == currentFilter ||
554                (filterToUse == -1 && currentFilter.startsWith(selectedFilter)))
555                filterToUse = i;
556            QString filter = hideDetails ? [self removeExtensions:currentFilter] : currentFilter;
557            [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)];
558        }
559        if (filterToUse != -1)
560            [mPopUpButton selectItemAtIndex:filterToUse];
561    }
562}
563
564- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name
565{
566    for (int i=0; i<mNameFilterDropDownList->size(); ++i) {
567        if (mNameFilterDropDownList->at(i).startsWith(name))
568            return qt_clean_filter_list(mNameFilterDropDownList->at(i));
569    }
570    return QStringList();
571}
572
573- (void)createAccessory
574{
575    NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } };
576    mAccessoryView = [[NSView alloc] initWithFrame:accessoryRect];
577    [mAccessoryView addSubview:mTextField];
578    [mAccessoryView addSubview:mPopUpButton];
579}
580
581@end
582
583QT_BEGIN_NAMESPACE
584
585void QFileDialogPrivate::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath)
586{
587    emit q_func()->currentChanged(newPath);
588}
589
590void QFileDialogPrivate::QNSOpenSavePanelDelegate_panelClosed(bool accepted)
591{
592    if (accepted)
593        q_func()->accept();
594    else
595        q_func()->reject();
596}
597
598void QFileDialogPrivate::QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir)
599{
600    setLastVisitedDirectory(newDir);
601    emit q_func()->directoryEntered(newDir);
602}
603
604void QFileDialogPrivate::QNSOpenSavePanelDelegate_filterSelected(int menuIndex)
605{
606    emit q_func()->filterSelected(nameFilters.at(menuIndex));
607}
608
609void QFileDialogPrivate::setDirectory_sys(const QString &directory)
610{
611#ifndef QT_MAC_USE_COCOA
612    if (directory == mCurrentLocation)
613        return;
614    mCurrentLocation = directory;
615    emit q_func()->directoryEntered(mCurrentLocation);
616
617    FSRef fsRef;
618    if (qt_mac_create_fsref(directory, &fsRef) == noErr) {
619        AEDesc desc;
620        if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr)
621            NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc);
622    }
623#else
624    QMacCocoaAutoReleasePool pool;
625    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
626    [delegate->mSavePanel setDirectory:qt_mac_QStringToNSString(directory)];
627#endif
628}
629
630QString QFileDialogPrivate::directory_sys() const
631{
632#ifndef QT_MAC_USE_COCOA
633    return mCurrentLocation;
634#else
635    QMacCocoaAutoReleasePool pool;
636    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
637    return qt_mac_NSStringToQString([delegate->mSavePanel directory]);
638#endif
639}
640
641void QFileDialogPrivate::selectFile_sys(const QString &filename)
642{
643    QString filePath = filename;
644    if (QDir::isRelativePath(filePath))
645        filePath = QFileInfo(directory_sys(), filePath).filePath();
646
647#ifndef QT_MAC_USE_COCOA
648    // Update the selection list immidiatly, so
649    // subsequent calls to selectedFiles() gets correct:
650    mCurrentSelectionList.clear();
651    mCurrentSelectionList << filename;
652    if (mCurrentSelection != filename){
653        mCurrentSelection = filename;
654        emit q_func()->currentChanged(mCurrentSelection);
655    }
656
657    AEDescList descList;
658    if (AECreateList(0, 0, false, &descList) != noErr)
659        return;
660
661    FSRef fsRef;
662    if (qt_mac_create_fsref(filePath, &fsRef) == noErr) {
663        AEDesc desc;
664        if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr){
665            if (AEPutDesc(&descList, 0, &desc) == noErr)
666                NavCustomControl(mDialog, kNavCtlSetSelection, (void*)&descList);
667        }
668    }
669
670    // Type the file name into the save dialog's text field:
671    UInt8 *strBuffer = (UInt8 *)malloc(1024);
672    qt_mac_to_pascal_string(QFileInfo(filename).fileName(), strBuffer);
673    NavCustomControl(mDialog, kNavCtlSetEditFileName, strBuffer);
674    free(strBuffer);
675#else
676    // There seems to no way to select a file once the dialog is running.
677    // So do the next best thing, set the file's directory:
678    setDirectory_sys(QFileInfo(filePath).absolutePath());
679#endif
680}
681
682QStringList QFileDialogPrivate::selectedFiles_sys() const
683{
684#ifndef QT_MAC_USE_COCOA
685    if (q_func()->acceptMode() == QFileDialog::AcceptOpen){
686        return mCurrentSelectionList;
687    } else {
688        return QStringList() << mCurrentLocation + QLatin1Char('/')
689                                + QCFString::toQString(NavDialogGetSaveFileName(mDialog));
690    }
691#else
692    QMacCocoaAutoReleasePool pool;
693    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
694    return [delegate selectedFiles];
695#endif
696}
697
698void QFileDialogPrivate::setNameFilters_sys(const QStringList &filters)
699{
700#ifndef QT_MAC_USE_COCOA
701    Q_UNUSED(filters);
702#else
703    QMacCocoaAutoReleasePool pool;
704    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
705    bool hideDetails = q_func()->testOption(QFileDialog::HideNameFilterDetails);
706    [delegate setNameFilters:filters hideDetails:hideDetails];
707#endif
708}
709
710void QFileDialogPrivate::setFilter_sys()
711{
712#ifndef QT_MAC_USE_COCOA
713#else
714    Q_Q(QFileDialog);
715    QMacCocoaAutoReleasePool pool;
716    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
717    *(delegate->mQDirFilter) = model->filter();
718    delegate->mFileMode = fileMode;
719    [delegate->mSavePanel setTitle:qt_mac_QStringToNSString(q->windowTitle())];
720    [delegate->mSavePanel setPrompt:[delegate strip:acceptLabel]];
721    if (fileNameLabelExplicitlySat)
722        [delegate->mSavePanel setNameFieldLabel:[delegate strip:qFileDialogUi->fileNameLabel->text()]];
723
724    [delegate updateProperties];
725#endif
726}
727
728void QFileDialogPrivate::selectNameFilter_sys(const QString &filter)
729{
730    int index = nameFilters.indexOf(filter);
731    if (index != -1) {
732#ifndef QT_MAC_USE_COCOA
733        NavMenuItemSpec navSpec;
734        bzero(&navSpec, sizeof(NavMenuItemSpec));
735        navSpec.menuType = index;
736        NavCustomControl(mDialog, kNavCtlSelectCustomType, &navSpec);
737#else
738        QMacCocoaAutoReleasePool pool;
739        QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
740        [delegate->mPopUpButton selectItemAtIndex:index];
741        [delegate filterChanged:nil];
742#endif
743    }
744}
745
746QString QFileDialogPrivate::selectedNameFilter_sys() const
747{
748#ifndef QT_MAC_USE_COCOA
749    int index = filterInfo.currentSelection;
750#else
751    QMacCocoaAutoReleasePool pool;
752    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
753    int index = [delegate->mPopUpButton indexOfSelectedItem];
754#endif
755    return index != -1 ? nameFilters.at(index) : QString();
756}
757
758void QFileDialogPrivate::deleteNativeDialog_sys()
759{
760#ifndef QT_MAC_USE_COCOA
761    if (mDialog)
762        NavDialogDispose(mDialog);
763    mDialog = 0;
764    mDialogStarted = false;
765#else
766    QMacCocoaAutoReleasePool pool;
767    [reinterpret_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate) release];
768    mDelegate = 0;
769#endif
770    nativeDialogInUse = false;
771}
772
773bool QFileDialogPrivate::setVisible_sys(bool visible)
774{
775    Q_Q(QFileDialog);
776    if (!visible == q->isHidden())
777        return false;
778
779    if (q->windowFlags() & Qt::WindowStaysOnTopHint) {
780        // The native file dialog tries all it can to stay
781        // on the NSModalPanel level. And it might also show
782        // its own "create directory" dialog that we cannot control.
783        // So we need to use the non-native version in this case...
784        return false;
785    }
786
787#ifndef QT_MAC_USE_COCOA
788    return visible ? showCarbonNavServicesDialog() : hideCarbonNavServicesDialog();
789#else
790    return visible ? showCocoaFilePanel() : hideCocoaFilePanel();
791#endif
792}
793
794#ifndef QT_MAC_USE_COCOA
795Boolean QFileDialogPrivate::qt_mac_filedialog_filter_proc(AEDesc *theItem, void *info,
796                                                                 void *data, NavFilterModes)
797{
798    QFileDialogPrivate *fileDialogPrivate = static_cast<QFileDialogPrivate *>(data);
799
800    if (!fileDialogPrivate || fileDialogPrivate->filterInfo.filters.isEmpty()
801        || (fileDialogPrivate->filterInfo.currentSelection < 0
802                && fileDialogPrivate->filterInfo.currentSelection
803                        >= fileDialogPrivate->filterInfo.filters.size()))
804        return true;
805
806    NavFileOrFolderInfo *theInfo = static_cast<NavFileOrFolderInfo *>(info);
807    QString file;
808    QString path;
809    const QtMacFilterName &fn
810           = fileDialogPrivate->filterInfo.filters.at(fileDialogPrivate->filterInfo.currentSelection);
811    if (theItem->descriptorType == typeFSRef) {
812        FSRef ref;
813        AEGetDescData(theItem, &ref, sizeof(ref));
814        UInt8 str_buffer[1024];
815        FSRefMakePath(&ref, str_buffer, 1024);
816        path = QString::fromUtf8(reinterpret_cast<const char *>(str_buffer));
817        int slsh = path.lastIndexOf(QLatin1Char('/'));
818        if (slsh != -1)
819            file = path.right(path.length() - slsh - 1);
820        else
821            file = path;
822    }
823    QStringList reg = fn.regexp.split(QLatin1String(";"));
824    for (QStringList::const_iterator it = reg.constBegin(); it != reg.constEnd(); ++it) {
825        QRegExp rg(*it, Qt::CaseInsensitive, QRegExp::Wildcard);
826#ifdef DEBUG_FILEDIALOG_FILTERS
827        qDebug("QFileDialogPrivate::qt_mac_filedialog_filter_proc:%d, asked to filter.. %s (%s)", __LINE__,
828                qPrintable(file), qPrintable(*it));
829#endif
830        if (rg.exactMatch(file))
831            return true;
832    }
833
834    if (theInfo->isFolder) {
835        if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:qt_mac_QStringToNSString(path)])
836            return false;
837        return true;
838    }
839    return false;
840}
841
842void QFileDialogPrivate::qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg,
843        NavCBRecPtr p, NavCallBackUserData data)
844{
845    QFileDialogPrivate *fileDialogPrivate = static_cast<QFileDialogPrivate *>(data);
846
847    switch(msg) {
848    case kNavCBPopupMenuSelect: {
849        NavMenuItemSpec *s = static_cast<NavMenuItemSpec *>(p->eventData.eventDataParms.param);
850        if (int(s->menuType) != fileDialogPrivate->filterInfo.currentSelection) {
851            fileDialogPrivate->filterInfo.currentSelection = s->menuType;
852            emit fileDialogPrivate->q_func()->filterSelected(fileDialogPrivate->nameFilters.at(s->menuType));
853        }
854        if (fileDialogPrivate->acceptMode == QFileDialog::AcceptSave) {
855            QString base = QCFString::toQString(NavDialogGetSaveFileName(p->context));
856            QFileInfo fi(base);
857            base = fi.completeBaseName();
858            const QtMacFilterName &fn = fileDialogPrivate->filterInfo.filters.at(
859                                                       fileDialogPrivate->filterInfo.currentSelection);
860            QStringList reg = fn.regexp.split(QLatin1String(";"), QString::SkipEmptyParts);
861            if (reg.count()) {
862                QString r = reg.first();
863                r  = r.right(r.length()-1);      // Strip the *
864                base += r;                        //"." + QString::number(s->menuType);
865            }
866            NavDialogSetSaveFileName(p->context, QCFString::toCFStringRef(base));
867        }
868#ifdef DEBUG_FILEDIALOG_FILTERS
869        qDebug("QFileDialogPrivate::qt_mac_filedialog_event_proc:%d - Selected a filter: %ld", __LINE__, s->menuType);
870#endif
871        break; }
872    case kNavCBStart:{
873        fileDialogPrivate->mDialogStarted = true;
874        // Set selected file:
875        QModelIndexList indexes = fileDialogPrivate->qFileDialogUi->listView->selectionModel()->selectedRows();
876        QString selected;
877        if (!indexes.isEmpty())
878            selected = indexes.at(0).data(QFileSystemModel::FilePathRole).toString();
879        else
880            selected = fileDialogPrivate->typedFiles().value(0);
881        fileDialogPrivate->selectFile_sys(selected);
882        fileDialogPrivate->selectNameFilter_sys(fileDialogPrivate->qFileDialogUi->fileTypeCombo->currentText());
883        break; }
884    case kNavCBSelectEntry:{
885        // Event: Current selection has changed.
886        QStringList prevSelectionList = fileDialogPrivate->mCurrentSelectionList;
887        fileDialogPrivate->mCurrentSelectionList.clear();
888        QString fileNameToEmit;
889
890        AEDescList *descList = (AEDescList *)p->eventData.eventDataParms.param;
891        // Get the number of files selected:
892        UInt8 strBuffer[1024];
893        long count;
894        OSErr err = AECountItems(descList, &count);
895        if (err != noErr || !count)
896            break;
897
898        for (long index=1; index<=count; ++index) {
899            FSRef ref;
900            err = AEGetNthPtr(descList, index, typeFSRef, 0, 0, &ref, sizeof(ref), 0);
901            if (err != noErr)
902                break;
903            FSRefMakePath(&ref, strBuffer, 1024);
904            QString selected = QString::fromUtf8((const char *)strBuffer);
905            fileDialogPrivate->mCurrentSelectionList << selected;
906            if (!prevSelectionList.contains(selected))
907                fileNameToEmit = selected;
908        }
909
910        if (!fileNameToEmit.isEmpty() && fileNameToEmit != fileDialogPrivate->mCurrentSelection)
911            emit fileDialogPrivate->q_func()->currentChanged(fileNameToEmit);
912        fileDialogPrivate->mCurrentSelection = fileNameToEmit;
913        break; }
914    case kNavCBShowDesktop:
915    case kNavCBNewLocation:{
916        // Event: Current directory has changed.
917        AEDesc *desc = (AEDesc *)p->eventData.eventDataParms.param;
918        FSRef ref;
919        AEGetDescData(desc, &ref, sizeof(ref));
920        UInt8 *strBuffer = (UInt8 *)malloc(1024);
921        FSRefMakePath(&ref, strBuffer, 1024);
922        QString newLocation = QString::fromUtf8((const char *)strBuffer);
923        free(strBuffer);
924        if (fileDialogPrivate->mCurrentLocation != newLocation){
925            fileDialogPrivate->mCurrentLocation = newLocation;
926            QFileDialog::FileMode mode = fileDialogPrivate->fileMode;
927            if (mode == QFileDialog::AnyFile || mode == QFileDialog::ExistingFile
928                    || mode == QFileDialog::ExistingFiles){
929                // When changing directory, the current selection is cleared if
930                // we are supposed to be selecting files only:
931                if (!fileDialogPrivate->mCurrentSelection.isEmpty()){
932                    fileDialogPrivate->mCurrentSelectionList.clear();
933                    fileDialogPrivate->mCurrentSelection.clear();
934                    emit fileDialogPrivate->q_func()->currentChanged(fileDialogPrivate->mCurrentSelection);
935                }
936            }
937            fileDialogPrivate->setLastVisitedDirectory(newLocation);
938            emit fileDialogPrivate->q_func()->directoryEntered(newLocation);
939        }
940        break; }
941    case kNavCBAccept:
942        fileDialogPrivate->mDialogClosed = true;
943        fileDialogPrivate->q_func()->accept();
944        break;
945    case kNavCBCancel:
946        fileDialogPrivate->mDialogClosed = true;
947        fileDialogPrivate->q_func()->reject();
948        break;
949    }
950}
951
952static QFileDialogPrivate::QtMacFilterName qt_mac_extract_filter(const QString &rawFilter, bool showDetails)
953{
954    QFileDialogPrivate::QtMacFilterName ret;
955    ret.filter = rawFilter;
956    QString result = rawFilter;
957    QRegExp r(QString::fromLatin1(qt_file_dialog_filter_reg_exp));
958    int index = r.indexIn(result);
959    if (index >= 0)
960        result = r.cap(2);
961
962    if (showDetails) {
963        ret.description = rawFilter;
964    } else {
965        if (index >= 0)
966            ret.description = r.cap(1).trimmed();
967        if (ret.description.isEmpty())
968            ret.description = result;
969    }
970    ret.regexp = result.replace(QLatin1Char(' '), QLatin1Char(';'));
971    return ret;
972}
973
974static QList<QFileDialogPrivate::QtMacFilterName> qt_mac_make_filters_list(const QString &filter, bool showDetails)
975{
976#ifdef DEBUG_FILEDIALOG_FILTERS
977    qDebug("QFileDialog:%d - Got filter (%s)", __LINE__, filter.latin1());
978#endif
979
980    QList<QFileDialogPrivate::QtMacFilterName> ret;
981    QString f(filter);
982    if (f.isEmpty())
983        f = QFileDialog::tr("All Files (*)");
984    if (f.isEmpty())
985        return ret;
986    QStringList filts = qt_make_filter_list(f);
987    for (QStringList::const_iterator it = filts.constBegin(); it != filts.constEnd(); ++it) {
988        QFileDialogPrivate::QtMacFilterName filter = qt_mac_extract_filter(*it, showDetails);
989#ifdef DEBUG_FILEDIALOG_FILTERS
990        qDebug("QFileDialog:%d Split out filter (%d) '%s' '%s' [%s]", __LINE__, ret.count(),
991                filter->regxp.latin1(), filter->description.latin1(), (*it).latin1());
992#endif
993        ret.append(filter);
994    }
995    return ret;
996}
997
998void QFileDialogPrivate::createNavServicesDialog()
999{
1000    Q_Q(QFileDialog);
1001    if (mDialog)
1002        deleteNativeDialog_sys();
1003
1004    NavDialogCreationOptions navOptions;
1005    NavGetDefaultDialogCreationOptions(&navOptions);
1006
1007    // Translate QFileDialog settings into NavDialog options:
1008    if (qt_mac_is_macsheet(q)) {
1009        navOptions.modality = kWindowModalityWindowModal;
1010        navOptions.parentWindow = qt_mac_window_for(q->parentWidget());
1011    } else if (q->windowModality() ==  Qt::ApplicationModal)
1012        navOptions.modality = kWindowModalityAppModal;
1013    else
1014        navOptions.modality = kWindowModalityNone;
1015    navOptions.optionFlags |= kNavSupportPackages;
1016    if (q->testOption(QFileDialog::DontConfirmOverwrite))
1017        navOptions.optionFlags |= kNavDontConfirmReplacement;
1018    if (fileMode != QFileDialog::ExistingFiles)
1019        navOptions.optionFlags &= ~kNavAllowMultipleFiles;
1020
1021    navOptions.windowTitle = QCFString::toCFStringRef(q->windowTitle());
1022
1023    navOptions.location.h = -1;
1024    navOptions.location.v = -1;
1025
1026    QWidget *parent = q->parentWidget();
1027    if (parent && parent->isVisible()) {
1028        WindowClass wclass;
1029        GetWindowClass(qt_mac_window_for(parent), &wclass);
1030        parent = parent->window();
1031        QString s = parent->windowTitle();
1032        navOptions.clientName = QCFString::toCFStringRef(s);
1033    }
1034
1035    filterInfo.currentSelection = 0;
1036    filterInfo.filters = qt_mac_make_filters_list(nameFilters.join(QLatin1String(";;")), q->isNameFilterDetailsVisible());
1037    QCFType<CFArrayRef> filterArray;
1038    if (filterInfo.filters.size() > 1) {
1039        int i = 0;
1040        CFStringRef *cfstringArray = static_cast<CFStringRef *>(malloc(sizeof(CFStringRef)
1041                                                                   * filterInfo.filters.size()));
1042        for (i = 0; i < filterInfo.filters.size(); ++i) {
1043            cfstringArray[i] = QCFString::toCFStringRef(filterInfo.filters.at(i).description);
1044        }
1045        filterArray = CFArrayCreate(kCFAllocatorDefault,
1046                        reinterpret_cast<const void **>(cfstringArray), filterInfo.filters.size(),
1047                        &kCFTypeArrayCallBacks);
1048        navOptions.popupExtension = filterArray;
1049        free(cfstringArray);
1050    }
1051
1052    if (q->acceptMode() == QFileDialog::AcceptSave) {
1053        if (NavCreatePutFileDialog(&navOptions, 'cute', kNavGenericSignature,
1054                    QFileDialogPrivate::qt_mac_filedialog_event_proc, this, &mDialog)) {
1055            qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
1056            return;
1057        }
1058    } else if (fileMode == QFileDialog::DirectoryOnly || fileMode == QFileDialog::Directory) {
1059        if (NavCreateChooseFolderDialog(&navOptions,
1060                    QFileDialogPrivate::qt_mac_filedialog_event_proc, 0, this, &mDialog)) {
1061            qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
1062            return;
1063        }
1064    } else {
1065        if (NavCreateGetFileDialog(&navOptions, 0,
1066                    QFileDialogPrivate::qt_mac_filedialog_event_proc, 0,
1067                    QFileDialogPrivate::qt_mac_filedialog_filter_proc, this, &mDialog)) {
1068            qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
1069            return;
1070        }
1071    }
1072
1073    // Set start-up directory:
1074    if (mCurrentLocation.isEmpty())
1075        mCurrentLocation = rootPath();
1076    FSRef fsRef;
1077    if (qt_mac_create_fsref(mCurrentLocation, &fsRef) == noErr) {
1078        AEDesc desc;
1079        if (AECreateDesc(typeFSRef, &fsRef, sizeof(FSRef), &desc) == noErr)
1080            NavCustomControl(mDialog, kNavCtlSetLocation, (void*)&desc);
1081    }
1082}
1083
1084bool QFileDialogPrivate::showCarbonNavServicesDialog()
1085{
1086    Q_Q(QFileDialog);
1087    if (q->acceptMode() == QFileDialog::AcceptSave && q->windowModality() == Qt::NonModal)
1088        return false; // cannot do native no-modal save dialogs.
1089    createNavServicesDialog();
1090    mDialogClosed = false;
1091    if (q->windowModality() != Qt::ApplicationModal)
1092        NavDialogRun(mDialog);
1093    return true;
1094}
1095
1096bool QFileDialogPrivate::hideCarbonNavServicesDialog()
1097{
1098    if (!mDialogClosed){
1099        mDialogClosed = true;
1100        NavCustomControl(mDialog, kNavCtlCancel, 0);
1101    }
1102    return true;
1103}
1104
1105#else // Cocoa
1106
1107void QFileDialogPrivate::createNSOpenSavePanelDelegate()
1108{
1109    Q_Q(QFileDialog);
1110    if (mDelegate)
1111        return;
1112
1113    bool selectDir = q->selectedFiles().isEmpty();
1114    QString selection(selectDir ? q->directory().absolutePath() : q->selectedFiles().value(0));
1115    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) alloc]
1116        initWithAcceptMode:acceptMode
1117        title:q->windowTitle()
1118        hideNameFilterDetails:q->testOption(QFileDialog::HideNameFilterDetails)
1119        qDirFilter:model->filter()
1120        fileOptions:opts
1121        fileMode:fileMode
1122        selectFile:selection
1123        confirmOverwrite:!q->testOption(QFileDialog::DontConfirmOverwrite)
1124        priv:this];
1125
1126    mDelegate = delegate;
1127}
1128
1129bool QFileDialogPrivate::showCocoaFilePanel()
1130{
1131    Q_Q(QFileDialog);
1132    QMacCocoaAutoReleasePool pool;
1133    createNSOpenSavePanelDelegate();
1134    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
1135    if (qt_mac_is_macsheet(q))
1136        [delegate showWindowModalSheet:q->parentWidget()];
1137    else if (!q->testAttribute(Qt::WA_ShowModal))
1138        [delegate showModelessPanel];
1139    return true;
1140}
1141
1142bool QFileDialogPrivate::hideCocoaFilePanel()
1143{
1144    if (!mDelegate){
1145        // Nothing to do. We return false to leave the question
1146        // open regarding whether or not to go native:
1147        return false;
1148    } else {
1149        QMacCocoaAutoReleasePool pool;
1150        QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
1151        [delegate closePanel];
1152        // Even when we hide it, we are still using a
1153        // native dialog, so return true:
1154        return true;
1155    }
1156}
1157
1158#endif
1159
1160void QFileDialogPrivate::mac_nativeDialogModalHelp()
1161{
1162    // Do a queued meta-call to open the native modal dialog so it opens after the new
1163    // event loop has started to execute (in QDialog::exec). Using a timer rather than
1164    // a queued meta call is intentional to ensure that the call is only delivered when
1165    // [NSApplication run] runs (timers are handeled special in cocoa). If NSApplication is not
1166    // running (which is the case if e.g a top-most QEventLoop has been
1167    // interrupted, and the second-most event loop has not yet been reactivated (regardless
1168    // if [NSApplication run] is still on the stack)), showing a native modal dialog will fail.
1169    if (nativeDialogInUse){
1170        Q_Q(QFileDialog);
1171        QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel()));
1172    }
1173}
1174
1175void QFileDialogPrivate::_q_macRunNativeAppModalPanel()
1176{
1177    QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
1178#ifndef QT_MAC_USE_COCOA
1179    NavDialogRun(mDialog);
1180#else
1181    Q_Q(QFileDialog);
1182    QMacCocoaAutoReleasePool pool;
1183    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
1184    [delegate runApplicationModalPanel];
1185    dialogResultCode_sys() == QDialog::Accepted ? q->accept() : q->reject();
1186#endif
1187}
1188
1189QDialog::DialogCode QFileDialogPrivate::dialogResultCode_sys()
1190{
1191#ifndef QT_MAC_USE_COCOA
1192    NavUserAction result = NavDialogGetUserAction(mDialog);
1193    if (result == kNavUserActionCancel || result == kNavUserActionNone)
1194        return QDialog::Rejected;
1195    else
1196        return QDialog::Accepted;
1197#else
1198    QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
1199    return [delegate dialogResultCode];
1200#endif
1201}
1202
1203
1204QT_END_NAMESPACE
1205
1206#endif // QT_NO_FILEDIALOG
1207
1208