diff options
Diffstat (limited to 'src/lib/FL/Fl_Native_File_Chooser_MAC.cxx')
-rw-r--r-- | src/lib/FL/Fl_Native_File_Chooser_MAC.cxx | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/src/lib/FL/Fl_Native_File_Chooser_MAC.cxx b/src/lib/FL/Fl_Native_File_Chooser_MAC.cxx new file mode 100644 index 0000000..e556edf --- /dev/null +++ b/src/lib/FL/Fl_Native_File_Chooser_MAC.cxx | |||
@@ -0,0 +1,833 @@ | |||
1 | // | ||
2 | // Fl_Native_File_Chooser_MAC.cxx -- FLTK native OS file chooser widget | ||
3 | // | ||
4 | // Copyright 2004 by Greg Ercolano. | ||
5 | // | ||
6 | // This library is free software; you can redistribute it and/or | ||
7 | // modify it under the terms of the GNU Library General Public | ||
8 | // License as published by the Free Software Foundation; either | ||
9 | // version 2 of the License, or (at your option) any later version. | ||
10 | // | ||
11 | // This library is distributed in the hope that it will be useful, | ||
12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
14 | // Library General Public License for more details. | ||
15 | // | ||
16 | // You should have received a copy of the GNU Library General Public | ||
17 | // License along with this library; if not, write to the Free Software | ||
18 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||
19 | // USA. | ||
20 | // | ||
21 | // Please keep code 80 column compliant. | ||
22 | // | ||
23 | // 10 20 30 40 50 60 70 | ||
24 | // | | | | | | | | ||
25 | // 4567890123456789012345678901234567890123456789012345678901234567890123456789 | ||
26 | // | ||
27 | // TODO: | ||
28 | // o When doing 'open file', only dir is preset, not filename. | ||
29 | // Possibly 'preset_file' could be used to select the filename. | ||
30 | // | ||
31 | #include <FL/Fl.H> | ||
32 | #include <FL/Fl_Native_File_Chooser.H> | ||
33 | #include "common.cxx" // strnew/strfree/strapp/chrcat | ||
34 | |||
35 | // TRY TO CONVERT AN AEDesc TO AN FSSpec | ||
36 | // As per Apple Technical Q&A QA1274 | ||
37 | // eg: http://developer.apple.com/qa/qa2001/qa1274.html | ||
38 | // Returns 'noErr' if OK, | ||
39 | // or an 'OSX result code' on error. | ||
40 | // | ||
41 | static int AEDescToFSSpec(const AEDesc* desc, FSSpec* fsspec) { | ||
42 | OSStatus err = noErr; | ||
43 | AEDesc coerceDesc; | ||
44 | // If AEDesc isn't already an FSSpec, convert it to one | ||
45 | if ( desc->descriptorType != typeFSS ) { | ||
46 | if ( ( err = AECoerceDesc(desc, typeFSS, &coerceDesc) ) == noErr ) { | ||
47 | // Get FSSpec out of AEDesc | ||
48 | err = AEGetDescData(&coerceDesc, fsspec, sizeof(FSSpec)); | ||
49 | AEDisposeDesc(&coerceDesc); | ||
50 | } | ||
51 | } else { | ||
52 | err = AEGetDescData(desc, fsspec, sizeof(FSSpec)); | ||
53 | } | ||
54 | return( err ); | ||
55 | } | ||
56 | |||
57 | // CONVERT AN FSSpec TO A PATHNAME | ||
58 | static void FSSpecToPath(const FSSpec &spec, char *buff, int bufflen) { | ||
59 | FSRef fsRef; | ||
60 | FSpMakeFSRef(&spec, &fsRef); | ||
61 | FSRefMakePath(&fsRef, (UInt8*)buff, bufflen); | ||
62 | } | ||
63 | |||
64 | // CONVERT REGULAR PATH -> FSSpec | ||
65 | // If file does not exist, expect fnfErr. | ||
66 | // Returns 'noErr' if OK, | ||
67 | // or an 'OSX result code' on error. | ||
68 | // | ||
69 | static OSStatus PathToFSSpec(const char *path, FSSpec &spec) { | ||
70 | OSStatus err; | ||
71 | FSRef ref; | ||
72 | if ((err = FSPathMakeRef((const UInt8*)path, &ref, NULL)) != noErr) { | ||
73 | return(err); | ||
74 | } | ||
75 | // FSRef -> FSSpec | ||
76 | if ((err = FSGetCatalogInfo(&ref, kFSCatInfoNone, NULL, NULL, &spec, | ||
77 | NULL)) != noErr) { | ||
78 | return(err); | ||
79 | } | ||
80 | return(noErr); | ||
81 | } | ||
82 | |||
83 | // NAVREPLY: CTOR | ||
84 | Fl_Native_File_Chooser::NavReply::NavReply() { | ||
85 | _valid_reply = 0; | ||
86 | } | ||
87 | |||
88 | // NAVREPLY: DTOR | ||
89 | Fl_Native_File_Chooser::NavReply::~NavReply() { | ||
90 | if ( _valid_reply ) { | ||
91 | NavDisposeReply(&_reply); | ||
92 | } | ||
93 | } | ||
94 | |||
95 | // GET REPLY FROM THE NAV* DIALOG | ||
96 | int Fl_Native_File_Chooser::NavReply::get_reply(NavDialogRef& ref) { | ||
97 | if ( _valid_reply ) { | ||
98 | NavDisposeReply(&_reply); // dispose of previous | ||
99 | _valid_reply = 0; | ||
100 | } | ||
101 | if ( ref == NULL || NavDialogGetReply(ref, &_reply) != noErr ) { | ||
102 | return(-1); | ||
103 | } | ||
104 | _valid_reply = 1; | ||
105 | return(0); | ||
106 | } | ||
107 | |||
108 | // RETURN THE BASENAME USER WANTS TO 'Save As' | ||
109 | int Fl_Native_File_Chooser::NavReply::get_saveas_basename(char *s, int slen) { | ||
110 | if (CFStringGetCString(_reply.saveFileName, s, slen-1, | ||
111 | kCFStringEncodingUTF8) == false) { | ||
112 | s[0] = '\0'; | ||
113 | return(-1); | ||
114 | } | ||
115 | return(0); | ||
116 | } | ||
117 | |||
118 | // RETURN THE DIRECTORY NAME | ||
119 | // Returns 0 on success, -1 on error. | ||
120 | // | ||
121 | int Fl_Native_File_Chooser::NavReply::get_dirname(char *s, int slen) { | ||
122 | FSSpec fsspec; | ||
123 | if ( AEDescToFSSpec(&_reply.selection, &fsspec) != noErr ) { | ||
124 | // Conversion failed? Return empty name | ||
125 | s[0] = 0; | ||
126 | return(-1); | ||
127 | } | ||
128 | FSSpecToPath(fsspec, s, slen); | ||
129 | return(0); | ||
130 | } | ||
131 | |||
132 | // RETURN MULTIPLE DIRECTORIES | ||
133 | // Returns: 0 on success with pathnames[] containing pathnames selected, | ||
134 | // -1 on error | ||
135 | // | ||
136 | int Fl_Native_File_Chooser::NavReply::get_pathnames(char **&pathnames, | ||
137 | int& tpathnames) { | ||
138 | // How many items selected? | ||
139 | long count = 0; | ||
140 | if ( AECountItems(&_reply.selection, &count) != noErr ) | ||
141 | { return(-1); } | ||
142 | |||
143 | // Allocate space for that many pathnames | ||
144 | pathnames = new char*[count]; | ||
145 | memset((void*)pathnames, 0, count*sizeof(char*)); | ||
146 | tpathnames = count; | ||
147 | |||
148 | // Walk list of pathnames selected | ||
149 | for (short index=1; index<=count; index++) { | ||
150 | AEKeyword keyWord; | ||
151 | AEDesc desc; | ||
152 | if (AEGetNthDesc(&_reply.selection, index, typeFSS, &keyWord, | ||
153 | &desc) != noErr) { | ||
154 | pathnames[index-1] = strnew(""); | ||
155 | continue; | ||
156 | } | ||
157 | FSSpec fsspec; | ||
158 | if (AEGetDescData(&desc, &fsspec, sizeof(FSSpec)) != noErr ) { | ||
159 | pathnames[index-1] = strnew(""); | ||
160 | continue; | ||
161 | } | ||
162 | char s[4096]; | ||
163 | FSSpecToPath(fsspec, s, sizeof(s)-1); | ||
164 | pathnames[index-1] = strnew(s); | ||
165 | AEDisposeDesc(&desc); | ||
166 | } | ||
167 | return(0); | ||
168 | } | ||
169 | |||
170 | // FREE PATHNAMES ARRAY, IF IT HAS ANY CONTENTS | ||
171 | void Fl_Native_File_Chooser::clear_pathnames() { | ||
172 | if ( _pathnames ) { | ||
173 | while ( --_tpathnames >= 0 ) { | ||
174 | _pathnames[_tpathnames] = strfree(_pathnames[_tpathnames]); | ||
175 | } | ||
176 | delete [] _pathnames; | ||
177 | _pathnames = NULL; | ||
178 | } | ||
179 | _tpathnames = 0; | ||
180 | } | ||
181 | |||
182 | // SET A SINGLE PATHNAME | ||
183 | void Fl_Native_File_Chooser::set_single_pathname(const char *s) { | ||
184 | clear_pathnames(); | ||
185 | _pathnames = new char*[1]; | ||
186 | _pathnames[0] = strnew(s); | ||
187 | _tpathnames = 1; | ||
188 | } | ||
189 | |||
190 | // GET THE 'Save As' FILENAME | ||
191 | // Returns -1 on error, errmsg() has reason, filename == "". | ||
192 | // 0 if OK, filename() has filename chosen. | ||
193 | // | ||
194 | int Fl_Native_File_Chooser::get_saveas_basename(NavDialogRef& ref) { | ||
195 | if ( ref == NULL ) { | ||
196 | errmsg("get_saveas_basename: ref is NULL"); | ||
197 | return(-1); | ||
198 | } | ||
199 | NavReply reply; | ||
200 | OSStatus err; | ||
201 | if ((err = reply.get_reply(ref)) != noErr ) { | ||
202 | errmsg("NavReply::get_reply() failed"); | ||
203 | clear_pathnames(); | ||
204 | return(-1); | ||
205 | } | ||
206 | |||
207 | char pathname[4096] = ""; | ||
208 | // Directory name.. | ||
209 | // -2 leaves room to append '/' | ||
210 | // | ||
211 | if ( reply.get_dirname(pathname, sizeof(pathname)-2) < 0 ) { | ||
212 | clear_pathnames(); | ||
213 | errmsg("NavReply::get_dirname() failed"); | ||
214 | return(-1); | ||
215 | } | ||
216 | // Append '/' | ||
217 | int len = strlen(pathname); | ||
218 | pathname[len++] = '/'; | ||
219 | pathname[len] = '\0'; | ||
220 | // Basename.. | ||
221 | if ( reply.get_saveas_basename(pathname+len, sizeof(pathname)-len) < 0 ) { | ||
222 | clear_pathnames(); | ||
223 | errmsg("NavReply::get_saveas_basename() failed"); | ||
224 | return(-1); | ||
225 | } | ||
226 | set_single_pathname(pathname); | ||
227 | return(0); | ||
228 | } | ||
229 | |||
230 | // GET (POTENTIALLY) MULTIPLE FILENAMES | ||
231 | // Returns: | ||
232 | // -1 -- error, errmsg() has reason, filename == "" | ||
233 | // 0 -- OK, pathnames()/filename() has pathname(s) chosen | ||
234 | // | ||
235 | int Fl_Native_File_Chooser::get_pathnames(NavDialogRef& ref) { | ||
236 | if ( ref == NULL ) { | ||
237 | errmsg("get_saveas_basename: ref is NULL"); | ||
238 | return(-1); | ||
239 | } | ||
240 | NavReply reply; | ||
241 | OSStatus err; | ||
242 | if ((err = reply.get_reply(ref)) != noErr ) { | ||
243 | errmsg("NavReply::get_reply() failed"); | ||
244 | clear_pathnames(); | ||
245 | return(-1); | ||
246 | } | ||
247 | // First, clear pathnames array of any previous contents | ||
248 | clear_pathnames(); | ||
249 | if ( reply.get_pathnames(_pathnames, _tpathnames) < 0 ) { | ||
250 | clear_pathnames(); | ||
251 | errmsg("NavReply::get_dirname() failed"); | ||
252 | return(-1); | ||
253 | } | ||
254 | return(0); | ||
255 | } | ||
256 | |||
257 | // NAV CALLBACK EVENT HANDLER | ||
258 | void Fl_Native_File_Chooser::event_handler( | ||
259 | NavEventCallbackMessage callBackSelector, | ||
260 | NavCBRecPtr cbparm, | ||
261 | void *data) { | ||
262 | OSStatus err; | ||
263 | Fl_Native_File_Chooser *nfb = (Fl_Native_File_Chooser*)data; | ||
264 | switch (callBackSelector) { | ||
265 | case kNavCBStart: | ||
266 | if ( nfb->directory() || nfb->preset_file() ) { | ||
267 | const char *pathname = nfb->directory() ? nfb->directory() : nfb->preset_file(); | ||
268 | FSSpec spec; | ||
269 | if ( ( err = PathToFSSpec(pathname, spec) ) != noErr ) { | ||
270 | fprintf(stderr, "PathToFSSpec(%s) failed: err=%d\n", | ||
271 | pathname, (int)err); | ||
272 | break; | ||
273 | } | ||
274 | AEDesc desc; | ||
275 | if ((err = AECreateDesc(typeFSS, | ||
276 | &spec, sizeof(FSSpec), &desc)) != noErr) { | ||
277 | fprintf(stderr, "AECreateDesc() failed: err=%d\n", | ||
278 | (int)err); | ||
279 | } | ||
280 | if ((err = NavCustomControl(cbparm->context, | ||
281 | kNavCtlSetLocation, &desc)) != noErr) { | ||
282 | fprintf(stderr, "NavCustomControl() failed: err=%d\n", | ||
283 | (int)err); | ||
284 | } | ||
285 | AEDisposeDesc(&desc); | ||
286 | } | ||
287 | if ( nfb->_btype == BROWSE_SAVE_FILE && nfb->preset_file() ) { | ||
288 | CFStringRef namestr = | ||
289 | CFStringCreateWithCString(NULL, | ||
290 | nfb->preset_file(), | ||
291 | kCFStringEncodingASCII); | ||
292 | NavDialogSetSaveFileName(cbparm->context, namestr); | ||
293 | CFRelease(namestr); | ||
294 | } | ||
295 | NavCustomControl(cbparm->context, kNavCtlSetActionState, | ||
296 | &nfb->_keepstate ); | ||
297 | |||
298 | // Select the right filter in pop-up menu | ||
299 | if ( nfb->_filt_value == nfb->_filt_total ) { | ||
300 | // Select All Documents | ||
301 | NavPopupMenuItem kAll = kNavAllFiles; | ||
302 | NavCustomControl(cbparm->context, kNavCtlSelectAllType, &kAll); | ||
303 | } else if (nfb->_filt_value < nfb->_filt_total) { | ||
304 | // Select custom filter | ||
305 | nfb->_tempitem.version = kNavMenuItemSpecVersion; | ||
306 | nfb->_tempitem.menuCreator = 'extn'; | ||
307 | nfb->_tempitem.menuType = nfb->_filt_value; | ||
308 | *nfb->_tempitem.menuItemName = '\0'; // needed on 10.3+ | ||
309 | NavCustomControl(cbparm->context, | ||
310 | kNavCtlSelectCustomType, | ||
311 | &(nfb->_tempitem)); | ||
312 | } | ||
313 | break; | ||
314 | |||
315 | case kNavCBPopupMenuSelect: | ||
316 | NavMenuItemSpecPtr ptr; | ||
317 | // they really buried this one! | ||
318 | ptr = (NavMenuItemSpecPtr)cbparm->eventData.eventDataParms.param; | ||
319 | if ( ptr->menuCreator ) { | ||
320 | // Gets index to filter ( menuCreator = 'extn' ) | ||
321 | nfb->_filt_value = ptr->menuType; | ||
322 | } else { | ||
323 | // All docs filter selected ( menuCreator = '\0\0\0\0' ) | ||
324 | nfb->_filt_value = nfb->_filt_total; | ||
325 | } | ||
326 | break; | ||
327 | |||
328 | case kNavCBSelectEntry: | ||
329 | NavActionState astate; | ||
330 | switch ( nfb->_btype ) { | ||
331 | // these don't need selection override | ||
332 | case BROWSE_MULTI_FILE: | ||
333 | case BROWSE_MULTI_DIRECTORY: | ||
334 | case BROWSE_SAVE_FILE: | ||
335 | break; | ||
336 | |||
337 | // These need to allow only one item, so disable | ||
338 | // Open button if user tries to select multiple files | ||
339 | case BROWSE_SAVE_DIRECTORY: | ||
340 | case BROWSE_DIRECTORY: | ||
341 | case BROWSE_FILE: | ||
342 | SInt32 selectcount; | ||
343 | AECountItems((AEDescList*)cbparm-> | ||
344 | eventData.eventDataParms.param, | ||
345 | &selectcount); | ||
346 | if ( selectcount > 1 ) { | ||
347 | NavCustomControl(cbparm->context, | ||
348 | kNavCtlSetSelection, | ||
349 | NULL); | ||
350 | astate = nfb->_keepstate | | ||
351 | kNavDontOpenState | | ||
352 | kNavDontChooseState; | ||
353 | NavCustomControl(cbparm->context, | ||
354 | kNavCtlSetActionState, | ||
355 | &astate ); | ||
356 | } | ||
357 | else { | ||
358 | astate= nfb->_keepstate | kNavNormalState; | ||
359 | NavCustomControl(cbparm->context, | ||
360 | kNavCtlSetActionState, | ||
361 | &astate ); | ||
362 | } | ||
363 | break; | ||
364 | } | ||
365 | break; | ||
366 | } | ||
367 | } | ||
368 | |||
369 | // CONSTRUCTOR | ||
370 | Fl_Native_File_Chooser::Fl_Native_File_Chooser(int val) { | ||
371 | _btype = val; | ||
372 | NavGetDefaultDialogCreationOptions(&_opts); | ||
373 | _opts.optionFlags |= kNavDontConfirmReplacement; // no confirms for "save as" | ||
374 | _options = NO_OPTIONS; | ||
375 | _ref = NULL; | ||
376 | memset(&_tempitem, 0, sizeof(_tempitem)); | ||
377 | _pathnames = NULL; | ||
378 | _tpathnames = 0; | ||
379 | _title = NULL; | ||
380 | _filter = NULL; | ||
381 | _filt_names = NULL; | ||
382 | memset(_filt_patt, 0, sizeof(char*) * MAXFILTERS); | ||
383 | _filt_total = 0; | ||
384 | _filt_value = 0; | ||
385 | _directory = NULL; | ||
386 | _preset_file = NULL; | ||
387 | _errmsg = NULL; | ||
388 | _keepstate = kNavNormalState; | ||
389 | } | ||
390 | |||
391 | // DESTRUCTOR | ||
392 | Fl_Native_File_Chooser::~Fl_Native_File_Chooser() { | ||
393 | // _opts // nothing to manage | ||
394 | if (_ref) { NavDialogDispose(_ref); _ref = NULL; } | ||
395 | // _options // nothing to manage | ||
396 | // _keepstate // nothing to manage | ||
397 | // _tempitem // nothing to manage | ||
398 | clear_pathnames(); | ||
399 | _directory = strfree(_directory); | ||
400 | _title = strfree(_title); | ||
401 | _preset_file = strfree(_preset_file); | ||
402 | _filter = strfree(_filter); | ||
403 | //_filt_names // managed by clear_filters() | ||
404 | //_filt_patt[i] // managed by clear_filters() | ||
405 | //_filt_total // managed by clear_filters() | ||
406 | clear_filters(); | ||
407 | //_filt_value // nothing to manage | ||
408 | _errmsg = strfree(_errmsg); | ||
409 | } | ||
410 | |||
411 | // SET THE TYPE OF BROWSER | ||
412 | void Fl_Native_File_Chooser::type(int val) { | ||
413 | _btype = val; | ||
414 | } | ||
415 | |||
416 | // GET TYPE OF BROWSER | ||
417 | int Fl_Native_File_Chooser::type() const { | ||
418 | return(_btype); | ||
419 | } | ||
420 | |||
421 | // SET OPTIONS | ||
422 | void Fl_Native_File_Chooser::options(int val) { | ||
423 | _options = val; | ||
424 | } | ||
425 | |||
426 | // GET OPTIONS | ||
427 | int Fl_Native_File_Chooser::options() const { | ||
428 | return(_options); | ||
429 | } | ||
430 | |||
431 | // SHOW THE BROWSER WINDOW | ||
432 | // Returns: | ||
433 | // 0 - user picked a file | ||
434 | // 1 - user cancelled | ||
435 | // -1 - failed; errmsg() has reason | ||
436 | // | ||
437 | int Fl_Native_File_Chooser::show() { | ||
438 | // Make sure fltk interface updates before posting our dialog | ||
439 | Fl::flush(); | ||
440 | |||
441 | // BROWSER TITLE | ||
442 | CFStringRef cfs_title; | ||
443 | cfs_title = CFStringCreateWithCString(NULL, | ||
444 | _title ? _title : "No Title", | ||
445 | kCFStringEncodingASCII); | ||
446 | _opts.windowTitle = cfs_title; | ||
447 | |||
448 | _keepstate = kNavNormalState; | ||
449 | |||
450 | // BROWSER FILTERS | ||
451 | CFArrayRef filter_array = NULL; | ||
452 | { | ||
453 | // One or more filters specified? | ||
454 | if ( _filt_total ) { | ||
455 | // NAMES -> CFArrayRef | ||
456 | CFStringRef tab = CFSTR("\t"); | ||
457 | CFStringRef tmp_cfs; | ||
458 | tmp_cfs = CFStringCreateWithCString(NULL, _filt_names, | ||
459 | kCFStringEncodingASCII); | ||
460 | filter_array = CFStringCreateArrayBySeparatingStrings( | ||
461 | NULL, tmp_cfs, tab); | ||
462 | CFRelease(tmp_cfs); | ||
463 | CFRelease(tab); | ||
464 | _opts.popupExtension = filter_array; | ||
465 | _opts.optionFlags |= kNavAllFilesInPopup; | ||
466 | } else { | ||
467 | filter_array = NULL; | ||
468 | _opts.popupExtension = NULL; | ||
469 | _opts.optionFlags |= kNavAllFilesInPopup; | ||
470 | } | ||
471 | } | ||
472 | |||
473 | // HANDLE OPTIONS WE SUPPORT | ||
474 | if ( _options & SAVEAS_CONFIRM ) { | ||
475 | _opts.optionFlags &= ~kNavDontConfirmReplacement; // enables confirm | ||
476 | } else { | ||
477 | _opts.optionFlags |= kNavDontConfirmReplacement; // disables confirm | ||
478 | } | ||
479 | |||
480 | // POST BROWSER | ||
481 | int err = post(); | ||
482 | |||
483 | // RELEASE _FILT_ARR | ||
484 | if ( filter_array ) CFRelease(filter_array); | ||
485 | filter_array = NULL; | ||
486 | _opts.popupExtension = NULL; | ||
487 | _filt_total = 0; | ||
488 | |||
489 | // RELEASE TITLE | ||
490 | if ( cfs_title ) CFRelease(cfs_title); | ||
491 | cfs_title = NULL; | ||
492 | |||
493 | return(err); | ||
494 | } | ||
495 | |||
496 | // POST BROWSER | ||
497 | // Internal use only. | ||
498 | // Assumes '_opts' has been initialized. | ||
499 | // | ||
500 | // Returns: | ||
501 | // 0 - user picked a file | ||
502 | // 1 - user cancelled | ||
503 | // -1 - failed; errmsg() has reason | ||
504 | // | ||
505 | int Fl_Native_File_Chooser::post() { | ||
506 | |||
507 | // INITIALIZE BROWSER | ||
508 | OSStatus err; | ||
509 | if ( _filt_total == 0 ) { // Make sure they match | ||
510 | _filt_value = 0; // TBD: move to someplace more logical? | ||
511 | } | ||
512 | |||
513 | if ( ! ( _options & NEW_FOLDER ) ) { | ||
514 | _keepstate |= kNavDontNewFolderState; | ||
515 | } | ||
516 | |||
517 | switch (_btype) { | ||
518 | case BROWSE_FILE: | ||
519 | case BROWSE_MULTI_FILE: | ||
520 | // Prompt user for one or more files | ||
521 | if ((err = NavCreateGetFileDialog( | ||
522 | &_opts, // options | ||
523 | 0, // file types | ||
524 | event_handler, // event handler | ||
525 | 0, // preview callback | ||
526 | filter_proc_cb, // filter callback | ||
527 | (void*)this, // callback data | ||
528 | &_ref)) != noErr ) { // dialog ref | ||
529 | errmsg("NavCreateGetFileDialog: failed"); | ||
530 | return(-1); | ||
531 | } | ||
532 | break; | ||
533 | |||
534 | case BROWSE_DIRECTORY: | ||
535 | case BROWSE_MULTI_DIRECTORY: | ||
536 | case BROWSE_SAVE_DIRECTORY: | ||
537 | // Prompts user for one or more files or folders | ||
538 | if ((err = NavCreateChooseFolderDialog( | ||
539 | &_opts, // options | ||
540 | event_handler, // event callback | ||
541 | 0, // filter callback | ||
542 | (void*)this, // callback data | ||
543 | &_ref)) != noErr ) { // dialog ref | ||
544 | errmsg("NavCreateChooseFolderDialog: failed"); | ||
545 | return(-1); | ||
546 | } | ||
547 | break; | ||
548 | |||
549 | case BROWSE_SAVE_FILE: | ||
550 | // Prompt user for filename to 'save as' | ||
551 | if ((err = NavCreatePutFileDialog( | ||
552 | &_opts, // options | ||
553 | 0, // file types | ||
554 | 0, // file creator | ||
555 | event_handler, // event handler | ||
556 | (void*)this, // callback data | ||
557 | &_ref)) != noErr ) { // dialog ref | ||
558 | errmsg("NavCreatePutFileDialog: failed"); | ||
559 | return(-1); | ||
560 | } | ||
561 | break; | ||
562 | } | ||
563 | |||
564 | // SHOW THE DIALOG | ||
565 | if ( ( err = NavDialogRun(_ref) ) != 0 ) { | ||
566 | char msg[80]; | ||
567 | sprintf(msg, "NavDialogRun: failed (err=%d)", (int)err); | ||
568 | errmsg(msg); | ||
569 | return(-1); | ||
570 | } | ||
571 | |||
572 | // WHAT ACTION DID USER CHOOSE? | ||
573 | NavUserAction act = NavDialogGetUserAction(_ref); | ||
574 | if ( act == kNavUserActionNone ) { | ||
575 | errmsg("Nothing happened yet (dialog still open)"); | ||
576 | return(-1); | ||
577 | } | ||
578 | else if ( act == kNavUserActionCancel ) { // user chose 'cancel' | ||
579 | return(1); | ||
580 | } | ||
581 | else if ( act == kNavUserActionSaveAs ) { // user chose 'save as' | ||
582 | return(get_saveas_basename(_ref)); | ||
583 | } | ||
584 | |||
585 | // TOO MANY FILES CHOSEN? | ||
586 | int ret = get_pathnames(_ref); | ||
587 | if ( _btype == BROWSE_FILE && ret == 0 && _tpathnames != 1 ) { | ||
588 | char msg[80]; | ||
589 | sprintf(msg, "Expected only one file to be chosen.. you chose %d.", | ||
590 | (int)_tpathnames); | ||
591 | errmsg(msg); | ||
592 | return(-1); | ||
593 | } | ||
594 | return(err); | ||
595 | } | ||
596 | |||
597 | // SET ERROR MESSAGE | ||
598 | // Internal use only. | ||
599 | // | ||
600 | void Fl_Native_File_Chooser::errmsg(const char *msg) { | ||
601 | _errmsg = strfree(_errmsg); | ||
602 | _errmsg = strnew(msg); | ||
603 | } | ||
604 | |||
605 | // RETURN ERROR MESSAGE | ||
606 | const char *Fl_Native_File_Chooser::errmsg() const { | ||
607 | return(_errmsg ? _errmsg : "No error"); | ||
608 | } | ||
609 | |||
610 | // GET FILENAME | ||
611 | const char* Fl_Native_File_Chooser::filename() const { | ||
612 | if ( _pathnames && _tpathnames > 0 ) return(_pathnames[0]); | ||
613 | return(""); | ||
614 | } | ||
615 | |||
616 | // GET FILENAME FROM LIST OF FILENAMES | ||
617 | const char* Fl_Native_File_Chooser::filename(int i) const { | ||
618 | if ( _pathnames && i < _tpathnames ) return(_pathnames[i]); | ||
619 | return(""); | ||
620 | } | ||
621 | |||
622 | // GET TOTAL FILENAMES CHOSEN | ||
623 | int Fl_Native_File_Chooser::count() const { | ||
624 | return(_tpathnames); | ||
625 | } | ||
626 | |||
627 | // PRESET PATHNAME | ||
628 | // Value can be NULL for none. | ||
629 | // | ||
630 | void Fl_Native_File_Chooser::directory(const char *val) { | ||
631 | _directory = strfree(_directory); | ||
632 | _directory = strnew(val); | ||
633 | } | ||
634 | |||
635 | // GET PRESET PATHNAME | ||
636 | // Returned value can be NULL if none set. | ||
637 | // | ||
638 | const char* Fl_Native_File_Chooser::directory() const { | ||
639 | return(_directory); | ||
640 | } | ||
641 | |||
642 | // SET TITLE | ||
643 | // Value can be NULL if no title desired. | ||
644 | // | ||
645 | void Fl_Native_File_Chooser::title(const char *val) { | ||
646 | _title = strfree(_title); | ||
647 | _title = strnew(val); | ||
648 | } | ||
649 | |||
650 | // GET TITLE | ||
651 | // Returned value can be NULL if none set. | ||
652 | // | ||
653 | const char *Fl_Native_File_Chooser::title() const { | ||
654 | return(_title); | ||
655 | } | ||
656 | |||
657 | // SET FILTER | ||
658 | // Can be NULL if no filter needed | ||
659 | // | ||
660 | void Fl_Native_File_Chooser::filter(const char *val) { | ||
661 | _filter = strfree(_filter); | ||
662 | _filter = strnew(val); | ||
663 | |||
664 | // Parse filter user specified | ||
665 | // IN: _filter = "C Files\t*.{cxx,h}\nText Files\t*.txt" | ||
666 | // OUT: _filt_names = "C Files\tText Files" | ||
667 | // _filt_patt[0] = "*.{cxx,h}" | ||
668 | // _filt_patt[1] = "*.txt" | ||
669 | // _filt_total = 2 | ||
670 | // | ||
671 | parse_filter(_filter); | ||
672 | } | ||
673 | |||
674 | // GET FILTER | ||
675 | // Returned value can be NULL if none set. | ||
676 | // | ||
677 | const char *Fl_Native_File_Chooser::filter() const { | ||
678 | return(_filter); | ||
679 | } | ||
680 | |||
681 | // CLEAR ALL FILTERS | ||
682 | // Internal use only. | ||
683 | // | ||
684 | void Fl_Native_File_Chooser::clear_filters() { | ||
685 | _filt_names = strfree(_filt_names); | ||
686 | for (int i=0; i<_filt_total; i++) { | ||
687 | _filt_patt[i] = strfree(_filt_patt[i]); | ||
688 | } | ||
689 | _filt_total = 0; | ||
690 | } | ||
691 | |||
692 | // PARSE USER'S FILTER SPEC | ||
693 | // Parses user specified filter ('in'), | ||
694 | // breaks out into _filt_patt[], _filt_names, and _filt_total. | ||
695 | // | ||
696 | // Handles: | ||
697 | // IN: OUT:_filt_names OUT: _filt_patt | ||
698 | // ------------------------------------ ------------------ --------------- | ||
699 | // "*.{ma,mb}" "*.{ma,mb} Files" "*.{ma,mb}" | ||
700 | // "*.[abc]" "*.[abc] Files" "*.[abc]" | ||
701 | // "*.txt" "*.txt Files" "*.c" | ||
702 | // "C Files\t*.[ch]" "C Files" "*.[ch]" | ||
703 | // "C Files\t*.[ch]\nText Files\t*.cxx" "C Files" "*.[ch]" | ||
704 | // | ||
705 | // Parsing Mode: | ||
706 | // IN:"C Files\t*.{cxx,h}" | ||
707 | // ||||||| ||||||||| | ||
708 | // mode: nnnnnnn wwwwwwwww | ||
709 | // \_____/ \_______/ | ||
710 | // Name Wildcard | ||
711 | // | ||
712 | void Fl_Native_File_Chooser::parse_filter(const char *in) { | ||
713 | clear_filters(); | ||
714 | if ( ! in ) return; | ||
715 | int has_name = strchr(in, '\t') ? 1 : 0; | ||
716 | |||
717 | char mode = has_name ? 'n' : 'w'; // parse mode: n=title, w=wildcard | ||
718 | char wildcard[1024] = ""; // parsed wildcard | ||
719 | char name[1024] = ""; | ||
720 | |||
721 | // Parse filter user specified | ||
722 | for ( ; 1; in++ ) { | ||
723 | |||
724 | //// DEBUG | ||
725 | //// printf("WORKING ON '%c': mode=<%c> name=<%s> wildcard=<%s>\n", | ||
726 | //// *in, mode, name, wildcard); | ||
727 | |||
728 | switch (*in) { | ||
729 | // FINISHED PARSING NAME? | ||
730 | case '\t': | ||
731 | if ( mode != 'n' ) goto regchar; | ||
732 | mode = 'w'; | ||
733 | break; | ||
734 | |||
735 | // ESCAPE NEXT CHAR | ||
736 | case '\\': | ||
737 | ++in; | ||
738 | goto regchar; | ||
739 | |||
740 | // FINISHED PARSING ONE OF POSSIBLY SEVERAL FILTERS? | ||
741 | case '\r': | ||
742 | case '\n': | ||
743 | case '\0': | ||
744 | // TITLE | ||
745 | // If user didn't specify a name, make one | ||
746 | // | ||
747 | if ( name[0] == '\0' ) { | ||
748 | sprintf(name, "%.*s Files", (int)sizeof(name)-10, wildcard); | ||
749 | } | ||
750 | // APPEND NEW FILTER TO LIST | ||
751 | if ( wildcard[0] ) { | ||
752 | // Add to filtername list | ||
753 | // Tab delimit if more than one. We later break | ||
754 | // tab delimited string into CFArray with | ||
755 | // CFStringCreateArrayBySeparatingStrings() | ||
756 | // | ||
757 | if ( _filt_total ) { | ||
758 | _filt_names = strapp(_filt_names, "\t"); | ||
759 | } | ||
760 | _filt_names = strapp(_filt_names, name); | ||
761 | |||
762 | // Add filter to the pattern array | ||
763 | _filt_patt[_filt_total++] = strnew(wildcard); | ||
764 | } | ||
765 | // RESET | ||
766 | wildcard[0] = name[0] = '\0'; | ||
767 | mode = strchr(in, '\t') ? 'n' : 'w'; | ||
768 | // DONE? | ||
769 | if ( *in == '\0' ) return; // done | ||
770 | else continue; // not done yet, more filters | ||
771 | |||
772 | // Parse all other chars | ||
773 | default: // handle all non-special chars | ||
774 | regchar: // handle regular char | ||
775 | switch ( mode ) { | ||
776 | case 'n': chrcat(name, *in); continue; | ||
777 | case 'w': chrcat(wildcard, *in); continue; | ||
778 | } | ||
779 | break; | ||
780 | } | ||
781 | } | ||
782 | //NOTREACHED | ||
783 | } | ||
784 | |||
785 | // STATIC: FILTER CALLBACK | ||
786 | Boolean Fl_Native_File_Chooser::filter_proc_cb(AEDesc *theItem, | ||
787 | void *info, | ||
788 | void *callBackUD, | ||
789 | NavFilterModes filterMode) { | ||
790 | return((Fl_Native_File_Chooser*)callBackUD)->filter_proc_cb2( | ||
791 | theItem, info, callBackUD, filterMode); | ||
792 | } | ||
793 | |||
794 | // FILTER CALLBACK | ||
795 | // Return true if match, | ||
796 | // false if no match. | ||
797 | // | ||
798 | Boolean Fl_Native_File_Chooser::filter_proc_cb2(AEDesc *theItem, | ||
799 | void *info, | ||
800 | void *callBackUD, | ||
801 | NavFilterModes filterMode) { | ||
802 | // All files chosen or no filters | ||
803 | if ( _filt_value == _filt_total ) return(true); | ||
804 | |||
805 | FSSpec fsspec; | ||
806 | char pathname[4096]; | ||
807 | |||
808 | // On fail, filter should return true by default | ||
809 | if ( AEDescToFSSpec(theItem, &fsspec) != noErr ) { | ||
810 | return(true); | ||
811 | } | ||
812 | FSSpecToPath(fsspec, pathname, sizeof(pathname)-1); | ||
813 | |||
814 | if ( fl_filename_isdir(pathname) ) return(true); | ||
815 | if ( fl_filename_match(pathname, _filt_patt[_filt_value]) ) return(true); | ||
816 | else return(false); | ||
817 | } | ||
818 | |||
819 | // SET PRESET FILE | ||
820 | // Value can be NULL for none. | ||
821 | // | ||
822 | void Fl_Native_File_Chooser::preset_file(const char* val) { | ||
823 | _preset_file = strfree(_preset_file); | ||
824 | _preset_file = strnew(val); | ||
825 | } | ||
826 | |||
827 | // PRESET FILE | ||
828 | // Returned value can be NULL if none set. | ||
829 | // | ||
830 | const char* Fl_Native_File_Chooser::preset_file() { | ||
831 | return(_preset_file); | ||
832 | } | ||
833 | |||