svn merge ^/trunk/blender -r41226:41227 .
[blender.git] / source / blender / editors / space_file / filelist.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) 2007 Blender Foundation.
19  * All rights reserved.
20  *
21  * The Original Code is: all of this file.
22  *
23  * Contributor(s): none yet.
24  *
25  * ***** END GPL LICENSE BLOCK *****
26  */
27
28 /** \file blender/editors/space_file/filelist.c
29  *  \ingroup spfile
30  */
31
32
33 /* global includes */
34
35 #include <stdlib.h>
36 #include <math.h>
37 #include <string.h>
38
39 #ifndef WIN32
40 #include <unistd.h>
41 #else
42 #include <io.h>
43 #include <direct.h>
44 #endif   
45 #include "MEM_guardedalloc.h"
46
47 #include "BLI_blenlib.h"
48 #include "BLI_linklist.h"
49 #include "BLI_threads.h"
50 #include "BLI_utildefines.h"
51
52 #ifdef WIN32
53 #include "BLI_winstuff.h"
54 #endif
55
56 #include "BKE_context.h"
57 #include "BKE_global.h"
58 #include "BKE_library.h"
59 #include "BKE_icons.h"
60 #include "BKE_main.h"
61 #include "BKE_report.h"
62 #include "BLO_readfile.h"
63 #include "BKE_idcode.h"
64
65 #include "DNA_space_types.h"
66
67 #include "ED_fileselect.h"
68 #include "ED_datafiles.h"
69
70 #include "IMB_imbuf.h"
71 #include "IMB_imbuf_types.h"
72 #include "IMB_thumbs.h"
73
74 #include "PIL_time.h"
75
76 #include "WM_api.h"
77 #include "WM_types.h"
78
79 #include "UI_resources.h"
80
81 #include "filelist.h"
82
83 /* max length of library group name within filesel */
84 #define GROUP_MAX 32
85
86 struct FileList;
87
88 typedef struct FileImage {
89         struct FileImage *next, *prev;
90         char path[FILE_MAX];
91         unsigned int flags;
92         int index;
93         short done;
94         ImBuf *img;
95 } FileImage;
96
97 typedef struct ThumbnailJob {
98         ListBase loadimages;
99         short *stop;
100         short *do_update;
101         struct FileList* filelist;
102         ReportList reports;
103 } ThumbnailJob;
104
105 typedef struct FileList
106 {
107         struct direntry *filelist;
108         int *fidx;
109         int numfiles;
110         int numfiltered;
111         char dir[FILE_MAX];
112         short prv_w;
113         short prv_h;
114         short hide_dot;
115         unsigned int filter;
116         char filter_glob[64];
117         short changed;
118
119         struct BlendHandle *libfiledata;
120         short hide_parent;
121
122         void (*readf)(struct FileList *);
123         int  (*filterf)(struct direntry* file, const char* dir, unsigned int filter, short hide_dot);
124
125 } FileList;
126
127 typedef struct FolderList
128 {
129         struct FolderList *next, *prev;
130         char *foldername;
131 } FolderList;
132
133 #define SPECIAL_IMG_SIZE 48
134 #define SPECIAL_IMG_ROWS 4
135 #define SPECIAL_IMG_COLS 4
136
137 #define SPECIAL_IMG_FOLDER 0
138 #define SPECIAL_IMG_PARENT 1
139 #define SPECIAL_IMG_REFRESH 2
140 #define SPECIAL_IMG_BLENDFILE 3
141 #define SPECIAL_IMG_SOUNDFILE 4
142 #define SPECIAL_IMG_MOVIEFILE 5
143 #define SPECIAL_IMG_PYTHONFILE 6
144 #define SPECIAL_IMG_TEXTFILE 7
145 #define SPECIAL_IMG_FONTFILE 8
146 #define SPECIAL_IMG_UNKNOWNFILE 9
147 #define SPECIAL_IMG_LOADING 10
148 #define SPECIAL_IMG_MAX SPECIAL_IMG_LOADING + 1
149
150 static ImBuf* gSpecialFileImages[SPECIAL_IMG_MAX];
151
152
153 /* ******************* SORT ******************* */
154
155 static int compare_name(const void *a1, const void *a2)
156 {
157         const struct direntry *entry1=a1, *entry2=a2;
158
159         /* type is equal to stat.st_mode */
160
161         if (S_ISDIR(entry1->type)){
162                 if (S_ISDIR(entry2->type)==0) return (-1);
163         } else{
164                 if (S_ISDIR(entry2->type)) return (1);
165         }
166         if (S_ISREG(entry1->type)){
167                 if (S_ISREG(entry2->type)==0) return (-1);
168         } else{
169                 if (S_ISREG(entry2->type)) return (1);
170         }
171         if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return (-1);
172         if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return (1);
173         
174         /* make sure "." and ".." are always first */
175         if( strcmp(entry1->relname, ".")==0 ) return (-1);
176         if( strcmp(entry2->relname, ".")==0 ) return (1);
177         if( strcmp(entry1->relname, "..")==0 ) return (-1);
178         if( strcmp(entry2->relname, "..")==0 ) return (1);
179         
180         return (BLI_natstrcmp(entry1->relname,entry2->relname));
181 }
182
183 static int compare_date(const void *a1, const void *a2) 
184 {
185         const struct direntry *entry1=a1, *entry2=a2;
186         
187         /* type is equal to stat.st_mode */
188
189         if (S_ISDIR(entry1->type)){
190                 if (S_ISDIR(entry2->type)==0) return (-1);
191         } else{
192                 if (S_ISDIR(entry2->type)) return (1);
193         }
194         if (S_ISREG(entry1->type)){
195                 if (S_ISREG(entry2->type)==0) return (-1);
196         } else{
197                 if (S_ISREG(entry2->type)) return (1);
198         }
199         if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return (-1);
200         if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return (1);
201
202         /* make sure "." and ".." are always first */
203         if( strcmp(entry1->relname, ".")==0 ) return (-1);
204         if( strcmp(entry2->relname, ".")==0 ) return (1);
205         if( strcmp(entry1->relname, "..")==0 ) return (-1);
206         if( strcmp(entry2->relname, "..")==0 ) return (1);
207         
208         if ( entry1->s.st_mtime < entry2->s.st_mtime) return 1;
209         if ( entry1->s.st_mtime > entry2->s.st_mtime) return -1;
210         
211         else return BLI_natstrcmp(entry1->relname,entry2->relname);
212 }
213
214 static int compare_size(const void *a1, const void *a2) 
215 {
216         const struct direntry *entry1=a1, *entry2=a2;
217
218         /* type is equal to stat.st_mode */
219
220         if (S_ISDIR(entry1->type)){
221                 if (S_ISDIR(entry2->type)==0) return (-1);
222         } else{
223                 if (S_ISDIR(entry2->type)) return (1);
224         }
225         if (S_ISREG(entry1->type)){
226                 if (S_ISREG(entry2->type)==0) return (-1);
227         } else{
228                 if (S_ISREG(entry2->type)) return (1);
229         }
230         if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return (-1);
231         if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return (1);
232
233         /* make sure "." and ".." are always first */
234         if( strcmp(entry1->relname, ".")==0 ) return (-1);
235         if( strcmp(entry2->relname, ".")==0 ) return (1);
236         if( strcmp(entry1->relname, "..")==0 ) return (-1);
237         if( strcmp(entry2->relname, "..")==0 ) return (1);
238         
239         if ( entry1->s.st_size < entry2->s.st_size) return 1;
240         if ( entry1->s.st_size > entry2->s.st_size) return -1;
241         else return BLI_natstrcmp(entry1->relname,entry2->relname);
242 }
243
244 static int compare_extension(const void *a1, const void *a2)
245 {
246         const struct direntry *entry1=a1, *entry2=a2;
247         const char *sufix1, *sufix2;
248         const char *nil="";
249
250         if (!(sufix1= strstr (entry1->relname, ".blend.gz"))) 
251                 sufix1= strrchr (entry1->relname, '.');
252         if (!(sufix2= strstr (entry2->relname, ".blend.gz")))
253                 sufix2= strrchr (entry2->relname, '.');
254         if (!sufix1) sufix1= nil;
255         if (!sufix2) sufix2= nil;
256
257         /* type is equal to stat.st_mode */
258
259         if (S_ISDIR(entry1->type)){
260                 if (S_ISDIR(entry2->type)==0) return (-1);
261         } else{
262                 if (S_ISDIR(entry2->type)) return (1);
263         }
264         if (S_ISREG(entry1->type)){
265                 if (S_ISREG(entry2->type)==0) return (-1);
266         } else{
267                 if (S_ISREG(entry2->type)) return (1);
268         }
269         if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return (-1);
270         if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return (1);
271         
272         /* make sure "." and ".." are always first */
273         if( strcmp(entry1->relname, ".")==0 ) return (-1);
274         if( strcmp(entry2->relname, ".")==0 ) return (1);
275         if( strcmp(entry1->relname, "..")==0 ) return (-1);
276         if( strcmp(entry2->relname, "..")==0 ) return (1);
277         
278         return (BLI_strcasecmp(sufix1, sufix2));
279 }
280
281 static int is_hidden_file(const char* filename, short hide_dot)
282 {
283         int is_hidden=0;
284
285         if (hide_dot) {
286                 if(filename[0]=='.' && filename[1]!='.' && filename[1]!=0) {
287                         is_hidden=1; /* ignore .file */
288                 } else if (((filename[0] == '.') && (filename[1] == 0) )) {
289                         is_hidden=1; /* ignore . */
290                 } else {
291                         int len=strlen(filename);
292                         if( (len>0) && (filename[len-1]=='~') ) {
293                                 is_hidden=1;  /* ignore file~ */
294                         }
295                 } 
296         } else {
297                 if (((filename[0] == '.') && (filename[1] == 0) )) {
298                         is_hidden=1; /* ignore . */
299                 }
300         }
301         return is_hidden;
302 }
303
304 static int is_filtered_file(struct direntry* file, const char* UNUSED(dir), unsigned int filter, short hide_dot)
305 {
306         int is_filtered=0;
307         if (filter) {
308                 if (file->flags & filter) {
309                         is_filtered=1;
310                 } else if (file->type & S_IFDIR) {
311                         if (filter & FOLDERFILE) {
312                                 is_filtered = 1;
313                         }
314                 }
315         } else {
316                 is_filtered = 1;
317         }
318         return is_filtered && !is_hidden_file(file->relname, hide_dot);
319 }
320
321 static int is_filtered_lib(struct direntry* file, const char* dir, unsigned int filter, short hide_dot)
322 {
323         int is_filtered=0;
324         char tdir[FILE_MAX], tgroup[GROUP_MAX];
325         if (BLO_is_a_library(dir, tdir, tgroup)) {
326                 is_filtered = !is_hidden_file(file->relname, hide_dot);
327         } else {
328                 is_filtered = is_filtered_file(file, dir, filter, hide_dot);
329         }
330         return is_filtered;
331 }
332
333 static int is_filtered_main(struct direntry* file, const char* UNUSED(dir), unsigned int UNUSED(filter), short hide_dot)
334 {
335         return !is_hidden_file(file->relname, hide_dot);
336 }
337
338 void filelist_filter(FileList* filelist)
339 {
340         int num_filtered = 0;
341         int i, j;
342         
343         if (!filelist->filelist)
344                 return;
345
346         // How many files are left after filter ?
347         for (i = 0; i < filelist->numfiles; ++i) {
348                 struct direntry *file = &filelist->filelist[i];
349                 if ( filelist->filterf(file, filelist->dir, filelist->filter, filelist->hide_dot) ) {
350                         num_filtered++;
351                 } 
352         }
353         
354         if (filelist->fidx) {
355                 MEM_freeN(filelist->fidx);
356                 filelist->fidx = NULL;
357         }
358         filelist->fidx = (int *)MEM_callocN(num_filtered*sizeof(int), "filteridx");
359         filelist->numfiltered = num_filtered;
360
361         for (i = 0, j=0; i < filelist->numfiles; ++i) {
362                 struct direntry *file = &filelist->filelist[i];
363                 if ( filelist->filterf(file, filelist->dir, filelist->filter, filelist->hide_dot) ) {
364                         filelist->fidx[j++] = i;
365                 }
366         }
367 }
368
369 void filelist_init_icons(void)
370 {
371         short x, y, k;
372         ImBuf *bbuf;
373         ImBuf *ibuf;
374 #ifdef WITH_HEADLESS
375         bbuf = NULL;
376 #else
377         bbuf = IMB_ibImageFromMemory((unsigned char*)datatoc_prvicons, datatoc_prvicons_size, IB_rect);
378 #endif
379         if (bbuf) {
380                 for (y=0; y<SPECIAL_IMG_ROWS; y++) {
381                         for (x=0; x<SPECIAL_IMG_COLS; x++) {
382                                 int tile = SPECIAL_IMG_COLS*y + x; 
383                                 if (tile < SPECIAL_IMG_MAX) {
384                                         ibuf = IMB_allocImBuf(SPECIAL_IMG_SIZE, SPECIAL_IMG_SIZE, 32, IB_rect);
385                                         for (k=0; k<SPECIAL_IMG_SIZE; k++) {
386                                                 memcpy(&ibuf->rect[k*SPECIAL_IMG_SIZE], &bbuf->rect[(k+y*SPECIAL_IMG_SIZE)*SPECIAL_IMG_SIZE*SPECIAL_IMG_COLS+x*SPECIAL_IMG_SIZE], SPECIAL_IMG_SIZE*sizeof(int));
387                                         }
388                                         gSpecialFileImages[tile] = ibuf;
389                                 }
390                         }
391                 }
392                 IMB_freeImBuf(bbuf);
393         }
394 }
395
396 void filelist_free_icons(void)
397 {
398         int i;
399         for (i=0; i < SPECIAL_IMG_MAX; ++i) {
400                 IMB_freeImBuf(gSpecialFileImages[i]);
401                 gSpecialFileImages[i] = NULL;
402         }
403 }
404
405 //-----------------FOLDERLIST (previous/next) --------------//
406 struct ListBase* folderlist_new(void)
407 {
408         ListBase* p = MEM_callocN( sizeof(ListBase), "folderlist" );
409         return p;
410 }
411
412 void folderlist_popdir(struct ListBase* folderlist, char *dir)
413 {
414         const char *prev_dir;
415         struct FolderList *folder;
416         folder = folderlist->last;
417
418         if(folder){
419                 // remove the current directory
420                 MEM_freeN(folder->foldername);
421                 BLI_freelinkN(folderlist, folder);
422
423                 folder = folderlist->last;
424                 if(folder){
425                         prev_dir = folder->foldername;
426                         BLI_strncpy(dir, prev_dir, FILE_MAXDIR);
427                 }
428         }
429         // delete the folder next or use setdir directly before PREVIOUS OP
430 }
431
432 void folderlist_pushdir(ListBase* folderlist, const char *dir)
433 {
434         struct FolderList *folder, *previous_folder;
435         previous_folder = folderlist->last;
436
437         // check if already exists
438         if(previous_folder && previous_folder->foldername){
439                 if(BLI_path_cmp(previous_folder->foldername, dir)==0){
440                         return;
441                 }
442         }
443
444         // create next folder element
445         folder = (FolderList*)MEM_mallocN(sizeof(FolderList),"FolderList");
446         folder->foldername = (char*)MEM_mallocN(sizeof(char)*(strlen(dir)+1), "foldername");
447         folder->foldername[0] = '\0';
448
449         BLI_strncpy(folder->foldername, dir, FILE_MAXDIR);
450
451         // add it to the end of the list
452         BLI_addtail(folderlist, folder);
453 }
454
455 int folderlist_clear_next(struct SpaceFile *sfile)
456 {
457         struct FolderList *folder;
458
459         // if there is no folder_next there is nothing we can clear
460         if (!sfile->folders_next)
461                 return 0;
462
463         // if previous_folder, next_folder or refresh_folder operators are executed it doesn't clear folder_next
464         folder = sfile->folders_prev->last;
465         if ((!folder) ||(BLI_path_cmp(folder->foldername, sfile->params->dir) == 0))
466                 return 0;
467
468         // eventually clear flist->folders_next
469         return 1;
470 }
471
472 /* not listbase itself */
473 void folderlist_free(ListBase* folderlist)
474 {
475         if (folderlist){
476                 FolderList *folder;
477                 for(folder= folderlist->first; folder; folder= folder->next)
478                         MEM_freeN(folder->foldername);
479                 BLI_freelistN(folderlist);
480         }
481 }
482
483 ListBase *folderlist_duplicate(ListBase* folderlist)
484 {
485         
486         if (folderlist) {
487                 ListBase *folderlistn= MEM_callocN(sizeof(ListBase), "copy folderlist");
488                 FolderList *folder;
489                 
490                 BLI_duplicatelist(folderlistn, folderlist);
491                 
492                 for(folder= folderlistn->first; folder; folder= folder->next) {
493                         folder->foldername= MEM_dupallocN(folder->foldername);
494                 }
495                 return folderlistn;
496         }
497         return NULL;
498 }
499
500
501 static void filelist_read_main(struct FileList* filelist);
502 static void filelist_read_library(struct FileList* filelist);
503 static void filelist_read_dir(struct FileList* filelist);
504
505 //------------------FILELIST------------------------//
506 struct FileList*        filelist_new(short type)
507 {
508         FileList* p = MEM_callocN( sizeof(FileList), "filelist" );
509         switch(type) {
510                 case FILE_MAIN:
511                         p->readf = filelist_read_main;
512                         p->filterf = is_filtered_main;
513                         break;
514                 case FILE_LOADLIB:
515                         p->readf = filelist_read_library;
516                         p->filterf = is_filtered_lib;
517                         break;
518                 default:
519                         p->readf = filelist_read_dir;
520                         p->filterf = is_filtered_file;
521
522         }
523         return p;
524 }
525
526
527 void filelist_free(struct FileList* filelist)
528 {
529         int i;
530
531         if (!filelist) {
532                 printf("Attempting to delete empty filelist.\n");
533                 return;
534         }
535         
536         if (filelist->fidx) {
537                 MEM_freeN(filelist->fidx);
538                 filelist->fidx = NULL;
539         }
540
541         for (i = 0; i < filelist->numfiles; ++i) {
542                 if (filelist->filelist[i].image) {                      
543                         IMB_freeImBuf(filelist->filelist[i].image);
544                 }
545                 filelist->filelist[i].image = NULL;
546                 if (filelist->filelist[i].relname)
547                         MEM_freeN(filelist->filelist[i].relname);
548                 if (filelist->filelist[i].path)
549                         MEM_freeN(filelist->filelist[i].path);
550                 filelist->filelist[i].relname = NULL;
551                 if (filelist->filelist[i].string)
552                         MEM_freeN(filelist->filelist[i].string);
553                 filelist->filelist[i].string = NULL;
554         }
555         
556         filelist->numfiles = 0;
557         free(filelist->filelist);
558         filelist->filelist = NULL;      
559         filelist->filter = 0;
560         filelist->filter_glob[0] = '\0';
561         filelist->numfiltered =0;
562         filelist->hide_dot =0;
563 }
564
565 void filelist_freelib(struct FileList* filelist)
566 {
567         if(filelist->libfiledata)       
568                 BLO_blendhandle_close(filelist->libfiledata);
569         filelist->libfiledata= NULL;
570 }
571
572 struct BlendHandle *filelist_lib(struct FileList* filelist)
573 {
574         return filelist->libfiledata;
575 }
576
577 int     filelist_numfiles(struct FileList* filelist)
578 {
579         return filelist->numfiltered;
580 }
581
582 const char * filelist_dir(struct FileList* filelist)
583 {
584         return filelist->dir;
585 }
586
587 void filelist_setdir(struct FileList* filelist, const char *dir)
588 {
589         BLI_strncpy(filelist->dir, dir, FILE_MAX);
590 }
591
592 void filelist_imgsize(struct FileList* filelist, short w, short h)
593 {
594         filelist->prv_w = w;
595         filelist->prv_h = h;
596 }
597
598 short filelist_changed(struct FileList* filelist)
599 {
600         return filelist->changed;
601 }
602
603 struct ImBuf * filelist_getimage(struct FileList* filelist, int index)
604 {
605         ImBuf* ibuf = NULL;
606         int fidx = 0;   
607         if ( (index < 0) || (index >= filelist->numfiltered) ) {
608                 return NULL;
609         }
610         fidx = filelist->fidx[index];
611         ibuf = filelist->filelist[fidx].image;
612
613         return ibuf;
614 }
615
616 struct ImBuf * filelist_geticon(struct FileList* filelist, int index)
617 {
618         ImBuf* ibuf= NULL;
619         struct direntry *file= NULL;
620         int fidx = 0;   
621         if ( (index < 0) || (index >= filelist->numfiltered) ) {
622                 return NULL;
623         }
624         fidx = filelist->fidx[index];
625         file = &filelist->filelist[fidx];
626         if (file->type & S_IFDIR) {
627                 if ( strcmp(filelist->filelist[fidx].relname, "..") == 0) {
628                         ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT];
629                 } else if  ( strcmp(filelist->filelist[fidx].relname, ".") == 0) {
630                         ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH];
631                 } else {
632                         ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER];
633                 }
634         } else {
635                 ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE];
636         }
637
638         if (file->flags & BLENDERFILE) {
639                 ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE];
640         } else if ( (file->flags & MOVIEFILE) || (file->flags & MOVIEFILE_ICON) ) {
641                 ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE];
642         } else if (file->flags & SOUNDFILE) {
643                 ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE];
644         } else if (file->flags & PYSCRIPTFILE) {
645                 ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE];
646         } else if (file->flags & FTFONTFILE) {
647                 ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE];
648         } else if (file->flags & TEXTFILE) {
649                 ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE];
650         } else if (file->flags & IMAGEFILE) {
651                 ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING];
652         }
653
654         return ibuf;
655 }
656
657 struct direntry * filelist_file(struct FileList* filelist, int index)
658 {
659         int fidx = 0;
660         
661         if ( (index < 0) || (index >= filelist->numfiltered) ) {
662                 return NULL;
663         }
664         fidx = filelist->fidx[index];
665
666         return &filelist->filelist[fidx];
667 }
668
669 int filelist_find(struct FileList* filelist, char *file)
670 {
671         int index = -1;
672         int i;
673         int fidx = -1;
674         
675         if (!filelist->fidx) 
676                 return fidx;
677
678         
679         for (i = 0; i < filelist->numfiles; ++i) {
680                 if ( strcmp(filelist->filelist[i].relname, file) == 0) { /* not dealing with user input so dont need BLI_path_cmp */
681                         index = i;
682                         break;
683                 }
684         }
685
686         for (i = 0; i < filelist->numfiltered; ++i) {
687                 if (filelist->fidx[i] == index) {
688                         fidx = i;
689                         break;
690                 }
691         }
692         return fidx;
693 }
694
695 void filelist_hidedot(struct FileList* filelist, short hide)
696 {
697         filelist->hide_dot = hide;
698 }
699
700 void filelist_setfilter(struct FileList* filelist, unsigned int filter)
701 {
702         filelist->filter = filter;
703 }
704
705 void filelist_setfilter_types(struct FileList* filelist, const char *filter_glob)
706 {
707         BLI_strncpy(filelist->filter_glob, filter_glob, sizeof(filelist->filter_glob));
708 }
709
710 static int file_is_blend_backup(const char *str)
711 {
712         short a, b;
713         int retval= 0;
714         
715         a= strlen(str);
716         b= 7;
717         
718         if(a==0 || b>=a);
719         else {
720                 char *loc;
721                 
722                 if(a > b+1)
723                         b++;
724                 
725                 /* allow .blend1 .blend2 .blend32 */
726                 loc= BLI_strcasestr(str+a-b, ".blend");
727                 
728                 if(loc)
729                         retval= 1;
730         }
731         
732         return (retval);
733 }
734
735
736 static int file_extension_type(const char *relname)
737 {
738         if(BLO_has_bfile_extension(relname)) {
739                 return BLENDERFILE;
740         } else if(file_is_blend_backup(relname)) {
741                 return BLENDERFILE_BACKUP;
742         } else if(BLI_testextensie(relname, ".py")) {
743                 return PYSCRIPTFILE;
744         } else if(BLI_testextensie(relname, ".txt")
745                           || BLI_testextensie(relname, ".glsl")
746                           || BLI_testextensie(relname, ".data")) {
747                 return TEXTFILE;
748         } else if( BLI_testextensie(relname, ".ttf")
749                           || BLI_testextensie(relname, ".ttc")
750                           || BLI_testextensie(relname, ".pfb")
751                           || BLI_testextensie(relname, ".otf")
752                           || BLI_testextensie(relname, ".otc")) {
753                 return FTFONTFILE;                      
754         } else if(BLI_testextensie(relname, ".btx")) {
755                 return BTXFILE;
756         } else if(BLI_testextensie(relname, ".dae")) {
757                 return COLLADAFILE;
758         } else if(BLI_testextensie_array(relname, imb_ext_image)
759                           || (G.have_quicktime && BLI_testextensie_array(relname, imb_ext_image_qt))) {
760                 return IMAGEFILE;                       
761         } else if(BLI_testextensie_array(relname, imb_ext_movie)) {
762                 return MOVIEFILE;                       
763         } else if(BLI_testextensie_array(relname, imb_ext_audio)) {
764                 return SOUNDFILE;
765         } 
766         return 0;
767 }
768
769 int ED_file_extension_icon(const char *relname)
770 {
771         int type= file_extension_type(relname);
772         
773         if (type == BLENDERFILE || type==BLENDERFILE_BACKUP)
774                 return ICON_FILE_BLEND;
775         else if (type ==  IMAGEFILE)
776                 return ICON_FILE_IMAGE;
777         else if (type ==  MOVIEFILE)
778                 return ICON_FILE_MOVIE;
779         else if (type ==  PYSCRIPTFILE)
780                 return ICON_FILE_SCRIPT;
781         else if (type ==  SOUNDFILE) 
782                 return ICON_FILE_SOUND;
783         else if (type ==  FTFONTFILE) 
784                 return ICON_FILE_FONT;
785         else if (type ==  BTXFILE) 
786                 return ICON_FILE_BLANK;
787         else if (type ==  COLLADAFILE) 
788                 return ICON_FILE_BLANK;
789         
790         return ICON_FILE_BLANK;
791 }
792
793 static void filelist_setfiletypes(struct FileList* filelist)
794 {
795         struct direntry *file;
796         int num;
797         
798         file= filelist->filelist;
799         
800         for(num=0; num<filelist->numfiles; num++, file++) {
801                 file->type= file->s.st_mode;    /* restore the mess below */ 
802                 
803                 /* Don't check extensions for directories */ 
804                 if (file->type & S_IFDIR) {
805                         continue;
806                 }
807                 file->flags = file_extension_type(file->relname);
808                 
809                 if(filelist->filter_glob
810                    && BLI_testextensie_glob(file->relname, filelist->filter_glob)) {
811                         file->flags= OPERATORFILE;
812                 }
813                 
814         }
815 }
816
817 static void filelist_read_dir(struct FileList* filelist)
818 {
819         char wdir[FILE_MAX]= "";
820         if (!filelist) return;
821
822         filelist->fidx = NULL;
823         filelist->filelist = NULL;
824
825         BLI_current_working_dir(wdir, sizeof(wdir));     /* backup cwd to restore after */
826
827         BLI_cleanup_dir(G.main->name, filelist->dir);
828         filelist->numfiles = BLI_dir_contents(filelist->dir, &(filelist->filelist));
829
830         if(!chdir(wdir)) {} /* fix warning about not checking return value */
831         filelist_setfiletypes(filelist);
832         filelist_filter(filelist);
833 }
834
835 static void filelist_read_main(struct FileList* filelist)
836 {
837         if (!filelist) return;
838         filelist_from_main(filelist);
839 }
840
841 static void filelist_read_library(struct FileList* filelist)
842 {
843         if (!filelist) return;
844         BLI_cleanup_dir(G.main->name, filelist->dir);
845         filelist_from_library(filelist);
846         if(!filelist->libfiledata) {
847                 int num;
848                 struct direntry *file;
849
850                 BLI_make_exist(filelist->dir);
851                 filelist_read_dir(filelist);
852                 file = filelist->filelist;
853                 for(num=0; num<filelist->numfiles; num++, file++) {
854                         if(BLO_has_bfile_extension(file->relname)) {
855                                 char name[FILE_MAXDIR+FILE_MAXFILE];
856                         
857                                 BLI_strncpy(name, filelist->dir, sizeof(name));
858                                 strcat(name, file->relname);
859                                 
860                                 /* prevent current file being used as acceptable dir */
861                                 if (BLI_path_cmp(G.main->name, name) != 0) {
862                                         file->type &= ~S_IFMT;
863                                         file->type |= S_IFDIR;
864                                 }
865                         }
866                 }
867         }
868 }
869
870 void filelist_readdir(struct FileList* filelist)
871 {
872         filelist->readf(filelist);
873 }
874
875 int filelist_empty(struct FileList* filelist)
876 {       
877         return filelist->filelist == NULL;
878 }
879
880 void filelist_parent(struct FileList* filelist)
881 {
882         BLI_parent_dir(filelist->dir);
883         BLI_make_exist(filelist->dir);
884         filelist_readdir(filelist);
885 }
886
887 void filelist_select_file(struct FileList* filelist, int index, FileSelType select, unsigned int flag, FileCheckType check)
888 {
889         struct direntry* file = filelist_file(filelist, index);
890         if (file != NULL) {     
891                 int check_ok = 0; 
892                 switch (check) {
893                         case CHECK_DIRS:
894                                 check_ok = S_ISDIR(file->type);
895                                 break;
896                         case CHECK_ALL:
897                                 check_ok = 1;
898                                 break;
899                         case CHECK_FILES:
900                         default:
901                                 check_ok = !S_ISDIR(file->type);
902                                 break;
903                 }
904                 if (check_ok) {
905                         switch (select) {
906                                 case FILE_SEL_REMOVE:
907                                         file->selflag &= ~flag;
908                                         break;
909                                 case FILE_SEL_ADD:
910                                         file->selflag |= flag;
911                                         break;
912                                 case FILE_SEL_TOGGLE:
913                                         file->selflag ^= flag;
914                                         break;
915                         }
916                 }
917         }
918 }
919
920 void filelist_select(struct FileList* filelist, FileSelection* sel, FileSelType select, unsigned int flag, FileCheckType check)
921 {
922         /* select all valid files between first and last indicated */
923         if ( (sel->first >= 0) && (sel->first < filelist->numfiltered) && (sel->last >= 0) && (sel->last < filelist->numfiltered) ) {
924                 int current_file;
925                 for (current_file = sel->first; current_file <= sel->last; current_file++) {    
926                         filelist_select_file(filelist, current_file, select, flag, check);
927                 }
928         }
929 }
930
931 int     filelist_is_selected(struct FileList* filelist, int index, FileCheckType check)
932 {
933         struct direntry* file = filelist_file(filelist, index);
934         if (!file) {
935                 return 0;
936         }
937         switch (check) {
938                 case CHECK_DIRS:
939                         return S_ISDIR(file->type) && (file->selflag & SELECTED_FILE);
940                 case CHECK_FILES:
941                         return S_ISREG(file->type) && (file->selflag & SELECTED_FILE);
942                 case CHECK_ALL:
943                 default:
944                         return (file->selflag & SELECTED_FILE);
945         }
946 }
947
948 void filelist_sort(struct FileList* filelist, short sort)
949 {
950         switch(sort) {
951         case FILE_SORT_ALPHA:
952                 qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_name);   
953                 break;
954         case FILE_SORT_TIME:
955                 qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_date);   
956                 break;
957         case FILE_SORT_SIZE:
958                 qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_size);   
959                 break;
960         case FILE_SORT_EXTENSION:
961                 qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_extension);      
962         }
963
964         filelist_filter(filelist);
965 }
966
967
968 int filelist_islibrary(struct FileList* filelist, char* dir, char* group)
969 {
970         return BLO_is_a_library(filelist->dir, dir, group);
971 }
972
973 static int groupname_to_code(char *group)
974 {
975         char buf[32];
976         char *lslash;
977         
978         BLI_strncpy(buf, group, sizeof(buf));
979         lslash= BLI_last_slash(buf);
980         if (lslash)
981                 lslash[0]= '\0';
982
983         return BKE_idcode_from_name(buf);
984 }
985  
986 void filelist_from_library(struct FileList* filelist)
987 {
988         LinkNode *l, *names, *previews;
989         struct ImBuf* ima;
990         int ok, i, nprevs, nnames, idcode;
991         char filename[FILE_MAXDIR+FILE_MAXFILE];
992         char dir[FILE_MAX], group[GROUP_MAX];   
993         
994         /* name test */
995         ok= filelist_islibrary(filelist, dir, group);
996         if (!ok) {
997                 /* free */
998                 if(filelist->libfiledata) BLO_blendhandle_close(filelist->libfiledata);
999                 filelist->libfiledata= NULL;
1000                 return;
1001         }
1002         
1003         BLI_strncpy(filename, G.main->name, sizeof(filename));
1004
1005         /* there we go */
1006         /* for the time being only read filedata when libfiledata==0 */
1007         if (filelist->libfiledata == NULL) {
1008                 filelist->libfiledata= BLO_blendhandle_from_file(dir, NULL);
1009                 if(filelist->libfiledata == NULL) return;
1010         }
1011         
1012         idcode= groupname_to_code(group);
1013
1014         /* memory for strings is passed into filelist[i].relname
1015          * and free'd in freefilelist */
1016         if (idcode) {
1017                 previews= BLO_blendhandle_get_previews(filelist->libfiledata, idcode, &nprevs);
1018                 names= BLO_blendhandle_get_datablock_names(filelist->libfiledata, idcode, &nnames);
1019                 /* ugh, no rewind, need to reopen */
1020                 BLO_blendhandle_close(filelist->libfiledata);
1021                 filelist->libfiledata= BLO_blendhandle_from_file(dir, NULL);
1022                 
1023         } else {
1024                 previews= NULL;
1025                 nprevs= 0;
1026                 names= BLO_blendhandle_get_linkable_groups(filelist->libfiledata);
1027                 nnames= BLI_linklist_length(names);
1028         }
1029
1030         filelist->numfiles= nnames + 1;
1031         filelist->filelist= malloc(filelist->numfiles * sizeof(*filelist->filelist));
1032         memset(filelist->filelist, 0, filelist->numfiles * sizeof(*filelist->filelist));
1033
1034         filelist->filelist[0].relname= BLI_strdup("..");
1035         filelist->filelist[0].type |= S_IFDIR;
1036                 
1037         for (i=0, l= names; i<nnames; i++, l= l->next) {
1038                 char *blockname= l->link;
1039
1040                 filelist->filelist[i + 1].relname= BLI_strdup(blockname);
1041                 if (idcode) {
1042                         filelist->filelist[i + 1].type |= S_IFREG;
1043                 } else {
1044                         filelist->filelist[i + 1].type |= S_IFDIR;
1045                 }
1046         }
1047         
1048         if(previews && (nnames != nprevs)) {
1049                 printf("filelist_from_library: error, found %d items, %d previews\n", nnames, nprevs);
1050         }
1051         else if(previews) {
1052                 for (i=0, l= previews; i<nnames; i++, l= l->next) {
1053                         PreviewImage *img= l->link;
1054                         
1055                         if (img) {
1056                                 unsigned int w = img->w[ICON_SIZE_PREVIEW];
1057                                 unsigned int h = img->h[ICON_SIZE_PREVIEW];
1058                                 unsigned int *rect = img->rect[ICON_SIZE_PREVIEW];
1059
1060                                 /* first allocate imbuf for copying preview into it */
1061                                 if (w > 0 && h > 0 && rect) {
1062                                         ima = IMB_allocImBuf(w, h, 32, IB_rect);
1063                                         memcpy(ima->rect, rect, w*h*sizeof(unsigned int));
1064                                         filelist->filelist[i + 1].image = ima;
1065                                         filelist->filelist[i + 1].flags = IMAGEFILE;
1066                                 }
1067                         }
1068                 }
1069         }
1070
1071         BLI_linklist_free(names, free);
1072         if (previews) BLI_linklist_free(previews, BKE_previewimg_freefunc);
1073
1074         filelist_sort(filelist, FILE_SORT_ALPHA);
1075
1076         BLI_strncpy(G.main->name, filename, sizeof(filename));  // prevent G.main->name to change
1077
1078         filelist->filter = 0;
1079         filelist_filter(filelist);
1080 }
1081
1082 void filelist_hideparent(struct FileList* filelist, short hide)
1083 {
1084         filelist->hide_parent = hide;
1085 }
1086
1087 void filelist_from_main(struct FileList *filelist)
1088 {
1089         ID *id;
1090         struct direntry *files, *firstlib = NULL;
1091         ListBase *lb;
1092         int a, fake, idcode, ok, totlib, totbl;
1093         
1094         // filelist->type = FILE_MAIN; // XXXXX TODO: add modes to filebrowser
1095
1096         if(filelist->dir[0]=='/') filelist->dir[0]= 0;
1097         
1098         if(filelist->dir[0]) {
1099                 idcode= groupname_to_code(filelist->dir);
1100                 if(idcode==0) filelist->dir[0]= 0;
1101         }
1102         
1103         if( filelist->dir[0]==0) {
1104                 
1105                 /* make directories */
1106                 filelist->numfiles= 24;
1107                 filelist->filelist= (struct direntry *)malloc(filelist->numfiles * sizeof(struct direntry));
1108                 
1109                 for(a=0; a<filelist->numfiles; a++) {
1110                         memset( &(filelist->filelist[a]), 0 , sizeof(struct direntry));
1111                         filelist->filelist[a].type |= S_IFDIR;
1112                 }
1113                 
1114                 filelist->filelist[0].relname= BLI_strdup("..");
1115                 filelist->filelist[2].relname= BLI_strdup("Scene");
1116                 filelist->filelist[3].relname= BLI_strdup("Object");
1117                 filelist->filelist[4].relname= BLI_strdup("Mesh");
1118                 filelist->filelist[5].relname= BLI_strdup("Curve");
1119                 filelist->filelist[6].relname= BLI_strdup("Metaball");
1120                 filelist->filelist[7].relname= BLI_strdup("Material");
1121                 filelist->filelist[8].relname= BLI_strdup("Texture");
1122                 filelist->filelist[9].relname= BLI_strdup("Image");
1123                 filelist->filelist[10].relname= BLI_strdup("Ika");
1124                 filelist->filelist[11].relname= BLI_strdup("Wave");
1125                 filelist->filelist[12].relname= BLI_strdup("Lattice");
1126                 filelist->filelist[13].relname= BLI_strdup("Lamp");
1127                 filelist->filelist[14].relname= BLI_strdup("Camera");
1128                 filelist->filelist[15].relname= BLI_strdup("Ipo");
1129                 filelist->filelist[16].relname= BLI_strdup("World");
1130                 filelist->filelist[17].relname= BLI_strdup("Screen");
1131                 filelist->filelist[18].relname= BLI_strdup("VFont");
1132                 filelist->filelist[19].relname= BLI_strdup("Text");
1133                 filelist->filelist[20].relname= BLI_strdup("Armature");
1134                 filelist->filelist[21].relname= BLI_strdup("Action");
1135                 filelist->filelist[22].relname= BLI_strdup("NodeTree");
1136                 filelist->filelist[23].relname= BLI_strdup("Speaker");
1137                 filelist_sort(filelist, FILE_SORT_ALPHA);
1138         }
1139         else {
1140
1141                 /* make files */
1142                 idcode= groupname_to_code(filelist->dir);
1143                 
1144                 lb= which_libbase(G.main, idcode );
1145                 if(lb == NULL) return;
1146                 
1147                 id= lb->first;
1148                 filelist->numfiles= 0;
1149                 while(id) {
1150                         if (!filelist->hide_dot || id->name[2] != '.') {
1151                                 filelist->numfiles++;
1152                         }
1153                         
1154                         id= id->next;
1155                 }
1156                 
1157                 /* XXXXX TODO: if databrowse F4 or append/link filelist->hide_parent has to be set */
1158                 if (!filelist->hide_parent) filelist->numfiles+= 1;
1159                 filelist->filelist= filelist->numfiles > 0 ? (struct direntry *)malloc(filelist->numfiles * sizeof(struct direntry)) : NULL;
1160
1161                 files = filelist->filelist;
1162                 
1163                 if (!filelist->hide_parent) {
1164                         memset( &(filelist->filelist[0]), 0 , sizeof(struct direntry));
1165                         filelist->filelist[0].relname= BLI_strdup("..");
1166                         filelist->filelist[0].type |= S_IFDIR;
1167                 
1168                         files++;
1169                 }
1170                 
1171                 id= lb->first;
1172                 totlib= totbl= 0;
1173                 
1174                 while(id) {
1175                         ok = 1;
1176                         if(ok) {
1177                                 if (!filelist->hide_dot || id->name[2] != '.') {
1178                                         memset( files, 0 , sizeof(struct direntry));
1179                                         if(id->lib==NULL)
1180                                                 files->relname= BLI_strdup(id->name+2);
1181                                         else {
1182                                                 files->relname= MEM_mallocN(FILE_MAXDIR+FILE_MAXFILE+32, "filename for lib");
1183                                                 sprintf(files->relname, "%s | %s", id->lib->name, id->name+2);
1184                                         }
1185                                         files->type |= S_IFREG;
1186 #if 0                           // XXXXX TODO show the selection status of the objects
1187                                         if(!filelist->has_func) { /* F4 DATA BROWSE */
1188                                                 if(idcode==ID_OB) {
1189                                                         if( ((Object *)id)->flag & SELECT) files->selflag |= SELECTED_FILE;
1190                                                 }
1191                                                 else if(idcode==ID_SCE) {
1192                                                         if( ((Scene *)id)->r.scemode & R_BG_RENDER) files->selflag |= SELECTED_FILE;
1193                                                 }                                       
1194                                         }
1195 #endif
1196                                         files->nr= totbl+1;
1197                                         files->poin= id;
1198                                         fake= id->flag & LIB_FAKEUSER;
1199                                         if(idcode == ID_MA || idcode == ID_TE || idcode == ID_LA || idcode == ID_WO || idcode == ID_IM) {
1200                                                 files->flags |= IMAGEFILE;
1201                                         }
1202                                         if(id->lib && fake) sprintf(files->extra, "LF %d", id->us);
1203                                         else if(id->lib) sprintf(files->extra, "L    %d", id->us);
1204                                         else if(fake) sprintf(files->extra, "F    %d", id->us);
1205                                         else sprintf(files->extra, "      %d", id->us);
1206                                         
1207                                         if(id->lib) {
1208                                                 if(totlib==0) firstlib= files;
1209                                                 totlib++;
1210                                         }
1211                                         
1212                                         files++;
1213                                 }
1214                                 totbl++;
1215                         }
1216                         
1217                         id= id->next;
1218                 }
1219                 
1220                 /* only qsort of library blocks */
1221                 if(totlib>1) {
1222                         qsort(firstlib, totlib, sizeof(struct direntry), compare_name);
1223                 }
1224         }
1225         filelist->filter = 0;
1226         filelist_filter(filelist);
1227 }
1228
1229 static void thumbnail_joblist_free(ThumbnailJob *tj)
1230 {
1231         FileImage* limg = tj->loadimages.first;
1232         
1233         /* free the images not yet copied to the filelist -> these will get freed with the filelist */
1234         for( ; limg; limg= limg->next) {
1235                 if ((limg->img) && (!limg->done)) {
1236                         IMB_freeImBuf(limg->img);
1237                 }
1238         }
1239         BLI_freelistN(&tj->loadimages);
1240 }
1241
1242 static void thumbnails_startjob(void *tjv, short *stop, short *do_update, float *UNUSED(progress))
1243 {
1244         ThumbnailJob *tj= tjv;
1245         FileImage* limg = tj->loadimages.first;
1246
1247         tj->stop= stop;
1248         tj->do_update= do_update;
1249
1250         while ( (*stop==0) && (limg) ) {
1251                 if ( limg->flags & IMAGEFILE ) {
1252                         limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_IMAGE);
1253                 } else if ( limg->flags & BLENDERFILE ) {
1254                         limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_BLEND);
1255                 } else if ( limg->flags & MOVIEFILE ) {
1256                         limg->img = IMB_thumb_manage(limg->path, THB_NORMAL, THB_SOURCE_MOVIE);
1257                         if (!limg->img) {
1258                                         /* remember that file can't be loaded via IMB_open_anim */
1259                                         limg->flags &= ~MOVIEFILE;
1260                                         limg->flags |= MOVIEFILE_ICON;
1261                                 }
1262                 }
1263                 *do_update = 1;
1264                 PIL_sleep_ms(10);
1265                 limg = limg->next;
1266         }
1267 }
1268
1269 static void thumbnails_update(void *tjv)
1270 {
1271         ThumbnailJob *tj= tjv;
1272
1273         if (tj->filelist && tj->filelist->filelist) {
1274                 FileImage* limg = tj->loadimages.first;
1275                 while (limg) {
1276                         if (!limg->done && limg->img) {
1277                                 tj->filelist->filelist[limg->index].image = limg->img;
1278                                 /* update flag for movie files where thumbnail can't be created */
1279                                 if (limg->flags & MOVIEFILE_ICON) {
1280                                         tj->filelist->filelist[limg->index].flags &= ~MOVIEFILE;
1281                                         tj->filelist->filelist[limg->index].flags |= MOVIEFILE_ICON;
1282                                 }
1283                                 limg->done=1;
1284                         }
1285                         limg = limg->next;
1286                 }
1287         }
1288 }
1289
1290 static void thumbnails_free(void *tjv)
1291 {
1292         ThumbnailJob *tj= tjv;
1293         thumbnail_joblist_free(tj);
1294         MEM_freeN(tj);
1295 }
1296
1297
1298 void thumbnails_start(struct FileList* filelist, const struct bContext* C)
1299 {
1300         wmJob *steve;
1301         ThumbnailJob *tj;
1302         int idx;
1303         
1304         /* prepare job data */
1305         tj= MEM_callocN(sizeof(ThumbnailJob), "thumbnails\n");
1306         tj->filelist = filelist;
1307         for (idx = 0; idx < filelist->numfiles;idx++) {
1308                 if (!filelist->filelist[idx].image) {
1309                         if ( (filelist->filelist[idx].flags & (IMAGEFILE|MOVIEFILE|BLENDERFILE)) ) {
1310                                 FileImage* limg = MEM_callocN(sizeof(struct FileImage), "loadimage");
1311                                 BLI_strncpy(limg->path, filelist->filelist[idx].path, FILE_MAX);
1312                                 limg->index= idx;
1313                                 limg->flags= filelist->filelist[idx].flags;
1314                                 BLI_addtail(&tj->loadimages, limg);
1315                         }
1316                 }
1317         }
1318
1319         BKE_reports_init(&tj->reports, RPT_PRINT);
1320
1321         /* setup job */
1322         steve= WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), filelist, "Thumbnails", 0);
1323         WM_jobs_customdata(steve, tj, thumbnails_free);
1324         WM_jobs_timer(steve, 0.5, NC_WINDOW, NC_WINDOW);
1325         WM_jobs_callbacks(steve, thumbnails_startjob, NULL, thumbnails_update, NULL);
1326
1327         /* start the job */
1328         WM_jobs_start(CTX_wm_manager(C), steve);
1329 }
1330
1331 void thumbnails_stop(struct FileList* filelist, const struct bContext* C)
1332 {
1333         WM_jobs_kill(CTX_wm_manager(C), filelist, NULL);
1334 }
1335
1336 int thumbnails_running(struct FileList* filelist, const struct bContext* C)
1337 {
1338         return WM_jobs_test(CTX_wm_manager(C), filelist);
1339 }