UI: add optional tip callback to uiBut, and use it for per-item tooltips in UIList.
[blender-staging.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 "BLI_utildefines.h"
41 #include "BLI_blenlib.h"
42
43 #ifdef WIN32
44 #  include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
45 #  ifndef _WIN32_IE
46 #    define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
47 #  endif
48 #  include <shlobj.h>  /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff
49                         * because 'near' is disabled through BLI_windstuff */
50 #  include "BLI_winstuff.h"
51 #endif
52
53 #ifdef __APPLE__
54 #include <Carbon/Carbon.h>
55 #endif /* __APPLE__ */
56
57 #ifdef __linux__
58 #include <mntent.h>
59 #endif
60
61 #include "fsmenu.h"  /* include ourselves */
62
63
64 /* FSMENU HANDLING */
65
66 /* FSMenuEntry's without paths indicate seperators */
67 typedef struct _FSMenuEntry FSMenuEntry;
68 struct _FSMenuEntry {
69         FSMenuEntry *next;
70
71         char *path;
72         short save;
73 };
74
75 typedef struct FSMenu {
76         FSMenuEntry *fsmenu_system;
77         FSMenuEntry *fsmenu_system_bookmarks;
78         FSMenuEntry *fsmenu_bookmarks;
79         FSMenuEntry *fsmenu_recent;
80 } FSMenu;
81
82 static FSMenu *g_fsmenu = NULL;
83
84 FSMenu *fsmenu_get(void)
85 {
86         if (!g_fsmenu) {
87                 g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
88         }
89         return g_fsmenu;
90 }
91
92 static FSMenuEntry *fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
93 {
94         FSMenuEntry *fsm_head = NULL;
95
96         switch (category) {
97                 case FS_CATEGORY_SYSTEM:
98                         fsm_head = fsmenu->fsmenu_system;
99                         break;
100                 case FS_CATEGORY_SYSTEM_BOOKMARKS:
101                         fsm_head = fsmenu->fsmenu_system_bookmarks;
102                         break;
103                 case FS_CATEGORY_BOOKMARKS:
104                         fsm_head = fsmenu->fsmenu_bookmarks;
105                         break;
106                 case FS_CATEGORY_RECENT:
107                         fsm_head = fsmenu->fsmenu_recent;
108                         break;
109         }
110         return fsm_head;
111 }
112
113 static void fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
114 {
115         switch (category) {
116                 case FS_CATEGORY_SYSTEM:
117                         fsmenu->fsmenu_system = fsm_head;
118                         break;
119                 case FS_CATEGORY_SYSTEM_BOOKMARKS:
120                         fsmenu->fsmenu_system_bookmarks = fsm_head;
121                         break;
122                 case FS_CATEGORY_BOOKMARKS:
123                         fsmenu->fsmenu_bookmarks = fsm_head;
124                         break;
125                 case FS_CATEGORY_RECENT:
126                         fsmenu->fsmenu_recent = fsm_head;
127                         break;
128         }
129 }
130
131 int fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
132 {
133         FSMenuEntry *fsm_iter;
134         int count = 0;
135
136         for (fsm_iter = fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
137                 count++;
138         }
139
140         return count;
141 }
142
143 char *fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
144 {
145         FSMenuEntry *fsm_iter;
146
147         for (fsm_iter = fsmenu_get_category(fsmenu, category); fsm_iter && idx; fsm_iter = fsm_iter->next) {
148                 idx--;
149         }
150
151         return fsm_iter ? fsm_iter->path : NULL;
152 }
153
154 short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
155 {
156         FSMenuEntry *fsm_iter;
157
158         for (fsm_iter = fsmenu_get_category(fsmenu, category); fsm_iter && idx; fsm_iter = fsm_iter->next) {
159                 idx--;
160         }
161
162         return fsm_iter ? fsm_iter->save : 0;
163 }
164
165 void fsmenu_insert_entry(struct FSMenu *fsmenu, FSMenuCategory category, const char *path, FSMenuInsert flag)
166 {
167         FSMenuEntry *fsm_prev;
168         FSMenuEntry *fsm_iter;
169         FSMenuEntry *fsm_head;
170
171         fsm_head = fsmenu_get_category(fsmenu, category);
172         fsm_prev = fsm_head;  /* this is odd and not really correct? */
173
174         for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
175                 if (fsm_iter->path) {
176                         const int cmp_ret = BLI_path_cmp(path, fsm_iter->path);
177                         if (cmp_ret == 0) {
178                                 if (flag & FS_INSERT_FIRST) {
179                                         if (fsm_iter != fsm_head) {
180                                                 fsm_prev->next = fsm_iter->next;
181                                                 fsm_iter->next = fsm_head;
182                                                 fsmenu_set_category(fsmenu, category, fsm_iter);
183                                         }
184                                 }
185                                 return;
186                         }
187                         else if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
188                                 break;
189                         }
190                 }
191                 else {
192                         /* if we're bookmarking this, file should come
193                          * before the last separator, only automatically added
194                          * current dir go after the last sep. */
195                         if (flag & FS_INSERT_SAVE) {
196                                 break;
197                         }
198                 }
199         }
200
201         fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
202         fsm_iter->path = BLI_strdup(path);
203         fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
204
205         if (fsm_prev) {
206                 if (flag & FS_INSERT_FIRST) {
207                         fsm_iter->next = fsm_head;
208                         fsmenu_set_category(fsmenu, category, fsm_iter);
209                 }
210                 else {
211                         fsm_iter->next = fsm_prev->next;
212                         fsm_prev->next = fsm_iter;
213                 }
214         }
215         else {
216                 fsm_iter->next = fsm_head;
217                 fsmenu_set_category(fsmenu, category, fsm_iter);
218         }
219 }
220
221 void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
222 {
223         FSMenuEntry *fsm_prev = NULL;
224         FSMenuEntry *fsm_iter;
225         FSMenuEntry *fsm_head;
226
227         fsm_head = fsmenu_get_category(fsmenu, category);
228
229         for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next)
230                 idx--;
231
232         if (fsm_iter) {
233                 /* you should only be able to remove entries that were 
234                  * not added by default, like windows drives.
235                  * also separators (where path == NULL) shouldn't be removed */
236                 if (fsm_iter->save && fsm_iter->path) {
237
238                         /* remove fsme from list */
239                         if (fsm_prev) {
240                                 fsm_prev->next = fsm_iter->next;
241                         }
242                         else {
243                                 fsm_head = fsm_iter->next;
244                                 fsmenu_set_category(fsmenu, category, fsm_head);
245                         }
246                         /* free entry */
247                         MEM_freeN(fsm_iter->path);
248                         MEM_freeN(fsm_iter);
249                 }
250         }
251 }
252
253 void fsmenu_write_file(struct FSMenu *fsmenu, const char *filename)
254 {
255         FSMenuEntry *fsm_iter = NULL;
256         int nwritten = 0;
257
258         FILE *fp = BLI_fopen(filename, "w");
259         if (!fp) return;
260         
261         fprintf(fp, "[Bookmarks]\n");
262         for (fsm_iter = fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter; fsm_iter = fsm_iter->next) {
263                 if (fsm_iter->path && fsm_iter->save) {
264                         fprintf(fp, "%s\n", fsm_iter->path);
265                 }
266         }
267         fprintf(fp, "[Recent]\n");
268         for (fsm_iter = fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsm_iter && (nwritten < FSMENU_RECENT_MAX); fsm_iter = fsm_iter->next, ++nwritten) {
269                 if (fsm_iter->path && fsm_iter->save) {
270                         fprintf(fp, "%s\n", fsm_iter->path);
271                 }
272         }
273         fclose(fp);
274 }
275
276 void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filename)
277 {
278         char line[FILE_MAXDIR];
279         FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
280         FILE *fp;
281
282         fp = BLI_fopen(filename, "r");
283         if (!fp) return;
284
285         while (fgets(line, sizeof(line), fp) != NULL) {       /* read a line */
286                 if (STREQLEN(line, "[Bookmarks]", 11)) {
287                         category = FS_CATEGORY_BOOKMARKS;
288                 }
289                 else if (STREQLEN(line, "[Recent]", 8)) {
290                         category = FS_CATEGORY_RECENT;
291                 }
292                 else {
293                         int len = strlen(line);
294                         if (len > 0) {
295                                 if (line[len - 1] == '\n') {
296                                         line[len - 1] = '\0';
297                                 }
298                                 /* don't do this because it can be slow on network drives,
299                                  * having a bookmark from a drive thats ejected or so isn't
300                                  * all _that_ bad */
301 #if 0
302                                 if (BLI_exists(line))
303 #endif
304                                 {
305                                         fsmenu_insert_entry(fsmenu, category, line, FS_INSERT_SAVE);
306                                 }
307                         }
308                 }
309         }
310         fclose(fp);
311 }
312
313 void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
314 {
315         char line[FILE_MAXDIR];
316 #ifdef WIN32
317         /* Add the drive names to the listing */
318         {
319                 __int64 tmp;
320                 char tmps[4];
321                 int i;
322                         
323                 tmp = GetLogicalDrives();
324                 
325                 for (i = 0; i < 26; i++) {
326                         if ((tmp >> i) & 1) {
327                                 tmps[0] = 'A' + i;
328                                 tmps[1] = ':';
329                                 tmps[2] = '\\';
330                                 tmps[3] = 0;
331                                 
332                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, FS_INSERT_SORTED);
333                         }
334                 }
335
336                 /* Adding Desktop and My Documents */
337                 if (read_bookmarks) {
338                         SHGetSpecialFolderPath(0, line, CSIDL_PERSONAL, 0);
339                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
340                         SHGetSpecialFolderPath(0, line, CSIDL_DESKTOPDIRECTORY, 0);
341                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
342                 }
343         }
344 #else
345 #ifdef __APPLE__
346         {
347 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= 1050)
348                 OSErr err = noErr;
349                 int i;
350                 const char *home;
351                 
352                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
353                 for (i = 1; err != nsvErr; i++) {
354                         FSRef dir;
355                         unsigned char path[FILE_MAX];
356                         
357                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
358                         if (err != noErr)
359                                 continue;
360                         
361                         FSRefMakePath(&dir, path, FILE_MAX);
362                         if (!STREQ((char *)path, "/home") && !STREQ((char *)path, "/net")) {
363                                 /* /net and /home are meaningless on OSX, home folders are stored in /Users */
364                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, FS_INSERT_SORTED);
365                         }
366                 }
367
368                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
369                  * assume they are the standard ones 
370                  * TODO : replace hardcoded paths with proper BKE_appdir_folder_id calls */
371                 home = getenv("HOME");
372                 if (read_bookmarks && home) {
373                         BLI_snprintf(line, sizeof(line), "%s/", home);
374                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
375                         BLI_snprintf(line, sizeof(line), "%s/Desktop/", home);
376                         if (BLI_exists(line)) {
377                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
378                         }
379                         BLI_snprintf(line, sizeof(line), "%s/Documents/", home);
380                         if (BLI_exists(line)) {
381                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
382                         }
383                         BLI_snprintf(line, sizeof(line), "%s/Pictures/", home);
384                         if (BLI_exists(line)) {
385                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
386                         }
387                         BLI_snprintf(line, sizeof(line), "%s/Music/", home);
388                         if (BLI_exists(line)) {
389                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
390                         }
391                         BLI_snprintf(line, sizeof(line), "%s/Movies/", home);
392                         if (BLI_exists(line)) {
393                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
394                         }
395                 }
396 #else /* OSX 10.6+ */
397                 /* Get mounted volumes better method OSX 10.6 and higher, see: */
398                 /*https://developer.apple.com/library/mac/#documentation/CoreFOundation/Reference/CFURLRef/Reference/reference.html*/
399                 /* we get all volumes sorted including network and do not relay on user-defined finder visibility, less confusing */
400                 
401                 CFURLRef cfURL = NULL;
402                 CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
403                 CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(NULL, kCFURLEnumeratorSkipInvisibles, NULL);
404                 
405                 while (result != kCFURLEnumeratorEnd) {
406                         unsigned char defPath[FILE_MAX];
407
408                         result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
409                         if (result != kCFURLEnumeratorSuccess)
410                                 continue;
411                         
412                         CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
413                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)defPath, FS_INSERT_SORTED);
414                 }
415                 
416                 CFRelease(volEnum);
417                 
418                 /* Finally get user favorite places */
419                 if (read_bookmarks) {
420                         UInt32 seed;
421                         OSErr err = noErr;
422                         CFArrayRef pathesArray;
423                         LSSharedFileListRef list;
424                         LSSharedFileListItemRef itemRef;
425                         CFIndex i, pathesCount;
426                         CFURLRef cfURL = NULL;
427                         CFStringRef pathString = NULL;
428                         list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
429                         pathesArray = LSSharedFileListCopySnapshot(list, &seed);
430                         pathesCount = CFArrayGetCount(pathesArray);
431                         
432                         for (i = 0; i < pathesCount; i++) {
433                                 itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
434                                 
435                                 err = LSSharedFileListItemResolve(itemRef, 
436                                                                   kLSSharedFileListNoUserInteraction |
437                                                                   kLSSharedFileListDoNotMountVolumes,
438                                                                   &cfURL, NULL);
439                                 if (err != noErr)
440                                         continue;
441                                 
442                                 pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
443                                 
444                                 if (pathString == NULL || !CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingASCII))
445                                         continue;
446
447                                 /* Exclude "all my files" as it makes no sense in blender fileselector */
448                                 /* Exclude "airdrop" if wlan not active as it would show "" ) */
449                                 if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
450                                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_APPEND_LAST);
451                                 }
452                                 
453                                 CFRelease(pathString);
454                                 CFRelease(cfURL);
455                         }
456                         
457                         CFRelease(pathesArray);
458                         CFRelease(list);
459                 }
460 #endif /* OSX 10.6+ */
461         }
462 #else
463         /* unix */
464         {
465                 const char *home = getenv("HOME");
466
467                 if (read_bookmarks && home) {
468                         BLI_snprintf(line, sizeof(line), "%s/", home);
469                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
470                         BLI_snprintf(line, sizeof(line), "%s/Desktop/", home);
471                         if (BLI_exists(line)) {
472                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, FS_INSERT_SORTED);
473                         }
474                 }
475
476                 {
477                         int found = 0;
478 #ifdef __linux__
479                         /* loop over mount points */
480                         struct mntent *mnt;
481                         int len;
482                         FILE *fp;
483
484                         fp = setmntent(MOUNTED, "r");
485                         if (fp == NULL) {
486                                 fprintf(stderr, "could not get a list of mounted filesystemts\n");
487                         }
488                         else {
489                                 while ((mnt = getmntent(fp))) {
490                                         /* not sure if this is right, but seems to give the relevant mnts */
491                                         if (!STREQLEN(mnt->mnt_fsname, "/dev", 4))
492                                                 continue;
493
494                                         len = strlen(mnt->mnt_dir);
495                                         if (len && mnt->mnt_dir[len - 1] != '/') {
496                                                 BLI_snprintf(line, sizeof(line), "%s/", mnt->mnt_dir);
497                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, FS_INSERT_SORTED);
498                                         }
499                                         else {
500                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, FS_INSERT_SORTED);
501                                         }
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, "/", FS_INSERT_SORTED);
514                 }
515         }
516 #endif
517 #endif
518 }
519
520
521 static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
522 {
523         FSMenuEntry *fsm_iter = fsmenu_get_category(fsmenu, category);
524
525         while (fsm_iter) {
526                 FSMenuEntry *fsm_next = fsm_iter->next;
527
528                 if (fsm_iter->path) {
529                         MEM_freeN(fsm_iter->path);
530                 }
531                 MEM_freeN(fsm_iter);
532
533                 fsm_iter = fsm_next;
534         }
535 }
536
537 void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
538 {
539         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
540         fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
541
542         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
543         fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
544
545         /* Add all entries to system category */
546         fsmenu_read_system(fsmenu, true);
547 }
548
549 void fsmenu_free(void)
550 {
551         if (g_fsmenu) {
552                 fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM);
553                 fsmenu_free_category(g_fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
554                 fsmenu_free_category(g_fsmenu, FS_CATEGORY_BOOKMARKS);
555                 fsmenu_free_category(g_fsmenu, FS_CATEGORY_RECENT);
556                 MEM_freeN(g_fsmenu);
557         }
558
559         g_fsmenu = NULL;
560 }
561