8211257da028c71d85714d084032a19128f65357
[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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 #endif
59
60 #ifdef __linux__
61 #include <mntent.h>
62 #endif
63
64 #include "fsmenu.h"  /* include ourselves */
65
66
67 /* FSMENU HANDLING */
68
69         /* FSMenuEntry's without paths indicate seperators */
70 typedef struct _FSMenuEntry FSMenuEntry;
71 struct _FSMenuEntry {
72         FSMenuEntry *next;
73
74         char *path;
75         short save;
76 };
77
78 typedef struct FSMenu
79 {
80         FSMenuEntry *fsmenu_system;
81         FSMenuEntry *fsmenu_bookmarks;
82         FSMenuEntry *fsmenu_recent;
83
84 } FSMenu;
85
86 static FSMenu *g_fsmenu = NULL;
87
88 struct FSMenu* fsmenu_get(void)
89 {
90         if (!g_fsmenu) {
91                 g_fsmenu=MEM_callocN(sizeof(struct FSMenu), "fsmenu");
92         }
93         return g_fsmenu;
94 }
95
96 static FSMenuEntry *fsmenu_get_category(struct FSMenu* fsmenu, FSMenuCategory category)
97 {
98         FSMenuEntry *fsms = NULL;
99
100         switch(category) {
101                 case FS_CATEGORY_SYSTEM:
102                         fsms = fsmenu->fsmenu_system;
103                         break;
104                 case FS_CATEGORY_BOOKMARKS:
105                         fsms = fsmenu->fsmenu_bookmarks;
106                         break;
107                 case FS_CATEGORY_RECENT:
108                         fsms = fsmenu->fsmenu_recent;
109                         break;
110         }
111         return fsms;
112 }
113
114 static void fsmenu_set_category(struct FSMenu* fsmenu, FSMenuCategory category, FSMenuEntry *fsms)
115 {
116         switch(category) {
117                 case FS_CATEGORY_SYSTEM:
118                         fsmenu->fsmenu_system = fsms;
119                         break;
120                 case FS_CATEGORY_BOOKMARKS:
121                         fsmenu->fsmenu_bookmarks = fsms;
122                         break;
123                 case FS_CATEGORY_RECENT:
124                         fsmenu->fsmenu_recent = fsms;
125                         break;
126         }
127 }
128
129 int fsmenu_get_nentries(struct FSMenu* fsmenu, FSMenuCategory category)
130 {
131         FSMenuEntry *fsme;
132         int count= 0;
133
134         for (fsme= fsmenu_get_category(fsmenu, category); fsme; fsme= fsme->next) 
135                 count++;
136
137         return count;
138 }
139
140 char *fsmenu_get_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
141 {
142         FSMenuEntry *fsme;
143
144         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
145                 idx--;
146
147         return fsme?fsme->path:NULL;
148 }
149
150 short fsmenu_can_save (struct FSMenu* fsmenu, FSMenuCategory category, int idx)
151 {
152         FSMenuEntry *fsme;
153
154         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
155                 idx--;
156
157         return fsme?fsme->save:0;
158 }
159
160 void fsmenu_insert_entry(struct FSMenu* fsmenu, FSMenuCategory category, char *path, int sorted, short save)
161 {
162         FSMenuEntry *prev;
163         FSMenuEntry *fsme;
164         FSMenuEntry *fsms;
165
166         fsms = fsmenu_get_category(fsmenu, category);
167         prev= fsme= fsms;
168
169         for (; fsme; prev= fsme, fsme= fsme->next) {
170                 if (fsme->path) {
171                         if (BLI_streq(path, fsme->path)) {
172                                 return;
173                         } else if (sorted && strcmp(path, fsme->path)<0) {
174                                 break;
175                         }
176                 } else {
177                         // if we're bookmarking this, file should come 
178                         // before the last separator, only automatically added
179                         // current dir go after the last sep.
180                         if (save) {
181                                 break;
182                         }
183                 }
184         }
185         
186         fsme= MEM_mallocN(sizeof(*fsme), "fsme");
187         fsme->path= BLI_strdup(path);
188         fsme->save = save;
189
190         if (prev) {
191                 fsme->next= prev->next;
192                 prev->next= fsme;
193         } else {
194                 fsme->next= fsms;
195                 fsmenu_set_category(fsmenu, category, fsme);
196         }
197 }
198
199 void fsmenu_remove_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
200 {
201         FSMenuEntry *prev= NULL, *fsme= NULL;
202         FSMenuEntry *fsms = fsmenu_get_category(fsmenu, category);
203
204         for (fsme= fsms; fsme && idx; prev= fsme, fsme= fsme->next)             
205                 idx--;
206
207         if (fsme) {
208                 /* you should only be able to remove entries that were 
209                    not added by default, like windows drives.
210                    also separators (where path == NULL) shouldn't be removed */
211                 if (fsme->save && fsme->path) {
212
213                         /* remove fsme from list */
214                         if (prev) {
215                                 prev->next= fsme->next;
216                         } else {
217                                 fsms= fsme->next;
218                                 fsmenu_set_category(fsmenu, category, fsms);
219                         }
220                         /* free entry */
221                         MEM_freeN(fsme->path);
222                         MEM_freeN(fsme);
223                 }
224         }
225 }
226
227 void fsmenu_write_file(struct FSMenu* fsmenu, const char *filename)
228 {
229         FSMenuEntry *fsme= NULL;
230         int count=FSMENU_RECENT_MAX;
231
232         FILE *fp = fopen(filename, "w");
233         if (!fp) return;
234         
235         fprintf(fp, "[Bookmarks]\n");
236         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsme; fsme= fsme->next) {
237                 if (fsme->path && fsme->save) {
238                         fprintf(fp, "%s\n", fsme->path);
239                 }
240         }
241         fprintf(fp, "[Recent]\n");
242         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsme && count; fsme= fsme->next, --count) {
243                 if (fsme->path && fsme->save) {
244                         fprintf(fp, "%s\n", fsme->path);
245                 }
246         }
247         fclose(fp);
248 }
249
250 void fsmenu_read_bookmarks(struct FSMenu* fsmenu, const char *filename)
251 {
252         char line[256];
253         FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
254         FILE *fp;
255
256         fp = fopen(filename, "r");
257         if (!fp) return;
258
259         while ( fgets ( line, 256, fp ) != NULL ) /* read a line */
260         {
261                 if (strncmp(line, "[Bookmarks]", 11)==0){
262                         category = FS_CATEGORY_BOOKMARKS;
263                 } else if (strncmp(line, "[Recent]", 8)==0){
264                         category = FS_CATEGORY_RECENT;
265                 } else {
266                         int len = strlen(line);
267                         if (len>0) {
268                                 if (line[len-1] == '\n') {
269                                         line[len-1] = '\0';
270                                 }
271                                 if (BLI_exist(line)) {
272                                         fsmenu_insert_entry(fsmenu, category, line, 0, 1);
273                                 }
274                         }
275                 }
276         }
277         fclose(fp);
278 }
279
280 void fsmenu_read_system(struct FSMenu* fsmenu)
281 {
282         char line[256];
283         FILE *fp;
284
285 #ifdef WIN32
286         /* Add the drive names to the listing */
287         {
288                 __int64 tmp;
289                 char folder[256];
290                 char tmps[4];
291                 int i;
292                         
293                 tmp= GetLogicalDrives();
294                 
295                 for (i=2; i < 26; i++) {
296                         if ((tmp>>i) & 1) {
297                                 tmps[0]='A'+i;
298                                 tmps[1]=':';
299                                 tmps[2]='\\';
300                                 tmps[3]=0;
301                                 
302                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, 1, 0);
303                         }
304                 }
305
306                 /* Adding Desktop and My Documents */
307                 SHGetSpecialFolderPath(0, folder, CSIDL_PERSONAL, 0);
308                 fsmenu_insert_entry(fsmenu,FS_CATEGORY_BOOKMARKS, folder, 1, 0);
309                 SHGetSpecialFolderPath(0, folder, CSIDL_DESKTOPDIRECTORY, 0);
310                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, folder, 1, 0);
311         }
312 #else
313 #ifdef __APPLE__
314         {
315 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
316                 OSErr err=noErr;
317                 int i;
318                 char *home;
319                 
320                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
321                 for (i=1; err!=nsvErr; i++)
322                 {
323                         FSRef dir;
324                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
325                         
326                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
327                         if (err != noErr)
328                                 continue;
329                         
330                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
331                         if (strcmp((char*)path, "/home") && strcmp((char*)path, "/net"))
332                         { /* /net and /home are meaningless on OSX, home folders are stored in /Users */
333                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
334                         }
335                 }
336
337                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
338                  assume they are the standard ones 
339                  TODO : replace hardcoded paths with proper BLI_get_folder calls */
340                 home = getenv("HOME");
341                 if(home) {
342                         BLI_snprintf(line, 256, "%s/", home);
343                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
344                         BLI_snprintf(line, 256, "%s/Desktop/", home);
345                         if (BLI_exists(line)) {
346                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
347                         }
348                         BLI_snprintf(line, 256, "%s/Documents/", home);
349                         if (BLI_exists(line)) {
350                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
351                         }
352                         BLI_snprintf(line, 256, "%s/Pictures/", home);
353                         if (BLI_exists(line)) {
354                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
355                         }
356                         BLI_snprintf(line, 256, "%s/Music/", home);
357                         if (BLI_exists(line)) {
358                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
359                         }
360                         BLI_snprintf(line, 256, "%s/Movies/", home);
361                         if (BLI_exists(line)) {
362                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
363                         }
364                 }
365 #else
366                 /* 10.5 provides ability to retrieve Finder favorite places */
367                 UInt32 seed;
368                 OSErr err = noErr;
369                 CFArrayRef pathesArray;
370                 LSSharedFileListRef list;
371                 LSSharedFileListItemRef itemRef;
372                 CFIndex i, pathesCount;
373                 CFURLRef cfURL = NULL;
374                 CFStringRef pathString = NULL;
375                 
376                 /* First get local mounted volumes */
377                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
378                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
379                 pathesCount = CFArrayGetCount(pathesArray);
380                 
381                 for (i=0; i<pathesCount; i++)
382                 {
383                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
384                         
385                         err = LSSharedFileListItemResolve(itemRef, 
386                                                                                           kLSSharedFileListNoUserInteraction
387                                                                                           | kLSSharedFileListDoNotMountVolumes, 
388                                                                                           &cfURL, NULL);
389                         if (err != noErr)
390                                 continue;
391                         
392                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
393                         
394                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
395                                 continue;
396                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
397                         
398                         CFRelease(pathString);
399                         CFRelease(cfURL);
400                 }
401                 
402                 CFRelease(pathesArray);
403                 CFRelease(list);
404                 
405                 /* Then get network volumes */
406                 err = noErr;
407                 for (i=1; err!=nsvErr; i++)
408                 {
409                         FSRef dir;
410                         FSVolumeRefNum volRefNum;
411                         struct GetVolParmsInfoBuffer volParmsBuffer;
412                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
413                         
414                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, &volRefNum, kFSVolInfoNone, NULL, NULL, &dir);
415                         if (err != noErr)
416                                 continue;
417                         
418                         err = FSGetVolumeParms(volRefNum, &volParmsBuffer, sizeof(volParmsBuffer));
419                         if ((err != noErr) || (volParmsBuffer.vMServerAdr == 0)) /* Exclude local devices */
420                                 continue;
421                         
422                         
423                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
424                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
425                 }
426                 
427                 /* Finally get user favorite places */
428                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
429                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
430                 pathesCount = CFArrayGetCount(pathesArray);
431                 
432                 for (i=0; i<pathesCount; i++)
433                 {
434                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
435                         
436                         err = LSSharedFileListItemResolve(itemRef, 
437                                                                                           kLSSharedFileListNoUserInteraction
438                                                                                           | kLSSharedFileListDoNotMountVolumes, 
439                                                                                           &cfURL, NULL);
440                         if (err != noErr)
441                                 continue;
442                         
443                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
444                         
445                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
446                                 continue;
447                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
448                         
449                         CFRelease(pathString);
450                         CFRelease(cfURL);
451                 }
452                 
453                 CFRelease(pathesArray);
454                 CFRelease(list);
455 #endif /* OSX 10.5+ */
456         }
457 #else
458         /* unix */
459         {
460                 char *home= getenv("HOME");
461
462                 if(home) {
463                         BLI_snprintf(line, FILE_MAXDIR, "%s/", home);
464                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
465                         BLI_snprintf(line, FILE_MAXDIR, "%s/Desktop/", home);
466                         if (BLI_exists(line)) {
467                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
468                         }
469                 }
470
471                 {
472                         int found= 0;
473 #ifdef __linux__
474                         /* loop over mount points */
475                         struct mntent *mnt;
476                         int len;
477
478                         fp = setmntent (MOUNTED, "r");
479                         if (fp == NULL) {
480                                 fprintf(stderr, "could not get a list of mounted filesystemts\n");
481                         }
482                         else {
483                                 while ((mnt = getmntent (fp))) {
484                                         /* not sure if this is right, but seems to give the relevant mnts */
485                                         if(strncmp(mnt->mnt_fsname, "/dev", 4))
486                                                 continue;
487
488                                         len= strlen(mnt->mnt_dir);
489                                         if(len && mnt->mnt_dir[len-1] != '/') {
490                                                 BLI_snprintf(line, FILE_MAXDIR, "%s/", mnt->mnt_dir);
491                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
492                                         }
493                                         else
494                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, 1, 0);
495
496                                         found= 1;
497                                 }
498                                 if (endmntent (fp) == 0) {
499                                         fprintf(stderr, "could not close the list of mounted filesystemts\n");
500                                 }
501                         }
502 #endif
503
504                         /* fallback */
505                         if(!found)
506                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", 1, 0);
507                 }
508         }
509 #endif
510 #endif
511 }
512
513
514 static void fsmenu_free_category(struct FSMenu* fsmenu, FSMenuCategory category)
515 {
516         FSMenuEntry *fsme= fsmenu_get_category(fsmenu, category);
517
518         while (fsme) {
519                 FSMenuEntry *n= fsme->next;
520
521                 if (fsme->path) MEM_freeN(fsme->path);
522                 MEM_freeN(fsme);
523
524                 fsme= n;
525         }
526 }
527
528 void fsmenu_free(struct FSMenu* fsmenu)
529 {
530         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
531         fsmenu_free_category(fsmenu, FS_CATEGORY_BOOKMARKS);
532         fsmenu_free_category(fsmenu, FS_CATEGORY_RECENT);
533         MEM_freeN(fsmenu);
534 }
535