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