OSX: populate file dialog system & bookmark defaults paths with the finder's ones
[blender.git] / source / blender / editors / space_file / fsmenu.c
1 /**
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
21  * All rights reserved.
22  *
23  * The Original Code is: all of this file.
24  *
25  * Contributor(s): Andrea Weikert (c) 2008 Blender Foundation.
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdio.h>
33 #include <math.h>
34
35 #include "MEM_guardedalloc.h"
36
37 #include "DNA_space_types.h" /* FILE_MAX */
38
39 #include "BLI_blenlib.h"
40 #include "BLI_linklist.h"
41 #include "BLI_dynstr.h"
42 #include "BLI_string.h"
43
44 #ifdef WIN32
45 #include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
46 #ifndef _WIN32_IE
47 #define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
48 #endif
49 #include <shlobj.h> /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff because 'near' is disabled through BLI_windstuff */
50 #include "BLI_winstuff.h"
51 #endif
52
53 #ifdef __APPLE__
54 /* XXX BIG WARNING: carbon.h can not be included in blender code, it conflicts with struct ID */
55 #define ID ID_
56 #include <CoreServices/CoreServices.h>
57
58 #include "BKE_utildefines.h"
59 #endif
60
61 #ifdef __linux__
62 #include <mntent.h>
63 #endif
64
65 #include "fsmenu.h"  /* include ourselves */
66
67
68 /* FSMENU HANDLING */
69
70         /* FSMenuEntry's without paths indicate seperators */
71 typedef struct _FSMenuEntry FSMenuEntry;
72 struct _FSMenuEntry {
73         FSMenuEntry *next;
74
75         char *path;
76         short save;
77 };
78
79 typedef struct FSMenu
80 {
81         FSMenuEntry *fsmenu_system;
82         FSMenuEntry *fsmenu_bookmarks;
83         FSMenuEntry *fsmenu_recent;
84
85 } FSMenu;
86
87 static FSMenu *g_fsmenu = NULL;
88
89 struct FSMenu* fsmenu_get(void)
90 {
91         if (!g_fsmenu) {
92                 g_fsmenu=MEM_callocN(sizeof(struct FSMenu), "fsmenu");
93         }
94         return g_fsmenu;
95 }
96
97 static FSMenuEntry *fsmenu_get_category(struct FSMenu* fsmenu, FSMenuCategory category)
98 {
99         FSMenuEntry *fsms = NULL;
100
101         switch(category) {
102                 case FS_CATEGORY_SYSTEM:
103                         fsms = fsmenu->fsmenu_system;
104                         break;
105                 case FS_CATEGORY_BOOKMARKS:
106                         fsms = fsmenu->fsmenu_bookmarks;
107                         break;
108                 case FS_CATEGORY_RECENT:
109                         fsms = fsmenu->fsmenu_recent;
110                         break;
111         }
112         return fsms;
113 }
114
115 static void fsmenu_set_category(struct FSMenu* fsmenu, FSMenuCategory category, FSMenuEntry *fsms)
116 {
117         switch(category) {
118                 case FS_CATEGORY_SYSTEM:
119                         fsmenu->fsmenu_system = fsms;
120                         break;
121                 case FS_CATEGORY_BOOKMARKS:
122                         fsmenu->fsmenu_bookmarks = fsms;
123                         break;
124                 case FS_CATEGORY_RECENT:
125                         fsmenu->fsmenu_recent = fsms;
126                         break;
127         }
128 }
129
130 int fsmenu_get_nentries(struct FSMenu* fsmenu, FSMenuCategory category)
131 {
132         FSMenuEntry *fsme;
133         int count= 0;
134
135         for (fsme= fsmenu_get_category(fsmenu, category); fsme; fsme= fsme->next) 
136                 count++;
137
138         return count;
139 }
140
141 char *fsmenu_get_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
142 {
143         FSMenuEntry *fsme;
144
145         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
146                 idx--;
147
148         return fsme?fsme->path:NULL;
149 }
150
151 short fsmenu_can_save (struct FSMenu* fsmenu, FSMenuCategory category, int idx)
152 {
153         FSMenuEntry *fsme;
154
155         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
156                 idx--;
157
158         return fsme?fsme->save:0;
159 }
160
161 void fsmenu_insert_entry(struct FSMenu* fsmenu, FSMenuCategory category, char *path, int sorted, short save)
162 {
163         FSMenuEntry *prev;
164         FSMenuEntry *fsme;
165         FSMenuEntry *fsms;
166
167         fsms = fsmenu_get_category(fsmenu, category);
168         prev= fsme= fsms;
169
170         for (; fsme; prev= fsme, fsme= fsme->next) {
171                 if (fsme->path) {
172                         if (BLI_streq(path, fsme->path)) {
173                                 return;
174                         } else if (sorted && strcmp(path, fsme->path)<0) {
175                                 break;
176                         }
177                 } else {
178                         // if we're bookmarking this, file should come 
179                         // before the last separator, only automatically added
180                         // current dir go after the last sep.
181                         if (save) {
182                                 break;
183                         }
184                 }
185         }
186         
187         fsme= MEM_mallocN(sizeof(*fsme), "fsme");
188         fsme->path= BLI_strdup(path);
189         fsme->save = save;
190
191         if (prev) {
192                 fsme->next= prev->next;
193                 prev->next= fsme;
194         } else {
195                 fsme->next= fsms;
196                 fsmenu_set_category(fsmenu, category, fsme);
197         }
198 }
199
200 void fsmenu_remove_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
201 {
202         FSMenuEntry *prev= NULL, *fsme= NULL;
203         FSMenuEntry *fsms = fsmenu_get_category(fsmenu, category);
204
205         for (fsme= fsms; fsme && idx; prev= fsme, fsme= fsme->next)             
206                 idx--;
207
208         if (fsme) {
209                 /* you should only be able to remove entries that were 
210                    not added by default, like windows drives.
211                    also separators (where path == NULL) shouldn't be removed */
212                 if (fsme->save && fsme->path) {
213
214                         /* remove fsme from list */
215                         if (prev) {
216                                 prev->next= fsme->next;
217                         } else {
218                                 fsms= fsme->next;
219                                 fsmenu_set_category(fsmenu, category, fsms);
220                         }
221                         /* free entry */
222                         MEM_freeN(fsme->path);
223                         MEM_freeN(fsme);
224                 }
225         }
226 }
227
228 void fsmenu_write_file(struct FSMenu* fsmenu, const char *filename)
229 {
230         FSMenuEntry *fsme= NULL;
231         int count=FSMENU_RECENT_MAX;
232
233         FILE *fp = fopen(filename, "w");
234         if (!fp) return;
235         
236         fprintf(fp, "[Bookmarks]\n");
237         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsme; fsme= fsme->next) {
238                 if (fsme->path && fsme->save) {
239                         fprintf(fp, "%s\n", fsme->path);
240                 }
241         }
242         fprintf(fp, "[Recent]\n");
243         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsme && count; fsme= fsme->next, --count) {
244                 if (fsme->path && fsme->save) {
245                         fprintf(fp, "%s\n", fsme->path);
246                 }
247         }
248         fclose(fp);
249 }
250
251 void fsmenu_read_file(struct FSMenu* fsmenu, const char *filename)
252 {
253         char line[256];
254         FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
255         FILE *fp;
256
257 #ifdef WIN32
258         /* Add the drive names to the listing */
259         {
260                 __int64 tmp;
261                 char folder[256];
262                 char tmps[4];
263                 int i;
264                         
265                 tmp= GetLogicalDrives();
266                 
267                 for (i=2; i < 26; i++) {
268                         if ((tmp>>i) & 1) {
269                                 tmps[0]='a'+i;
270                                 tmps[1]=':';
271                                 tmps[2]='\\';
272                                 tmps[3]=0;
273                                 
274                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, 1, 0);
275                         }
276                 }
277
278                 /* Adding Desktop and My Documents */
279                 SHGetSpecialFolderPath(0, folder, CSIDL_PERSONAL, 0);
280                 fsmenu_insert_entry(fsmenu,FS_CATEGORY_BOOKMARKS, folder, 1, 0);
281                 SHGetSpecialFolderPath(0, folder, CSIDL_DESKTOPDIRECTORY, 0);
282                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, folder, 1, 0);
283         }
284 #else
285 #ifdef __APPLE__
286         {
287 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
288                 OSErr err=noErr;
289                 int i;
290                 char *home;
291                 
292                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
293                 for (i=1; err!=nsvErr; i++)
294                 {
295                         FSRef dir;
296                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
297                         
298                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
299                         if (err != noErr)
300                                 continue;
301                         
302                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
303                         if (strcmp((char*)path, "/home") && strcmp((char*)path, "/net"))
304                         { /* /net and /home are meaningless on OSX, home folders are stored in /Users */
305                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
306                         }
307                 }
308
309                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
310                  assume they are the standard ones 
311                  TODO : replace hardcoded paths with proper BLI_get_folder calls */
312                 home = BLI_gethome();
313                 if(home) {
314                         BLI_snprintf(line, 256, "%s/", home);
315                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
316                         BLI_snprintf(line, 256, "%s/Desktop/", home);
317                         if (BLI_exists(line)) {
318                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
319                         }
320                         BLI_snprintf(line, 256, "%s/Documents/", home);
321                         if (BLI_exists(line)) {
322                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
323                         }
324                         BLI_snprintf(line, 256, "%s/Pictures/", home);
325                         if (BLI_exists(line)) {
326                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
327                         }
328                         BLI_snprintf(line, 256, "%s/Music/", home);
329                         if (BLI_exists(line)) {
330                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
331                         }
332                         BLI_snprintf(line, 256, "%s/Movies/", home);
333                         if (BLI_exists(line)) {
334                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
335                         }
336                 }
337 #else
338                 /* 10.5 provides ability to retrieve Finder favorite places */
339                 UInt32 seed;
340                 OSErr err = noErr;
341                 CFArrayRef pathesArray;
342                 LSSharedFileListRef list;
343                 LSSharedFileListItemRef itemRef;
344                 CFIndex i, pathesCount;
345                 CFURLRef cfURL = NULL;
346                 CFStringRef pathString = NULL;
347                 
348                 /* First get mounted volumes */
349                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
350                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
351                 pathesCount = CFArrayGetCount(pathesArray);
352                 
353                 for (i=0; i<pathesCount; i++)
354                 {
355                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
356                         
357                         err = LSSharedFileListItemResolve(itemRef, 
358                                                                                           kLSSharedFileListNoUserInteraction
359                                                                                           | kLSSharedFileListDoNotMountVolumes, 
360                                                                                           &cfURL, NULL);
361                         if (err != noErr)
362                                 continue;
363                         
364                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
365                         
366                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
367                                 continue;
368                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
369                         
370                         CFRelease(pathString);
371                         CFRelease(cfURL);
372                 }
373                 
374                 CFRelease(pathesArray);
375                 CFRelease(list);
376                 
377                 /* Then get user favorite places */
378                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
379                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
380                 pathesCount = CFArrayGetCount(pathesArray);
381                 
382                 for (i=0; i<pathesCount; i++)
383                 {
384                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
385                         
386                         err = LSSharedFileListItemResolve(itemRef, 
387                                                                                           kLSSharedFileListNoUserInteraction
388                                                                                           | kLSSharedFileListDoNotMountVolumes, 
389                                                                                           &cfURL, NULL);
390                         if (err != noErr)
391                                 continue;
392                         
393                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
394                         
395                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
396                                 continue;
397                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
398                         
399                         CFRelease(pathString);
400                         CFRelease(cfURL);
401                 }
402                 
403                 CFRelease(pathesArray);
404                 CFRelease(list);
405 #endif /* OSX 10.5+ */
406         }
407 #else
408         /* unix */
409         {
410                 char dir[FILE_MAXDIR];
411                 char *home= BLI_gethome();
412
413                 if(home) {
414                         BLI_snprintf(dir, FILE_MAXDIR, "%s/", home);
415                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, dir, 1, 0);
416                         BLI_snprintf(dir, FILE_MAXDIR, "%s/Desktop/", home);
417                         if (BLI_exists(dir)) {
418                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, dir, 1, 0);
419                         }
420                 }
421
422                 {
423                         int found= 0;
424 #ifdef __linux__
425                         /* loop over mount points */
426                         struct mntent *mnt;
427                         FILE *fp;
428                         int len;
429
430                         fp = setmntent (MOUNTED, "r");
431                         if (fp == NULL) {
432                                 fprintf(stderr, "could not get a list of mounted filesystemts\n");
433                         }
434                         else {
435                                 while ((mnt = getmntent (fp))) {
436                                         /* not sure if this is right, but seems to give the relevant mnts */
437                                         if(strncmp(mnt->mnt_fsname, "/dev", 4))
438                                                 continue;
439
440                                         len= strlen(mnt->mnt_dir);
441                                         if(len && mnt->mnt_dir[len-1] != '/') {
442                                                 BLI_snprintf(dir, FILE_MAXDIR, "%s/", mnt->mnt_dir);
443                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, dir, 1, 0);
444                                         }
445                                         else
446                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, 1, 0);
447
448                                         found= 1;
449                                 }
450                                 if (endmntent (fp) == 0) {
451                                         fprintf(stderr, "could not close the list of mounted filesystemts\n");
452                                 }
453                         }
454 #endif
455
456                         /* fallback */
457                         if(!found)
458                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", 1, 0);
459                 }
460         }
461 #endif
462 #endif
463
464         fp = fopen(filename, "r");
465         if (!fp) return;
466
467         while ( fgets ( line, 256, fp ) != NULL ) /* read a line */
468         {
469                 if (strncmp(line, "[Bookmarks]", 11)==0){
470                         category = FS_CATEGORY_BOOKMARKS;
471                 } else if (strncmp(line, "[Recent]", 8)==0){
472                         category = FS_CATEGORY_RECENT;
473                 } else {
474                         int len = strlen(line);
475                         if (len>0) {
476                                 if (line[len-1] == '\n') {
477                                         line[len-1] = '\0';
478                                 }
479                                 if (BLI_exist(line)) {
480                                         fsmenu_insert_entry(fsmenu, category, line, 0, 1);
481                                 }
482                         }
483                 }
484         }
485         fclose(fp);
486 }
487
488 static void fsmenu_free_category(struct FSMenu* fsmenu, FSMenuCategory category)
489 {
490         FSMenuEntry *fsme= fsmenu_get_category(fsmenu, category);
491
492         while (fsme) {
493                 FSMenuEntry *n= fsme->next;
494
495                 if (fsme->path) MEM_freeN(fsme->path);
496                 MEM_freeN(fsme);
497
498                 fsme= n;
499         }
500 }
501
502 void fsmenu_free(struct FSMenu* fsmenu)
503 {
504         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
505         fsmenu_free_category(fsmenu, FS_CATEGORY_BOOKMARKS);
506         fsmenu_free_category(fsmenu, FS_CATEGORY_RECENT);
507         MEM_freeN(fsmenu);
508 }
509