doxygen: prevent GPL license block from being parsed as doxygen comment.
[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, const 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 #ifdef WIN32
284         /* Add the drive names to the listing */
285         {
286                 __int64 tmp;
287                 char tmps[4];
288                 int i;
289                         
290                 tmp= GetLogicalDrives();
291                 
292                 for (i=2; i < 26; i++) {
293                         if ((tmp>>i) & 1) {
294                                 tmps[0]='A'+i;
295                                 tmps[1]=':';
296                                 tmps[2]='\\';
297                                 tmps[3]=0;
298                                 
299                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, 1, 0);
300                         }
301                 }
302
303                 /* Adding Desktop and My Documents */
304                 SHGetSpecialFolderPath(0, line, CSIDL_PERSONAL, 0);
305                 fsmenu_insert_entry(fsmenu,FS_CATEGORY_BOOKMARKS, line, 1, 0);
306                 SHGetSpecialFolderPath(0, line, CSIDL_DESKTOPDIRECTORY, 0);
307                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
308         }
309 #else
310 #ifdef __APPLE__
311         {
312 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
313                 OSErr err=noErr;
314                 int i;
315                 char *home;
316                 
317                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
318                 for (i=1; err!=nsvErr; i++)
319                 {
320                         FSRef dir;
321                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
322                         
323                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
324                         if (err != noErr)
325                                 continue;
326                         
327                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
328                         if (strcmp((char*)path, "/home") && strcmp((char*)path, "/net"))
329                         { /* /net and /home are meaningless on OSX, home folders are stored in /Users */
330                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
331                         }
332                 }
333
334                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
335                  assume they are the standard ones 
336                  TODO : replace hardcoded paths with proper BLI_get_folder calls */
337                 home = getenv("HOME");
338                 if(home) {
339                         BLI_snprintf(line, 256, "%s/", home);
340                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
341                         BLI_snprintf(line, 256, "%s/Desktop/", home);
342                         if (BLI_exists(line)) {
343                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
344                         }
345                         BLI_snprintf(line, 256, "%s/Documents/", home);
346                         if (BLI_exists(line)) {
347                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
348                         }
349                         BLI_snprintf(line, 256, "%s/Pictures/", home);
350                         if (BLI_exists(line)) {
351                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
352                         }
353                         BLI_snprintf(line, 256, "%s/Music/", home);
354                         if (BLI_exists(line)) {
355                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
356                         }
357                         BLI_snprintf(line, 256, "%s/Movies/", home);
358                         if (BLI_exists(line)) {
359                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
360                         }
361                 }
362 #else
363                 /* 10.5 provides ability to retrieve Finder favorite places */
364                 UInt32 seed;
365                 OSErr err = noErr;
366                 CFArrayRef pathesArray;
367                 LSSharedFileListRef list;
368                 LSSharedFileListItemRef itemRef;
369                 CFIndex i, pathesCount;
370                 CFURLRef cfURL = NULL;
371                 CFStringRef pathString = NULL;
372                 
373                 /* First get local mounted volumes */
374                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
375                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
376                 pathesCount = CFArrayGetCount(pathesArray);
377                 
378                 for (i=0; i<pathesCount; i++)
379                 {
380                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
381                         
382                         err = LSSharedFileListItemResolve(itemRef, 
383                                                                                           kLSSharedFileListNoUserInteraction
384                                                                                           | kLSSharedFileListDoNotMountVolumes, 
385                                                                                           &cfURL, NULL);
386                         if (err != noErr)
387                                 continue;
388                         
389                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
390                         
391                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
392                                 continue;
393                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
394                         
395                         CFRelease(pathString);
396                         CFRelease(cfURL);
397                 }
398                 
399                 CFRelease(pathesArray);
400                 CFRelease(list);
401                 
402                 /* Then get network volumes */
403                 err = noErr;
404                 for (i=1; err!=nsvErr; i++)
405                 {
406                         FSRef dir;
407                         FSVolumeRefNum volRefNum;
408                         struct GetVolParmsInfoBuffer volParmsBuffer;
409                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
410                         
411                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, &volRefNum, kFSVolInfoNone, NULL, NULL, &dir);
412                         if (err != noErr)
413                                 continue;
414                         
415                         err = FSGetVolumeParms(volRefNum, &volParmsBuffer, sizeof(volParmsBuffer));
416                         if ((err != noErr) || (volParmsBuffer.vMServerAdr == 0)) /* Exclude local devices */
417                                 continue;
418                         
419                         
420                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
421                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
422                 }
423                 
424                 /* Finally get user favorite places */
425                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
426                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
427                 pathesCount = CFArrayGetCount(pathesArray);
428                 
429                 for (i=0; i<pathesCount; i++)
430                 {
431                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
432                         
433                         err = LSSharedFileListItemResolve(itemRef, 
434                                                                                           kLSSharedFileListNoUserInteraction
435                                                                                           | kLSSharedFileListDoNotMountVolumes, 
436                                                                                           &cfURL, NULL);
437                         if (err != noErr)
438                                 continue;
439                         
440                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
441                         
442                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
443                                 continue;
444                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
445                         
446                         CFRelease(pathString);
447                         CFRelease(cfURL);
448                 }
449                 
450                 CFRelease(pathesArray);
451                 CFRelease(list);
452 #endif /* OSX 10.5+ */
453         }
454 #else
455         /* unix */
456         {
457                 char *home= getenv("HOME");
458
459                 if(home) {
460                         BLI_snprintf(line, FILE_MAXDIR, "%s/", home);
461                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
462                         BLI_snprintf(line, FILE_MAXDIR, "%s/Desktop/", home);
463                         if (BLI_exists(line)) {
464                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
465                         }
466                 }
467
468                 {
469                         int found= 0;
470 #ifdef __linux__
471                         /* loop over mount points */
472                         struct mntent *mnt;
473                         int len;
474                         FILE *fp;
475
476                         fp = setmntent (MOUNTED, "r");
477                         if (fp == NULL) {
478                                 fprintf(stderr, "could not get a list of mounted filesystemts\n");
479                         }
480                         else {
481                                 while ((mnt = getmntent (fp))) {
482                                         /* not sure if this is right, but seems to give the relevant mnts */
483                                         if(strncmp(mnt->mnt_fsname, "/dev", 4))
484                                                 continue;
485
486                                         len= strlen(mnt->mnt_dir);
487                                         if(len && mnt->mnt_dir[len-1] != '/') {
488                                                 BLI_snprintf(line, FILE_MAXDIR, "%s/", mnt->mnt_dir);
489                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
490                                         }
491                                         else
492                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, 1, 0);
493
494                                         found= 1;
495                                 }
496                                 if (endmntent (fp) == 0) {
497                                         fprintf(stderr, "could not close the list of mounted filesystemts\n");
498                                 }
499                         }
500 #endif
501
502                         /* fallback */
503                         if(!found)
504                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", 1, 0);
505                 }
506         }
507 #endif
508 #endif
509 }
510
511
512 static void fsmenu_free_category(struct FSMenu* fsmenu, FSMenuCategory category)
513 {
514         FSMenuEntry *fsme= fsmenu_get_category(fsmenu, category);
515
516         while (fsme) {
517                 FSMenuEntry *n= fsme->next;
518
519                 if (fsme->path) MEM_freeN(fsme->path);
520                 MEM_freeN(fsme);
521
522                 fsme= n;
523         }
524 }
525
526 void fsmenu_free(struct FSMenu* fsmenu)
527 {
528         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
529         fsmenu_free_category(fsmenu, FS_CATEGORY_BOOKMARKS);
530         fsmenu_free_category(fsmenu, FS_CATEGORY_RECENT);
531         MEM_freeN(fsmenu);
532 }
533