Native file dialog support for Mac
[profile/ivi/qtbase.git] / src / plugins / platforms / cocoa / qcocoafiledialoghelper.mm
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtGui module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qcocoafiledialoghelper.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
69 QT_FORWARD_DECLARE_CLASS(QFileDialogPrivate)
70 QT_FORWARD_DECLARE_CLASS(QString)
71 QT_FORWARD_DECLARE_CLASS(QStringList)
72 QT_FORWARD_DECLARE_CLASS(QWidget)
73 QT_FORWARD_DECLARE_CLASS(QAction)
74 QT_FORWARD_DECLARE_CLASS(QFileInfo)
75 QT_USE_NAMESPACE
76
77 @class QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate);
78
79 @interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate)
80 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6
81     : NSObject<NSOpenSavePanelDelegate>
82 #else
83     : NSObject
84 #endif
85 {
86     @public
87     NSOpenPanel *mOpenPanel;
88     NSSavePanel *mSavePanel;
89     NSView *mAccessoryView;
90     NSPopUpButton *mPopUpButton;
91     NSTextField *mTextField;
92     QFileDialogPrivate *mPriv;
93     QCocoaFileDialogHelper *mHelper;
94     NSString *mCurrentDir;
95     bool mConfirmOverwrite;
96     int mReturnCode;
97
98     QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode) mAcceptMode;
99     QT_PREPEND_NAMESPACE(QDir::Filters) *mQDirFilter;
100     QT_PREPEND_NAMESPACE(QFileDialog::FileMode) mFileMode;
101     QT_PREPEND_NAMESPACE(QFileDialog::Options) *mFileOptions;
102
103     QString *mLastFilterCheckPath;
104     QString *mCurrentSelection;
105     QStringList *mQDirFilterEntryList;
106     QStringList *mNameFilterDropDownList;
107     QStringList *mSelectedNameFilter;
108 }
109
110 - (NSString *)strip:(const QString &)label;
111 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename;
112 - (void)filterChanged:(id)sender;
113 - (void)showModelessPanel;
114 - (BOOL)runApplicationModalPanel;
115 - (void)showWindowModalSheet:(QWidget *)docWidget;
116 - (void)updateProperties;
117 - (QStringList)acceptableExtensionsForSave;
118 - (QString)removeExtensions:(const QString &)filter;
119 - (void)createTextField;
120 - (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails;
121 - (QStringList)findStrippedFilterWithVisualFilterName:(QString)name;
122 - (void)createAccessory;
123
124 @end
125
126 @implementation QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate)
127
128 - (id)initWithAcceptMode:(QT_PREPEND_NAMESPACE(QFileDialog::AcceptMode))acceptMode
129     title:(const QString &)title
130     hideNameFilterDetails:(bool)hideNameFilterDetails
131     qDirFilter:(QT_PREPEND_NAMESPACE(QDir::Filters))qDirFilter
132     fileOptions:(QT_PREPEND_NAMESPACE(QFileDialog::Options))fileOptions
133     fileMode:(QT_PREPEND_NAMESPACE(QFileDialog::FileMode))fileMode
134     selectFile:(const QString &)selectFile
135     confirmOverwrite:(bool)confirm
136     priv:(QFileDialogPrivate *)priv
137     helper:(QCocoaFileDialogHelper *)helper
138 {
139     self = [super init];
140
141     mAcceptMode = acceptMode;
142     if (mAcceptMode == QT_PREPEND_NAMESPACE(QFileDialog::AcceptOpen)){
143         mOpenPanel = [NSOpenPanel openPanel];
144         mSavePanel = mOpenPanel;
145     } else {
146         mSavePanel = [NSSavePanel savePanel];
147         mOpenPanel = 0;
148     }
149
150     [mSavePanel setLevel:NSModalPanelWindowLevel];
151     [mSavePanel setDelegate:self];
152     mQDirFilter = new QT_PREPEND_NAMESPACE(QDir::Filters)(qDirFilter);
153     mFileOptions = new QT_PREPEND_NAMESPACE(QFileDialog::Options)(fileOptions);
154     mFileMode = fileMode;
155     mConfirmOverwrite = confirm;
156     mReturnCode = -1;
157     mPriv = priv;
158     mHelper = helper;
159     mLastFilterCheckPath = new QString;
160     mQDirFilterEntryList = new QStringList;
161     mNameFilterDropDownList = new QStringList(priv->nameFilters);
162     QString selectedVisualNameFilter = priv->qFileDialogUi->fileTypeCombo->currentText();
163     mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
164
165     QFileInfo sel(selectFile);
166     if (sel.isDir()){
167         mCurrentDir = [qt_mac_QStringToNSString(sel.absoluteFilePath()) retain];
168         mCurrentSelection = new QString;
169     } else {
170         mCurrentDir = [qt_mac_QStringToNSString(sel.absolutePath()) retain];
171         mCurrentSelection = new QString(sel.absoluteFilePath());
172     }
173
174     [mSavePanel setTitle:qt_mac_QStringToNSString(title)];
175     [self createPopUpButton:selectedVisualNameFilter hideDetails:hideNameFilterDetails];
176     [self createTextField];
177     [self createAccessory];
178     [mSavePanel setAccessoryView:mNameFilterDropDownList->size() > 1 ? mAccessoryView : nil];
179
180     if (mPriv){
181         [mSavePanel setPrompt:[self strip:mPriv->acceptLabel]];
182         if (mPriv->fileNameLabelExplicitlySat)
183             [mSavePanel setNameFieldLabel:[self strip:mPriv->qFileDialogUi->fileNameLabel->text()]];
184     }
185
186     [self updateProperties];
187     [mSavePanel retain];
188     return self;
189 }
190
191 - (void)dealloc
192 {
193     delete mQDirFilter;
194     delete mFileOptions;
195     delete mLastFilterCheckPath;
196     delete mQDirFilterEntryList;
197     delete mNameFilterDropDownList;
198     delete mSelectedNameFilter;
199     delete mCurrentSelection;
200
201     [mSavePanel orderOut:mSavePanel];
202     [mSavePanel setAccessoryView:nil];
203     [mPopUpButton release];
204     [mTextField release];
205     [mAccessoryView release];
206     [mSavePanel setDelegate:nil];
207     [mSavePanel release];
208     [mCurrentDir release];
209     [super dealloc];
210 }
211
212 - (NSString *)strip:(const QString &)label
213 {
214     QAction a(label, 0);
215     return qt_mac_QStringToNSString(a.iconText());
216 }
217
218 - (void)closePanel
219 {
220     *mCurrentSelection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]);
221     [mSavePanel close];
222 }
223
224 - (void)showModelessPanel
225 {
226     if (mOpenPanel){
227         QFileInfo info(*mCurrentSelection);
228         NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
229         NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
230         bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
231             || [self panel:nil shouldShowFilename:filepath];
232         [mOpenPanel
233             beginForDirectory:mCurrentDir
234             file:selectable ? filename : nil
235             types:nil
236             modelessDelegate:self
237             didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
238             contextInfo:nil];
239     }
240 }
241
242 - (BOOL)runApplicationModalPanel
243 {
244     QFileInfo info(*mCurrentSelection);
245     NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
246     NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
247     bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
248         || [self panel:nil shouldShowFilename:filepath];
249     mReturnCode = [mSavePanel
250         runModalForDirectory:mCurrentDir
251         file:selectable ? filename : @"untitled"];
252
253     QAbstractEventDispatcher::instance()->interrupt();
254     return (mReturnCode == NSOKButton);
255 }
256
257 - (QT_PREPEND_NAMESPACE(QDialog::DialogCode))dialogResultCode
258 {
259     return (mReturnCode == NSOKButton) ? QT_PREPEND_NAMESPACE(QDialog::Accepted) : QT_PREPEND_NAMESPACE(QDialog::Rejected);
260 }
261
262 - (void)showWindowModalSheet:(QWidget *)docWidget
263 {
264     Q_UNUSED(docWidget);
265     QFileInfo info(*mCurrentSelection);
266     NSString *filename = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.fileName());
267     NSString *filepath = QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(info.filePath());
268     bool selectable = (mAcceptMode == QFileDialog::AcceptSave)
269         || [self panel:nil shouldShowFilename:filepath];
270     [mSavePanel
271         beginSheetForDirectory:mCurrentDir
272         file:selectable ? filename : nil
273         modalForWindow:nil
274         modalDelegate:self
275         didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:)
276         contextInfo:nil];
277 }
278
279 - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
280 {
281     Q_UNUSED(sender);
282
283     if ([filename length] == 0)
284         return NO;
285
286     // Always accept directories regardless of their names (unless it is a bundle):
287     BOOL isDir;
288     if ([[NSFileManager defaultManager] fileExistsAtPath:filename isDirectory:&isDir] && isDir) {
289         if ([mSavePanel treatsFilePackagesAsDirectories] == NO) {
290             if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO)
291                 return YES;
292         }
293     }
294
295     QString qtFileName = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)(filename);
296     QFileInfo info(qtFileName.normalized(QT_PREPEND_NAMESPACE(QString::NormalizationForm_C)));
297     QString path = info.absolutePath();
298     if (path != *mLastFilterCheckPath){
299         *mLastFilterCheckPath = path;
300         *mQDirFilterEntryList = info.dir().entryList(*mQDirFilter);
301     }
302     // Check if the QDir filter accepts the file:
303     if (!mQDirFilterEntryList->contains(info.fileName()))
304         return NO;
305
306     // No filter means accept everything
307     if (mSelectedNameFilter->isEmpty())
308         return YES;
309     // Check if the current file name filter accepts the file:
310     for (int i=0; i<mSelectedNameFilter->size(); ++i) {
311         if (QDir::match(mSelectedNameFilter->at(i), qtFileName))
312             return YES;
313     }
314     return NO;
315 }
316
317 - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag
318 {
319     Q_UNUSED(sender);
320     if (!okFlag)
321         return filename;
322     if (mConfirmOverwrite)
323         return filename;
324
325     // User has clicked save, and no overwrite confirmation should occur.
326     // To get the latter, we need to change the name we return (hence the prefix):
327     return [@"___qt_very_unlikely_prefix_" stringByAppendingString:filename];
328 }
329
330 - (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails
331 {
332     [mPopUpButton removeAllItems];
333     *mNameFilterDropDownList = filters;
334     if (filters.size() > 0){
335         for (int i=0; i<filters.size(); ++i) {
336             QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
337             [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)];
338         }
339         [mPopUpButton selectItemAtIndex:0];
340         [mSavePanel setAccessoryView:mAccessoryView];
341     } else
342         [mSavePanel setAccessoryView:nil];
343
344     [self filterChanged:self];
345 }
346
347 - (void)filterChanged:(id)sender
348 {
349     // This mDelegate function is called when the _name_ filter changes.
350     Q_UNUSED(sender);
351     QString selection = mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]);
352     *mSelectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
353     [mSavePanel validateVisibleColumns];
354     [self updateProperties];
355     if (mHelper)
356         mHelper->QNSOpenSavePanelDelegate_filterSelected([mPopUpButton indexOfSelectedItem]);
357 }
358
359 - (QString)currentNameFilter
360 {
361     return mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]);
362 }
363
364 - (QStringList)selectedFiles
365 {
366     if (mOpenPanel)
367         return QT_PREPEND_NAMESPACE(qt_mac_NSArrayToQStringList)([mOpenPanel filenames]);
368     else{
369         QStringList result;
370         QString filename = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString)([mSavePanel filename]);
371         result << filename.remove(QLatin1String("___qt_very_unlikely_prefix_"));
372         return result;
373     }
374 }
375
376 - (void)updateProperties
377 {
378     // Call this functions if mFileMode, mFileOptions,
379     // mNameFilterDropDownList or mQDirFilter changes.
380     // The savepanel does not contain the neccessary functions for this.
381     bool chooseFilesOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFile)
382         || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles);
383     bool chooseDirsOnly = mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::Directory)
384         || mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::DirectoryOnly)
385         || *mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ShowDirsOnly);
386
387     [mOpenPanel setCanChooseFiles:!chooseDirsOnly];
388     [mOpenPanel setCanChooseDirectories:!chooseFilesOnly];
389     [mSavePanel setCanCreateDirectories:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::ReadOnly))];
390     [mOpenPanel setAllowsMultipleSelection:(mFileMode == QT_PREPEND_NAMESPACE(QFileDialog::ExistingFiles))];
391     [mOpenPanel setResolvesAliases:!(*mFileOptions & QT_PREPEND_NAMESPACE(QFileDialog::DontResolveSymlinks))];
392
393     QStringList ext = [self acceptableExtensionsForSave];
394     if (mPriv && !ext.isEmpty() && !mPriv->defaultSuffix.isEmpty())
395         ext.prepend(mPriv->defaultSuffix);
396     [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : QT_PREPEND_NAMESPACE(qt_mac_QStringListToNSMutableArray(ext))];
397
398     if ([mSavePanel isVisible])
399         [mOpenPanel validateVisibleColumns];
400 }
401
402 - (void)panelSelectionDidChange:(id)sender
403 {
404     Q_UNUSED(sender);
405     if (mHelper) {
406         QString selection = QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString([mSavePanel filename]));
407         if (selection != mCurrentSelection) {
408             *mCurrentSelection = selection;
409             mHelper->QNSOpenSavePanelDelegate_selectionChanged(selection);
410         }
411     }
412 }
413
414 - (void)openPanelDidEnd:(NSOpenPanel *)panel returnCode:(int)returnCode  contextInfo:(void *)contextInfo
415 {
416     Q_UNUSED(panel);
417     Q_UNUSED(contextInfo);
418     mReturnCode = returnCode;
419     if (mHelper)
420         mHelper->QNSOpenSavePanelDelegate_panelClosed(returnCode == NSOKButton);
421 }
422
423 - (void)panel:(id)sender directoryDidChange:(NSString *)path
424 {
425     Q_UNUSED(sender);
426     if (!mHelper)
427         return;
428     if ([path isEqualToString:mCurrentDir])
429         return;
430
431     [mCurrentDir release];
432     mCurrentDir = [path retain];
433     mHelper->QNSOpenSavePanelDelegate_directoryEntered(QT_PREPEND_NAMESPACE(qt_mac_NSStringToQString(mCurrentDir)));
434 }
435
436 /*
437     Returns a list of extensions (e.g. "png", "jpg", "gif")
438     for the current name filter. If a filter do not conform
439     to the format *.xyz or * or *.*, an empty list
440     is returned meaning accept everything.
441 */
442 - (QStringList)acceptableExtensionsForSave
443 {
444     QStringList result;
445     for (int i=0; i<mSelectedNameFilter->count(); ++i) {
446         const QString &filter = mSelectedNameFilter->at(i);
447         if (filter.startsWith(QLatin1String("*."))
448                 && !filter.contains(QLatin1Char('?'))
449                 && filter.count(QLatin1Char('*')) == 1) {
450             result += filter.mid(2);
451         } else {
452             return QStringList(); // Accept everything
453         }
454     }
455     return result;
456 }
457
458 - (QString)removeExtensions:(const QString &)filter
459 {
460     QRegExp regExp(QT_PREPEND_NAMESPACE(QString::fromLatin1)(QT_PREPEND_NAMESPACE(QFileDialogPrivate::qt_file_dialog_filter_reg_exp)));
461     if (regExp.indexIn(filter) != -1)
462         return regExp.cap(1).trimmed();
463     return filter;
464 }
465
466 - (void)createTextField
467 {
468     NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } };
469     mTextField = [[NSTextField alloc] initWithFrame:textRect];
470     [[mTextField cell] setFont:[NSFont systemFontOfSize:
471             [NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
472     [mTextField setAlignment:NSRightTextAlignment];
473     [mTextField setEditable:false];
474     [mTextField setSelectable:false];
475     [mTextField setBordered:false];
476     [mTextField setDrawsBackground:false];
477     if (mPriv){
478         [mTextField setStringValue:[self strip:mPriv->qFileDialogUi->fileTypeLabel->text()]];
479     } else
480         [mTextField setStringValue:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(QT_PREPEND_NAMESPACE(QFileDialog::tr)("Files of type:"))];
481 }
482
483 - (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails
484 {
485     NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } };
486     mPopUpButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO];
487     [mPopUpButton setTarget:self];
488     [mPopUpButton setAction:@selector(filterChanged:)];
489
490     QStringList *filters = mNameFilterDropDownList;
491     if (filters->size() > 0){
492         for (int i=0; i<mNameFilterDropDownList->size(); ++i) {
493             QString filter = hideDetails ? [self removeExtensions:filters->at(i)] : filters->at(i);
494             [mPopUpButton addItemWithTitle:QT_PREPEND_NAMESPACE(qt_mac_QStringToNSString)(filter)];
495             if (filters->at(i).startsWith(selectedFilter))
496                 [mPopUpButton selectItemAtIndex:i];
497         }
498     }
499 }
500
501 - (QStringList) findStrippedFilterWithVisualFilterName:(QString)name
502 {
503     for (int i=0; i<mNameFilterDropDownList->size(); ++i) {
504         if (mNameFilterDropDownList->at(i).startsWith(name))
505             return QFileDialogPrivate::qt_clean_filter_list(mNameFilterDropDownList->at(i));
506     }
507     return QStringList();
508 }
509
510 - (void)createAccessory
511 {
512     NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } };
513     mAccessoryView = [[NSView alloc] initWithFrame:accessoryRect];
514     [mAccessoryView addSubview:mTextField];
515     [mAccessoryView addSubview:mPopUpButton];
516 }
517
518 @end
519
520 QT_BEGIN_NAMESPACE
521
522 static bool qt_mac_is_macsheet(const QWidget *w)
523 {
524     if (!w)
525         return false;
526
527     Qt::WindowModality modality = w->windowModality();
528     if (modality == Qt::ApplicationModal)
529         return false;
530     return w->parentWidget() && (modality == Qt::WindowModal || w->windowType() == Qt::Sheet);
531 }
532
533 QCocoaFileDialogHelper::QCocoaFileDialogHelper(QFileDialog *dialog) :
534     qtFileDialog(dialog), mDelegate(0)
535 {
536 }
537
538 QCocoaFileDialogHelper::~QCocoaFileDialogHelper()
539 {
540
541 }
542
543 void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath)
544 {
545     qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "currentChanged", Q_ARG(QString, newPath));
546 }
547
548 void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_panelClosed(bool accepted)
549 {
550     if (accepted)
551         qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "accept");
552     else
553         qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "reject");
554 }
555
556 void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir)
557 {
558     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
559     priv->setLastVisitedDirectory(newDir);
560     qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "directoryEntered", Q_ARG(QString, newDir));
561 }
562
563 void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_filterSelected(int menuIndex)
564 {
565     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
566     qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "filterSelected", Q_ARG(QString, priv->nameFilters.at(menuIndex)));
567 }
568
569 extern OSErr qt_mac_create_fsref(const QString &, FSRef *); // qglobal.cpp
570 extern void qt_mac_to_pascal_string(QString s, Str255 str, TextEncoding encoding=0, int len=-1); // qglobal.cpp
571
572 void QCocoaFileDialogHelper::setDirectory_sys(const QString &directory)
573 {
574     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
575     [delegate->mSavePanel setDirectory:qt_mac_QStringToNSString(directory)];
576 }
577
578 QString QCocoaFileDialogHelper::directory_sys() const
579 {
580     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
581     return qt_mac_NSStringToQString([delegate->mSavePanel directory]);
582 }
583
584 void QCocoaFileDialogHelper::selectFile_sys(const QString &filename)
585 {
586     QString filePath = filename;
587     if (QDir::isRelativePath(filePath))
588         filePath = QFileInfo(directory_sys(), filePath).filePath();
589
590     // There seems to no way to select a file once the dialog is running.
591     // So do the next best thing, set the file's directory:
592     setDirectory_sys(QFileInfo(filePath).absolutePath());
593 }
594
595 QStringList QCocoaFileDialogHelper::selectedFiles_sys() const
596 {
597     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
598     return [delegate selectedFiles];
599 }
600
601 void QCocoaFileDialogHelper::setNameFilters_sys(const QStringList &filters)
602 {
603     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
604     bool hideDetails = qtFileDialog->testOption(QFileDialog::HideNameFilterDetails);
605     [delegate setNameFilters:filters hideDetails:hideDetails];
606 }
607
608 void QCocoaFileDialogHelper::setFilter_sys()
609 {
610     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
611     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
612     *(delegate->mQDirFilter) = priv->model->filter();
613     delegate->mFileMode = priv->fileMode;
614     [delegate->mSavePanel setTitle:qt_mac_QStringToNSString(qtFileDialog->windowTitle())];
615     [delegate->mSavePanel setPrompt:[delegate strip:priv->acceptLabel]];
616     if (priv->fileNameLabelExplicitlySat)
617         [delegate->mSavePanel setNameFieldLabel:[delegate strip:priv->qFileDialogUi->fileNameLabel->text()]];
618
619     [delegate updateProperties];
620 }
621
622 void QCocoaFileDialogHelper::selectNameFilter_sys(const QString &filter)
623 {
624     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
625     int index = priv->nameFilters.indexOf(filter);
626     if (index != -1) {
627         QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
628         [delegate->mPopUpButton selectItemAtIndex:index];
629         [delegate filterChanged:nil];
630     }
631 }
632
633 QString QCocoaFileDialogHelper::selectedNameFilter_sys() const
634 {
635     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
636     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
637     int index = [delegate->mPopUpButton indexOfSelectedItem];
638     return index != -1 ? priv->nameFilters.at(index) : QString();
639 }
640
641 void QCocoaFileDialogHelper::deleteNativeDialog_sys()
642 {
643     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
644     [reinterpret_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate) release];
645     mDelegate = 0;
646     priv->nativeDialogInUse = false;
647 }
648
649 bool QCocoaFileDialogHelper::setVisible_sys(bool visible)
650 {
651 //    Q_Q(QFileDialog);
652     if (!visible == qtFileDialog->isHidden())
653         return false;
654
655     if (qtFileDialog->windowFlags() & Qt::WindowStaysOnTopHint) {
656         // The native file dialog tries all it can to stay
657         // on the NSModalPanel level. And it might also show
658         // its own "create directory" dialog that we cannot control.
659         // So we need to use the non-native version in this case...
660         return false;
661     }
662
663     return visible ? showCocoaFilePanel() : hideCocoaFilePanel();
664 }
665
666 void QCocoaFileDialogHelper::createNSOpenSavePanelDelegate()
667 {
668     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
669     if (mDelegate)
670         return;
671
672     bool selectDir = qtFileDialog->selectedFiles().isEmpty();
673     QString selection(selectDir ? qtFileDialog->directory().absolutePath() : qtFileDialog->selectedFiles().value(0));
674     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = [[QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) alloc]
675         initWithAcceptMode:priv->acceptMode
676         title:qtFileDialog->windowTitle()
677         hideNameFilterDetails:qtFileDialog->testOption(QFileDialog::HideNameFilterDetails)
678         qDirFilter:priv->model->filter()
679         fileOptions:priv->opts
680         fileMode:priv->fileMode
681         selectFile:selection
682         confirmOverwrite:!qtFileDialog->testOption(QFileDialog::DontConfirmOverwrite)
683         priv:priv
684         helper:this];
685
686     mDelegate = delegate;
687 }
688
689 bool QCocoaFileDialogHelper::showCocoaFilePanel()
690 {
691 //    Q_Q(QFileDialog);
692     createNSOpenSavePanelDelegate();
693     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
694     if (qt_mac_is_macsheet(qtFileDialog))
695         [delegate showWindowModalSheet:qtFileDialog->parentWidget()];
696     else
697         [delegate showModelessPanel];
698     return true;
699 }
700
701 bool QCocoaFileDialogHelper::hideCocoaFilePanel()
702 {
703     if (!mDelegate){
704         // Nothing to do. We return false to leave the question
705         // open regarding whether or not to go native:
706         return false;
707     } else {
708         QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
709         [delegate closePanel];
710         // Even when we hide it, we are still using a
711         // native dialog, so return true:
712         return true;
713     }
714 }
715
716 void QCocoaFileDialogHelper::platformNativeDialogModalHelp()
717 {
718     // Do a queued meta-call to open the native modal dialog so it opens after the new
719     // event loop has started to execute (in QDialog::exec). Using a timer rather than
720     // a queued meta call is intentional to ensure that the call is only delivered when
721     // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not
722     // running (which is the case if e.g a top-most QEventLoop has been
723     // interrupted, and the second-most event loop has not yet been reactivated (regardless
724     // if [NSApp run] is still on the stack)), showing a native modal dialog will fail.
725     QFileDialogPrivate *priv = static_cast<QFileDialogPrivate*>(d_ptr);
726     if (priv->nativeDialogInUse){
727         QTimer::singleShot(1, qtFileDialog, SLOT(_q_platformRunNativeAppModalPanel()));
728     }
729 }
730
731 void QCocoaFileDialogHelper::_q_platformRunNativeAppModalPanel()
732 {
733     // TODO:
734 #if 0
735     QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
736 #endif
737     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
738     [delegate runApplicationModalPanel];
739     if (dialogResultCode_sys() == QDialog::Accepted)
740         qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "accept");
741     else
742         qtFileDialog->metaObject()->invokeMethod(qtFileDialog, "reject");
743 }
744
745 QDialog::DialogCode QCocoaFileDialogHelper::dialogResultCode_sys()
746 {
747     QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *delegate = static_cast<QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) *>(mDelegate);
748     return [delegate dialogResultCode];
749 }
750
751 bool QCocoaFileDialogHelper::defaultNameFilterDisables() const
752 {
753     return true;
754 }
755
756 QT_END_NAMESPACE
757
758 #endif // QT_NO_FILEDIALOG