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