ffe1677a6a9add201100ab2e04ed333247036167
[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 nskip= 0;
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         nskip = fsmenu_get_nentries(fsmenu, FS_CATEGORY_RECENT) - FSMENU_RECENT_MAX;
248         // skip first entries if list too long
249         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsme && (nskip>0); fsme= fsme->next, --nskip)
250                 ;
251         for (; fsme; fsme= fsme->next) {
252                 if (fsme->path && fsme->save) {
253                         fprintf(fp, "%s\n", fsme->path);
254                 }
255         }
256         fclose(fp);
257 }
258
259 void fsmenu_read_bookmarks(struct FSMenu* fsmenu, const char *filename)
260 {
261         char line[256];
262         FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
263         FILE *fp;
264
265         fp = fopen(filename, "r");
266         if (!fp) return;
267
268         while ( fgets ( line, 256, fp ) != NULL ) /* read a line */
269         {
270                 if (strncmp(line, "[Bookmarks]", 11)==0){
271                         category = FS_CATEGORY_BOOKMARKS;
272                 } else if (strncmp(line, "[Recent]", 8)==0){
273                         category = FS_CATEGORY_RECENT;
274                 } else {
275                         int len = strlen(line);
276                         if (len>0) {
277                                 if (line[len-1] == '\n') {
278                                         line[len-1] = '\0';
279                                 }
280                                 if (BLI_exist(line)) {
281                                         fsmenu_insert_entry(fsmenu, category, line, 0, 1);
282                                 }
283                         }
284                 }
285         }
286         fclose(fp);
287 }
288
289 void fsmenu_read_system(struct FSMenu* fsmenu)
290 {
291         char line[256];
292 #ifdef WIN32
293         /* Add the drive names to the listing */
294         {
295                 __int64 tmp;
296                 char tmps[4];
297                 int i;
298                         
299                 tmp= GetLogicalDrives();
300                 
301                 for (i=2; i < 26; i++) {
302                         if ((tmp>>i) & 1) {
303                                 tmps[0]='A'+i;
304                                 tmps[1]=':';
305                                 tmps[2]='\\';
306                                 tmps[3]=0;
307                                 
308                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, 1, 0);
309                         }
310                 }
311
312                 /* Adding Desktop and My Documents */
313                 SHGetSpecialFolderPath(0, line, CSIDL_PERSONAL, 0);
314                 fsmenu_insert_entry(fsmenu,FS_CATEGORY_BOOKMARKS, line, 1, 0);
315                 SHGetSpecialFolderPath(0, line, CSIDL_DESKTOPDIRECTORY, 0);
316                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
317         }
318 #else
319 #ifdef __APPLE__
320         {
321 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
322                 OSErr err=noErr;
323                 int i;
324                 const char *home;
325                 
326                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
327                 for (i=1; err!=nsvErr; i++)
328                 {
329                         FSRef dir;
330                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
331                         
332                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
333                         if (err != noErr)
334                                 continue;
335                         
336                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
337                         if (strcmp((char*)path, "/home") && strcmp((char*)path, "/net"))
338                         { /* /net and /home are meaningless on OSX, home folders are stored in /Users */
339                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
340                         }
341                 }
342
343                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
344                  assume they are the standard ones 
345                  TODO : replace hardcoded paths with proper BLI_get_folder calls */
346                 home = getenv("HOME");
347                 if(home) {
348                         BLI_snprintf(line, 256, "%s/", home);
349                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
350                         BLI_snprintf(line, 256, "%s/Desktop/", home);
351                         if (BLI_exists(line)) {
352                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
353                         }
354                         BLI_snprintf(line, 256, "%s/Documents/", home);
355                         if (BLI_exists(line)) {
356                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
357                         }
358                         BLI_snprintf(line, 256, "%s/Pictures/", home);
359                         if (BLI_exists(line)) {
360                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
361                         }
362                         BLI_snprintf(line, 256, "%s/Music/", home);
363                         if (BLI_exists(line)) {
364                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
365                         }
366                         BLI_snprintf(line, 256, "%s/Movies/", home);
367                         if (BLI_exists(line)) {
368                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
369                         }
370                 }
371 #else
372                 /* 10.5 provides ability to retrieve Finder favorite places */
373                 UInt32 seed;
374                 OSErr err = noErr;
375                 CFArrayRef pathesArray;
376                 LSSharedFileListRef list;
377                 LSSharedFileListItemRef itemRef;
378                 CFIndex i, pathesCount;
379                 CFURLRef cfURL = NULL;
380                 CFStringRef pathString = NULL;
381                 
382                 /* First get local mounted volumes */
383                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
384                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
385                 pathesCount = CFArrayGetCount(pathesArray);
386                 
387                 for (i=0; i<pathesCount; i++)
388                 {
389                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
390                         
391                         err = LSSharedFileListItemResolve(itemRef, 
392                                                                                           kLSSharedFileListNoUserInteraction
393                                                                                           | kLSSharedFileListDoNotMountVolumes, 
394                                                                                           &cfURL, NULL);
395                         if (err != noErr)
396                                 continue;
397                         
398                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
399                         
400                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
401                                 continue;
402                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
403                         
404                         CFRelease(pathString);
405                         CFRelease(cfURL);
406                 }
407                 
408                 CFRelease(pathesArray);
409                 CFRelease(list);
410                 
411                 /* Then get network volumes */
412                 err = noErr;
413                 for (i=1; err!=nsvErr; i++)
414                 {
415                         FSRef dir;
416                         FSVolumeRefNum volRefNum;
417                         struct GetVolParmsInfoBuffer volParmsBuffer;
418                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
419                         
420                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, &volRefNum, kFSVolInfoNone, NULL, NULL, &dir);
421                         if (err != noErr)
422                                 continue;
423                         
424                         err = FSGetVolumeParms(volRefNum, &volParmsBuffer, sizeof(volParmsBuffer));
425                         if ((err != noErr) || (volParmsBuffer.vMServerAdr == 0)) /* Exclude local devices */
426                                 continue;
427                         
428                         
429                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
430                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
431                 }
432                 
433                 /* Finally get user favorite places */
434                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
435                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
436                 pathesCount = CFArrayGetCount(pathesArray);
437                 
438                 for (i=0; i<pathesCount; i++)
439                 {
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