source: trunk/src/gui/dialogs/qfiledialog_mac.mm@ 578

Last change on this file since 578 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

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