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