Cleanup/refactor spacefile's 'check dir' code.
[blender.git] / source / blender / editors / space_file / file_ops.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) 2008 Blender Foundation.
19  * All rights reserved.
20  *
21  * 
22  * Contributor(s): Andrea Weikert (c) 2008 Blender Foundation
23  *
24  * ***** END GPL LICENSE BLOCK *****
25  */
26
27 /** \file blender/editors/space_file/file_ops.c
28  *  \ingroup spfile
29  */
30
31 #include "BLI_blenlib.h"
32 #include "BLI_utildefines.h"
33 #include "BLI_fileops_types.h"
34 #include "BLI_linklist.h"
35
36 #include "BLO_readfile.h"
37
38 #include "BKE_appdir.h"
39 #include "BKE_context.h"
40 #include "BKE_screen.h"
41 #include "BKE_global.h"
42 #include "BKE_report.h"
43 #include "BKE_main.h"
44
45 #ifdef WIN32
46 #  include "BLI_winstuff.h"
47 #endif
48
49 #include "ED_screen.h"
50 #include "ED_fileselect.h"
51
52 #include "UI_interface.h"
53
54 #include "MEM_guardedalloc.h"
55
56 #include "RNA_access.h"
57 #include "RNA_define.h"
58
59 #include "UI_view2d.h"
60
61 #include "WM_api.h"
62 #include "WM_types.h"
63
64 #include "file_intern.h"
65 #include "filelist.h"
66 #include "fsmenu.h"
67
68 #include <stdlib.h>
69 #include <string.h>
70 #include <stdio.h>
71 #include <ctype.h>
72 #include <errno.h>
73
74 /* ---------- FILE SELECTION ------------ */
75 static FileSelection find_file_mouse_rect(SpaceFile *sfile, ARegion *ar, const rcti *rect_region)
76 {
77         FileSelection sel;
78         
79         View2D *v2d = &ar->v2d;
80         rcti rect_view;
81         rctf rect_view_fl;
82         rctf rect_region_fl;
83
84         BLI_rctf_rcti_copy(&rect_region_fl, rect_region);
85
86         UI_view2d_region_to_view_rctf(v2d, &rect_region_fl, &rect_view_fl);
87
88         BLI_rcti_init(&rect_view,
89                       (int)(v2d->tot.xmin + rect_view_fl.xmin),
90                       (int)(v2d->tot.xmin + rect_view_fl.xmax),
91                       (int)(v2d->tot.ymax - rect_view_fl.ymin),
92                       (int)(v2d->tot.ymax - rect_view_fl.ymax));
93
94         sel  = ED_fileselect_layout_offset_rect(sfile->layout, &rect_view);
95         
96         return sel;
97 }
98
99 static void file_deselect_all(SpaceFile *sfile, unsigned int flag)
100 {
101         FileSelection sel;
102         sel.first = 0;
103         sel.last = filelist_files_ensure(sfile->files) - 1;
104         
105         filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL);
106 }
107
108 typedef enum FileSelect { 
109         FILE_SELECT_NOTHING = 0,
110         FILE_SELECT_DIR = 1, 
111         FILE_SELECT_FILE = 2 
112 } FileSelect;
113
114 static void clamp_to_filelist(int numfiles, FileSelection *sel)
115 {
116         /* border select before the first file */
117         if ( (sel->first < 0) && (sel->last >= 0) ) {
118                 sel->first = 0;
119         }
120         /* don't select if everything is outside filelist */
121         if ( (sel->first >= numfiles) && ((sel->last < 0) || (sel->last >= numfiles)) ) {
122                 sel->first = -1;
123                 sel->last = -1;
124         }
125         
126         /* fix if last file invalid */
127         if ( (sel->first > 0) && (sel->last < 0) )
128                 sel->last = numfiles - 1;
129
130         /* clamp */
131         if ( (sel->first >= numfiles) ) {
132                 sel->first = numfiles - 1;
133         }
134         if ( (sel->last >= numfiles) ) {
135                 sel->last = numfiles - 1;
136         }
137 }
138
139 static FileSelection file_selection_get(bContext *C, const rcti *rect, bool fill)
140 {
141         ARegion *ar = CTX_wm_region(C);
142         SpaceFile *sfile = CTX_wm_space_file(C);
143         int numfiles = filelist_files_ensure(sfile->files);
144         FileSelection sel;
145
146         sel = find_file_mouse_rect(sfile, ar, rect);
147         if (!((sel.first == -1) && (sel.last == -1)) ) {
148                 clamp_to_filelist(numfiles, &sel);
149         }
150
151
152         /* if desired, fill the selection up from the last selected file to the current one */
153         if (fill && (sel.last >= 0) && (sel.last < numfiles) ) {
154                 int f = sel.last;
155                 while (f >= 0) {
156                         if (filelist_entry_select_index_get(sfile->files, f, CHECK_ALL) )
157                                 break;
158                         f--;
159                 }
160                 if (f >= 0) {
161                         sel.first = f + 1;
162                 }
163         }
164         return sel;
165 }
166
167 static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen)
168 {
169         FileSelect retval = FILE_SELECT_NOTHING;
170         SpaceFile *sfile = CTX_wm_space_file(C);
171         FileSelectParams *params = ED_fileselect_get_params(sfile);
172         int numfiles = filelist_files_ensure(sfile->files);
173         const FileDirEntry *file;
174
175         /* make the selected file active */
176         if ((selected_idx >= 0) &&
177             (selected_idx < numfiles) &&
178             (file = filelist_file(sfile->files, selected_idx)))
179         {
180                 params->highlight_file = selected_idx;
181                 params->active_file = selected_idx;
182
183                 if (file->typeflag & FILE_TYPE_DIR) {
184                         const bool is_parent_dir = FILENAME_IS_PARENT(file->relpath);
185
186                         if (do_diropen == false) {
187                                 params->file[0] = '\0';
188                                 retval = FILE_SELECT_DIR;
189                         }
190                         /* the path is too long and we are not going up! */
191                         else if (!is_parent_dir && strlen(params->dir) + strlen(file->relpath) >= FILE_MAX) {
192                                 // XXX error("Path too long, cannot enter this directory");
193                         }
194                         else {
195                                 if (is_parent_dir) {
196                                         /* avoids /../../ */
197                                         BLI_parent_dir(params->dir);
198
199                                         if (params->recursion_level > 1) {
200                                                 /* Disable 'dirtree' recursion when going up in tree. */
201                                                 params->recursion_level = 0;
202                                                 filelist_setrecursion(sfile->files, params->recursion_level);
203                                         }
204                                 }
205                                 else {
206                                         BLI_cleanup_dir(G.main->name, params->dir);
207                                         strcat(params->dir, file->relpath);
208                                         BLI_add_slash(params->dir);
209                                 }
210
211                                 ED_file_change_dir(C);
212                                 retval = FILE_SELECT_DIR;
213                         }
214                 }
215                 else {
216                         retval = FILE_SELECT_FILE;
217                 }
218                 fileselect_file_set(sfile, selected_idx);
219         }
220         return retval;
221 }
222
223 /**
224  * \warning: loops over all files so better use cautiously
225  */
226 static bool file_is_any_selected(struct FileList *files)
227 {
228         const int numfiles = filelist_files_ensure(files);
229         int i;
230
231         /* Is any file selected ? */
232         for (i = 0; i < numfiles; ++i) {
233                 if (filelist_entry_select_index_get(files, i, CHECK_ALL)) {
234                         return true;
235                 }
236         }
237
238         return false;
239 }
240
241 /**
242  * If \a file is outside viewbounds, this adjusts view to make sure it's inside
243  */
244 static void file_ensure_inside_viewbounds(ARegion *ar, SpaceFile *sfile, const int file)
245 {
246         FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
247         rctf *cur = &ar->v2d.cur;
248         rcti rect;
249         bool changed = true;
250
251         file_tile_boundbox(ar, layout, file, &rect);
252
253         /* down - also use if tile is higher than viewbounds so view is aligned to file name */
254         if (cur->ymin > rect.ymin || layout->tile_h > ar->winy) {
255                 cur->ymin = rect.ymin - (2 * layout->tile_border_y);
256                 cur->ymax = cur->ymin + ar->winy;
257         }
258         /* up */
259         else if (cur->ymax < rect.ymax) {
260                 cur->ymax = rect.ymax + layout->tile_border_y;
261                 cur->ymin = cur->ymax - ar->winy;
262         }
263         /* left - also use if tile is wider than viewbounds so view is aligned to file name */
264         else if (cur->xmin > rect.xmin || layout->tile_w > ar->winx) {
265                 cur->xmin = rect.xmin - layout->tile_border_x;
266                 cur->xmax = cur->xmin + ar->winx;
267         }
268         /* right */
269         else if (cur->xmax < rect.xmax) {
270                 cur->xmax = rect.xmax + (2 * layout->tile_border_x);
271                 cur->xmin = cur->xmax - ar->winx;
272         }
273         else {
274                 BLI_assert(cur->xmin <= rect.xmin && cur->xmax >= rect.xmax &&
275                            cur->ymin <= rect.ymin && cur->ymax >= rect.ymax);
276                 changed = false;
277         }
278
279         if (changed) {
280                 UI_view2d_curRect_validate(&ar->v2d);
281         }
282 }
283
284
285 static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select, bool fill, bool do_diropen)
286 {
287         SpaceFile *sfile = CTX_wm_space_file(C);
288         FileSelect retval = FILE_SELECT_NOTHING;
289         FileSelection sel = file_selection_get(C, rect, fill); /* get the selection */
290         const FileCheckType check_type = (sfile->params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_ALL;
291         
292         /* flag the files as selected in the filelist */
293         filelist_entries_select_index_range_set(sfile->files, &sel, select, FILE_SEL_SELECTED, check_type);
294         
295         /* Don't act on multiple selected files */
296         if (sel.first != sel.last) select = 0;
297
298         /* Do we have a valid selection and are we actually selecting */
299         if ((sel.last >= 0) && (select != FILE_SEL_REMOVE)) {
300                 /* Check last selection, if selected, act on the file or dir */
301                 if (filelist_entry_select_index_get(sfile->files, sel.last, check_type)) {
302                         retval = file_select_do(C, sel.last, do_diropen);
303                 }
304         }
305
306         if (select != FILE_SEL_ADD && !file_is_any_selected(sfile->files)) {
307                 sfile->params->active_file = -1;
308         }
309         else {
310                 ARegion *ar = CTX_wm_region(C);
311                 const FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
312
313                 /* Adjust view to display selection. Doing iterations for first and last
314                  * selected item makes view showing as much of the selection possible.
315                  * Not really useful if tiles are (almost) bigger than viewbounds though. */
316                 if (((layout->flag & FILE_LAYOUT_HOR) && ar->winx > (1.2f * layout->tile_w)) ||
317                     ((layout->flag & FILE_LAYOUT_VER) && ar->winy > (2.0f * layout->tile_h)))
318                 {
319                         file_ensure_inside_viewbounds(ar, sfile, sel.last);
320                         file_ensure_inside_viewbounds(ar, sfile, sel.first);
321                 }
322         }
323
324         /* update operator for name change event */
325         file_draw_check(C);
326         
327         return retval;
328 }
329
330 static int file_border_select_find_last_selected(
331         SpaceFile *sfile, ARegion *ar, const FileSelection *sel,
332         const int mouse_xy[2])
333 {
334         FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
335         rcti bounds_first, bounds_last;
336         int dist_first, dist_last;
337         float mouseco_view[2];
338
339         UI_view2d_region_to_view(&ar->v2d, UNPACK2(mouse_xy), &mouseco_view[0], &mouseco_view[1]);
340
341         file_tile_boundbox(ar, layout, sel->first, &bounds_first);
342         file_tile_boundbox(ar, layout, sel->last, &bounds_last);
343
344         /* are first and last in the same column (horizontal layout)/row (vertical layout)? */
345         if ((layout->flag & FILE_LAYOUT_HOR && bounds_first.xmin == bounds_last.xmin) ||
346             (layout->flag & FILE_LAYOUT_VER && bounds_first.ymin != bounds_last.ymin))
347         {
348                 /* use vertical distance */
349                 const int my_loc = (int)mouseco_view[1];
350                 dist_first = BLI_rcti_length_y(&bounds_first, my_loc);
351                 dist_last = BLI_rcti_length_y(&bounds_last, my_loc);
352         }
353         else {
354                 /* use horizontal distance */
355                 const int mx_loc = (int)mouseco_view[0];
356                 dist_first = BLI_rcti_length_x(&bounds_first, mx_loc);
357                 dist_last = BLI_rcti_length_x(&bounds_last, mx_loc);
358         }
359
360         return (dist_first < dist_last) ? sel->first : sel->last;
361 }
362
363 static int file_border_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
364 {
365         ARegion *ar = CTX_wm_region(C);
366         SpaceFile *sfile = CTX_wm_space_file(C);
367         FileSelectParams *params = ED_fileselect_get_params(sfile);
368         FileSelection sel;
369         rcti rect;
370
371         int result;
372
373         result = WM_border_select_modal(C, op, event);
374
375         if (result == OPERATOR_RUNNING_MODAL) {
376                 WM_operator_properties_border_to_rcti(op, &rect);
377
378                 BLI_rcti_isect(&(ar->v2d.mask), &rect, &rect);
379
380                 sel = file_selection_get(C, &rect, 0);
381                 if ((sel.first != params->sel_first) || (sel.last != params->sel_last)) {
382                         int idx;
383
384                         file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED);
385                         filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL);
386                         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
387
388                         for (idx = sel.last; idx >= 0; idx--) {
389                                 const FileDirEntry *file = filelist_file(sfile->files, idx);
390
391                                 /* dont highlight readonly file (".." or ".") on border select */
392                                 if (FILENAME_IS_CURRPAR(file->relpath)) {
393                                         filelist_entry_select_set(sfile->files, file, FILE_SEL_REMOVE, FILE_SEL_HIGHLIGHTED, CHECK_ALL);
394                                 }
395
396                                 /* make sure highlight_file is no readonly file */
397                                 if (sel.last == idx) {
398                                         params->highlight_file = idx;
399                                 }
400                         }
401                 }
402                 params->sel_first = sel.first; params->sel_last = sel.last;
403                 params->active_file = file_border_select_find_last_selected(sfile, ar, &sel, event->mval);
404         }
405         else {
406                 params->highlight_file = -1;
407                 params->sel_first = params->sel_last = -1;
408                 fileselect_file_set(sfile, params->active_file);
409                 file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED);
410                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
411         }
412
413         return result;
414 }
415
416 static int file_border_select_exec(bContext *C, wmOperator *op)
417 {
418         ARegion *ar = CTX_wm_region(C);
419         SpaceFile *sfile = CTX_wm_space_file(C);
420         rcti rect;
421         FileSelect ret;
422         const bool select = (RNA_int_get(op->ptr, "gesture_mode") == GESTURE_MODAL_SELECT);
423         const bool extend = RNA_boolean_get(op->ptr, "extend");
424
425         WM_operator_properties_border_to_rcti(op, &rect);
426
427         if (!extend) {
428                 file_deselect_all(sfile, FILE_SEL_SELECTED);
429         }
430
431         BLI_rcti_isect(&(ar->v2d.mask), &rect, &rect);
432
433         ret = file_select(C, &rect, select ? FILE_SEL_ADD : FILE_SEL_REMOVE, false, false);
434
435         /* unselect '..' parent entry - it's not supposed to be selected if more than one file is selected */
436         filelist_entry_select_index_set(sfile->files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
437
438         if (FILE_SELECT_DIR == ret) {
439                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
440         }
441         else if (FILE_SELECT_FILE == ret) {
442                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
443         }
444         return OPERATOR_FINISHED;
445 }
446
447 void FILE_OT_select_border(wmOperatorType *ot)
448 {
449         /* identifiers */
450         ot->name = "Activate/Select File";
451         ot->description = "Activate/select the file(s) contained in the border";
452         ot->idname = "FILE_OT_select_border";
453         
454         /* api callbacks */
455         ot->invoke = WM_border_select_invoke;
456         ot->exec = file_border_select_exec;
457         ot->modal = file_border_select_modal;
458         ot->poll = ED_operator_file_active;
459         ot->cancel = WM_border_select_cancel;
460
461         /* properties */
462         WM_operator_properties_gesture_border(ot, 1);
463 }
464
465 static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
466 {
467         ARegion *ar = CTX_wm_region(C);
468         SpaceFile *sfile = CTX_wm_space_file(C);
469         FileSelect ret;
470         rcti rect;
471         const bool extend = RNA_boolean_get(op->ptr, "extend");
472         const bool fill = RNA_boolean_get(op->ptr, "fill");
473         const bool do_diropen = RNA_boolean_get(op->ptr, "open");
474
475         if (ar->regiontype != RGN_TYPE_WINDOW)
476                 return OPERATOR_CANCELLED;
477
478         rect.xmin = rect.xmax = event->mval[0];
479         rect.ymin = rect.ymax = event->mval[1];
480
481         if (!BLI_rcti_isect_pt(&ar->v2d.mask, rect.xmin, rect.ymin))
482                 return OPERATOR_CANCELLED;
483
484         if (sfile && sfile->params) {
485                 int idx = sfile->params->highlight_file;
486                 int numfiles = filelist_files_ensure(sfile->files);
487
488                 if ((idx >= 0) && (idx < numfiles)) {
489                         /* single select, deselect all selected first */
490                         if (!extend) {
491                                 file_deselect_all(sfile, FILE_SEL_SELECTED);
492                         }
493                 }
494         }
495
496         ret = file_select(C, &rect, extend ? FILE_SEL_TOGGLE : FILE_SEL_ADD, fill, do_diropen);
497
498         if (extend) {
499                 /* unselect '..' parent entry - it's not supposed to be selected if more than one file is selected */
500                 filelist_entry_select_index_set(sfile->files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
501         }
502
503         if (FILE_SELECT_DIR == ret)
504                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
505         else if (FILE_SELECT_FILE == ret)
506                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
507
508         WM_event_add_mousemove(C); /* for directory changes */
509         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
510
511         return OPERATOR_FINISHED;
512 }
513
514 void FILE_OT_select(wmOperatorType *ot)
515 {
516         PropertyRNA *prop;
517
518         /* identifiers */
519         ot->name = "Activate/Select File";
520         ot->description = "Activate/select file";
521         ot->idname = "FILE_OT_select";
522         
523         /* api callbacks */
524         ot->invoke = file_select_invoke;
525         ot->poll = ED_operator_file_active;
526
527         /* properties */
528         prop = RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend selection instead of deselecting everything first");
529         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
530         prop = RNA_def_boolean(ot->srna, "fill", false, "Fill", "Select everything beginning with the last selection");
531         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
532         prop = RNA_def_boolean(ot->srna, "open", true, "Open", "Open a directory when selecting it");
533         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
534 }
535
536 /**
537  * \returns true if selection has changed
538  */
539 static bool file_walk_select_selection_set(
540         bContext *C, SpaceFile *sfile,
541         const int direction, const int numfiles,
542         const int active_old, const int active_new, const int other_site,
543         const bool has_selection, const bool extend, const bool fill)
544 {
545         FileSelectParams *params = sfile->params;
546         struct FileList *files = sfile->files;
547         const int last_sel = params->active_file; /* store old value */
548         int active = active_old; /* could use active_old instead, just for readability */
549         bool deselect = false;
550
551         BLI_assert(params);
552
553         if (has_selection) {
554                 if (extend &&
555                     filelist_entry_select_index_get(files, active_old, CHECK_ALL) &&
556                     filelist_entry_select_index_get(files, active_new, CHECK_ALL))
557                 {
558                         /* conditions for deselecting: initial file is selected, new file is
559                          * selected and either other_side isn't selected/found or we use fill */
560                         deselect = (fill || other_site == -1 ||
561                                     !filelist_entry_select_index_get(files, other_site, CHECK_ALL));
562
563                         /* don't change highlight_file here since we either want to deselect active or we want to
564                          * walk through a block of selected files without selecting/deselecting anything */
565                         params->active_file = active_new;
566                         /* but we want to change active if we use fill (needed to get correct selection bounds) */
567                         if (deselect && fill) {
568                                 active = active_new;
569                         }
570                 }
571                 else {
572                         /* regular selection change */
573                         params->active_file = active = active_new;
574                 }
575         }
576         else {
577                 /* select last file */
578                 if (ELEM(direction, FILE_SELECT_WALK_UP, FILE_SELECT_WALK_LEFT)) {
579                         params->active_file = active = numfiles - 1;
580                 }
581                 /* select first file */
582                 else if (ELEM(direction, FILE_SELECT_WALK_DOWN, FILE_SELECT_WALK_RIGHT)) {
583                         params->active_file = active = extend ? 1 : 0;
584                 }
585                 else {
586                         BLI_assert(0);
587                 }
588         }
589
590         if (active < 0) {
591                 return false;
592         }
593
594         if (extend) {
595                 /* highlight the active walker file for extended selection for better visual feedback */
596                 params->highlight_file = params->active_file;
597
598                 /* unselect '..' parent entry - it's not supposed to be selected if more than one file is selected */
599                 filelist_entry_select_index_set(files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
600         }
601         else {
602                 /* deselect all first */
603                 file_deselect_all(sfile, FILE_SEL_SELECTED);
604
605                 /* highlight file under mouse pos */
606                 params->highlight_file = -1;
607                 WM_event_add_mousemove(C);
608         }
609
610         /* do the actual selection */
611         if (fill) {
612                 FileSelection sel = { MIN2(active, last_sel), MAX2(active, last_sel) };
613
614                 /* clamping selection to not include '..' parent entry */
615                 if (sel.first == 0) {
616                         sel.first = 1;
617                 }
618
619                 /* fill selection between last and first selected file */
620                 filelist_entries_select_index_range_set(
621                             files, &sel, deselect ? FILE_SEL_REMOVE : FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
622                 /* entire sel is cleared here, so select active again */
623                 if (deselect) {
624                         filelist_entry_select_index_set(files, active, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
625                 }
626         }
627         else {
628                 filelist_entry_select_index_set(
629                             files, active, deselect ? FILE_SEL_REMOVE : FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
630         }
631
632         BLI_assert(IN_RANGE(active, -1, numfiles));
633         fileselect_file_set(sfile, params->active_file);
634
635         /* ensure newly selected file is inside viewbounds */
636         file_ensure_inside_viewbounds(CTX_wm_region(C), sfile, params->active_file);
637
638         /* selection changed */
639         return true;
640 }
641
642 /**
643  * \returns true if selection has changed
644  */
645 static bool file_walk_select_do(
646         bContext *C, SpaceFile *sfile,
647         FileSelectParams *params, const int direction,
648         const bool extend, const bool fill)
649 {
650         struct FileList *files = sfile->files;
651         const int numfiles = filelist_files_ensure(files);
652         const bool has_selection = file_is_any_selected(files);
653         const int active_old = params->active_file;
654         int active_new = -1;
655         int other_site = -1; /* file on the other site of active_old */
656
657
658         /* *** get all needed files for handling selection *** */
659
660         if (has_selection) {
661                 ARegion *ar = CTX_wm_region(C);
662                 FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
663                 const int idx_shift = (layout->flag & FILE_LAYOUT_HOR) ? layout->rows : layout->columns;
664
665                 if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_UP) ||
666                     (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_LEFT))
667                 {
668                         active_new = active_old - 1;
669                         other_site = active_old + 1;
670                 }
671                 else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_DOWN) ||
672                          (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_RIGHT))
673                 {
674                         active_new = active_old + 1;
675                         other_site = active_old - 1;
676                 }
677                 else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_LEFT) ||
678                          (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_UP))
679                 {
680                         active_new = active_old - idx_shift;
681                         other_site = active_old + idx_shift;
682                 }
683                 else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_RIGHT) ||
684                          (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_DOWN))
685                 {
686
687                         active_new = active_old + idx_shift;
688                         other_site = active_old - idx_shift;
689                 }
690                 else {
691                         BLI_assert(0);
692                 }
693
694                 if (!IN_RANGE(active_new, 0, numfiles)) {
695                         if (extend) {
696                                 /* extend to invalid file -> abort */
697                                 return false;
698                         }
699                         /* if we don't extend, selecting '..' (index == 0) is allowed so
700                          * using key selection to go to parent directory is possible */
701                         else if (active_new != 0) {
702                                 /* select initial file */
703                                 active_new = active_old;
704                         }
705                 }
706                 if (!IN_RANGE(other_site, 0, numfiles)) {
707                         other_site = -1;
708                 }
709         }
710
711         return file_walk_select_selection_set(
712                     C, sfile, direction, numfiles, active_old, active_new, other_site, has_selection, extend, fill);
713 }
714
715 static int file_walk_select_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
716 {
717         SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C);
718         FileSelectParams *params = sfile->params;
719         const int direction = RNA_enum_get(op->ptr, "direction");
720         const bool extend = RNA_boolean_get(op->ptr, "extend");
721         const bool fill = RNA_boolean_get(op->ptr, "fill");
722
723         if (file_walk_select_do(C, sfile, params, direction, extend, fill)) {
724                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
725                 return OPERATOR_FINISHED;
726         }
727
728         return OPERATOR_CANCELLED;
729 }
730
731 void FILE_OT_select_walk(wmOperatorType *ot)
732 {
733         static EnumPropertyItem direction_items[] = {
734                 {FILE_SELECT_WALK_UP,    "UP",    0, "Prev",  ""},
735                 {FILE_SELECT_WALK_DOWN,  "DOWN",  0, "Next",  ""},
736                 {FILE_SELECT_WALK_LEFT,  "LEFT",  0, "Left",  ""},
737                 {FILE_SELECT_WALK_RIGHT, "RIGHT", 0, "Right", ""},
738                 {0, NULL, 0, NULL, NULL}
739         };
740         PropertyRNA *prop;
741
742         /* identifiers */
743         ot->name = "Walk Select/Deselect File";
744         ot->description = "Select/Deselect files by walking through them";
745         ot->idname = "FILE_OT_select_walk";
746
747         /* api callbacks */
748         ot->invoke = file_walk_select_invoke;
749         ot->poll = ED_operator_file_active;
750
751         /* properties */
752         prop = RNA_def_enum(ot->srna, "direction", direction_items, 0, "Walk Direction",
753                             "Select/Deselect file in this direction");
754         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
755         prop = RNA_def_boolean(ot->srna, "extend", false, "Extend",
756                                "Extend selection instead of deselecting everything first");
757         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
758         prop = RNA_def_boolean(ot->srna, "fill", false, "Fill", "Select everything beginning with the last selection");
759         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
760 }
761
762 static int file_select_all_exec(bContext *C, wmOperator *UNUSED(op))
763 {
764         ScrArea *sa = CTX_wm_area(C);
765         SpaceFile *sfile = CTX_wm_space_file(C);
766         FileSelection sel;
767         const int numfiles = filelist_files_ensure(sfile->files);
768         const bool has_selection = file_is_any_selected(sfile->files);
769
770         sel.first = 0; 
771         sel.last = numfiles - 1;
772
773         /* select all only if previously no file was selected */
774         if (has_selection) {
775                 filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
776                 sfile->params->active_file = -1;
777         }
778         else {
779                 const FileCheckType check_type = (sfile->params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_FILES;
780                 int i;
781
782                 filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_SELECTED, check_type);
783
784                 /* set active_file to first selected */
785                 for (i = 0; i < numfiles; i++) {
786                         if (filelist_entry_select_index_get(sfile->files, i, check_type)) {
787                                 sfile->params->active_file = i;
788                                 break;
789                         }
790                 }
791         }
792
793         file_draw_check(C);
794         WM_event_add_mousemove(C);
795         ED_area_tag_redraw(sa);
796
797         return OPERATOR_FINISHED;
798 }
799
800 void FILE_OT_select_all_toggle(wmOperatorType *ot)
801 {
802         /* identifiers */
803         ot->name = "(De)select All Files";
804         ot->description = "Select or deselect all files";
805         ot->idname = "FILE_OT_select_all_toggle";
806         
807         /* api callbacks */
808         ot->exec = file_select_all_exec;
809         ot->poll = ED_operator_file_active;
810
811         /* properties */
812 }
813
814 /* ---------- BOOKMARKS ----------- */
815
816 /* Note we could get rid of this one, but it's used by some addon so... Does not hurt keeping it around for now. */
817 static int bookmark_select_exec(bContext *C, wmOperator *op)
818 {
819         SpaceFile *sfile = CTX_wm_space_file(C);
820         PropertyRNA *prop;
821
822         if ((prop = RNA_struct_find_property(op->ptr, "dir"))) {
823                 char entry[256];
824                 FileSelectParams *params = sfile->params;
825
826                 RNA_property_string_get(op->ptr, prop, entry);
827                 BLI_strncpy(params->dir, entry, sizeof(params->dir));
828                 BLI_cleanup_dir(G.main->name, params->dir);
829                 ED_file_change_dir(C);
830
831                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
832         }
833         
834         return OPERATOR_FINISHED;
835 }
836
837 void FILE_OT_select_bookmark(wmOperatorType *ot)
838 {
839         PropertyRNA *prop;
840
841         /* identifiers */
842         ot->name = "Select Directory";
843         ot->description = "Select a bookmarked directory";
844         ot->idname = "FILE_OT_select_bookmark";
845         
846         /* api callbacks */
847         ot->exec = bookmark_select_exec;
848         ot->poll = ED_operator_file_active;
849
850         /* properties */
851         prop = RNA_def_string(ot->srna, "dir", NULL, FILE_MAXDIR, "Dir", "");
852         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
853 }
854
855 static int bookmark_add_exec(bContext *C, wmOperator *UNUSED(op))
856 {
857         ScrArea *sa = CTX_wm_area(C);
858         SpaceFile *sfile = CTX_wm_space_file(C);
859         struct FSMenu *fsmenu = ED_fsmenu_get();
860         struct FileSelectParams *params = ED_fileselect_get_params(sfile);
861
862         if (params->dir[0] != '\0') {
863                 char name[FILE_MAX];
864         
865                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, params->dir, NULL, FS_INSERT_SAVE);
866                 BLI_make_file_string("/", name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
867                 fsmenu_write_file(fsmenu, name);
868         }
869
870         ED_area_tag_refresh(sa);
871         ED_area_tag_redraw(sa);
872         return OPERATOR_FINISHED;
873 }
874
875 void FILE_OT_bookmark_add(wmOperatorType *ot)
876 {
877         /* identifiers */
878         ot->name = "Add Bookmark";
879         ot->description = "Add a bookmark for the selected/active directory";
880         ot->idname = "FILE_OT_bookmark_add";
881         
882         /* api callbacks */
883         ot->exec = bookmark_add_exec;
884         ot->poll = ED_operator_file_active;
885 }
886
887 static int bookmark_delete_exec(bContext *C, wmOperator *op)
888 {
889         ScrArea *sa = CTX_wm_area(C);
890         SpaceFile *sfile = CTX_wm_space_file(C);
891         struct FSMenu *fsmenu = ED_fsmenu_get();
892         int nentries = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS);
893
894         PropertyRNA *prop = RNA_struct_find_property(op->ptr, "index");
895
896         if (prop) {
897                 int index;
898                 if (RNA_property_is_set(op->ptr, prop)) {
899                         index = RNA_property_int_get(op->ptr, prop);
900                 }
901                 else {  /* if index unset, use active bookmark... */
902                         index = sfile->bookmarknr;
903                 }
904                 if ((index > -1) && (index < nentries)) {
905                         char name[FILE_MAX];
906                         
907                         fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index);
908                         BLI_make_file_string("/", name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
909                         fsmenu_write_file(fsmenu, name);
910                         ED_area_tag_refresh(sa);
911                         ED_area_tag_redraw(sa);
912                 }
913         }
914
915         return OPERATOR_FINISHED;
916 }
917
918 void FILE_OT_bookmark_delete(wmOperatorType *ot)
919 {
920         PropertyRNA *prop;
921
922         /* identifiers */
923         ot->name = "Delete Bookmark";
924         ot->description = "Delete selected bookmark";
925         ot->idname = "FILE_OT_bookmark_delete";
926         
927         /* api callbacks */
928         ot->exec = bookmark_delete_exec;
929         ot->poll = ED_operator_file_active;
930
931         /* properties */
932         prop = RNA_def_int(ot->srna, "index", -1, -1, 20000, "Index", "", -1, 20000);
933         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
934 }
935
936 static int bookmark_cleanup_exec(bContext *C, wmOperator *UNUSED(op))
937 {
938         ScrArea *sa = CTX_wm_area(C);
939         struct FSMenu *fsmenu = ED_fsmenu_get();
940         struct FSMenuEntry *fsme_next, *fsme = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS);
941         int index;
942         bool changed = false;
943
944         for (index = 0; fsme; fsme = fsme_next) {
945                 fsme_next = fsme->next;
946
947                 if (!BLI_is_dir(fsme->path)) {
948                         fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index);
949                         changed = true;
950                 }
951                 else {
952                         index++;
953                 }
954         }
955
956         if (changed) {
957                 char name[FILE_MAX];
958
959                 BLI_make_file_string("/", name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
960                 fsmenu_write_file(fsmenu, name);
961                 fsmenu_refresh_bookmarks_status(fsmenu);
962                 ED_area_tag_refresh(sa);
963                 ED_area_tag_redraw(sa);
964         }
965
966         return OPERATOR_FINISHED;
967 }
968
969 void FILE_OT_bookmark_cleanup(wmOperatorType *ot)
970 {
971         /* identifiers */
972         ot->name = "Cleanup Bookmarks";
973         ot->description = "Delete all invalid bookmarks";
974         ot->idname = "FILE_OT_bookmark_cleanup";
975
976         /* api callbacks */
977         ot->exec = bookmark_cleanup_exec;
978         ot->poll = ED_operator_file_active;
979
980         /* properties */
981 }
982
983 enum {
984         FILE_BOOKMARK_MOVE_TOP = -2,
985         FILE_BOOKMARK_MOVE_UP = -1,
986         FILE_BOOKMARK_MOVE_DOWN = 1,
987         FILE_BOOKMARK_MOVE_BOTTOM = 2,
988 };
989
990 static int bookmark_move_exec(bContext *C, wmOperator *op)
991 {
992         ScrArea *sa = CTX_wm_area(C);
993         SpaceFile *sfile = CTX_wm_space_file(C);
994         struct FSMenu *fsmenu = ED_fsmenu_get();
995         struct FSMenuEntry *fsmentry = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS);
996         const struct FSMenuEntry *fsmentry_org = fsmentry;
997
998         char fname[FILE_MAX];
999
1000         const int direction = RNA_enum_get(op->ptr, "direction");
1001         const int totitems = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS);
1002         const int act_index = sfile->bookmarknr;
1003         int new_index;
1004
1005         if (totitems < 2) {
1006                 return OPERATOR_CANCELLED;
1007         }
1008
1009         switch (direction) {
1010                 case FILE_BOOKMARK_MOVE_TOP:
1011                         new_index = 0;
1012                         break;
1013                 case FILE_BOOKMARK_MOVE_BOTTOM:
1014                         new_index = totitems - 1;
1015                         break;
1016                 case FILE_BOOKMARK_MOVE_UP:
1017                 case FILE_BOOKMARK_MOVE_DOWN:
1018                 default:
1019                         new_index = (totitems + act_index + direction) % totitems;
1020                         break;
1021         }
1022
1023         if (new_index == act_index) {
1024                 return OPERATOR_CANCELLED;
1025         }
1026
1027         BLI_linklist_move_item((LinkNode **)&fsmentry, act_index, new_index);
1028         if (fsmentry != fsmentry_org) {
1029                 ED_fsmenu_set_category(fsmenu, FS_CATEGORY_BOOKMARKS, fsmentry);
1030         }
1031
1032         /* Need to update active bookmark number. */
1033         sfile->bookmarknr = new_index;
1034
1035         BLI_make_file_string("/", fname, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
1036         fsmenu_write_file(fsmenu, fname);
1037
1038         ED_area_tag_redraw(sa);
1039         return OPERATOR_FINISHED;
1040 }
1041
1042 void FILE_OT_bookmark_move(wmOperatorType *ot)
1043 {
1044         static EnumPropertyItem slot_move[] = {
1045                 {FILE_BOOKMARK_MOVE_TOP, "TOP", 0, "Top", "Top of the list"},
1046                 {FILE_BOOKMARK_MOVE_UP, "UP", 0, "Up", ""},
1047                 {FILE_BOOKMARK_MOVE_DOWN, "DOWN", 0, "Down", ""},
1048                 {FILE_BOOKMARK_MOVE_BOTTOM, "BOTTOM", 0, "Bottom", "Bottom of the list"},
1049                 { 0, NULL, 0, NULL, NULL }
1050         };
1051
1052         /* identifiers */
1053         ot->name = "Move Bookmark";
1054         ot->idname = "FILE_OT_bookmark_move";
1055         ot->description = "Move the active bookmark up/down in the list";
1056
1057         /* api callbacks */
1058         ot->poll = ED_operator_file_active;
1059         ot->exec = bookmark_move_exec;
1060
1061         /* flags */
1062         ot->flag = OPTYPE_REGISTER;  /* No undo! */
1063
1064         RNA_def_enum(ot->srna, "direction", slot_move, 0, "Direction", "Direction to move, UP or DOWN");
1065 }
1066
1067 static int reset_recent_exec(bContext *C, wmOperator *UNUSED(op))
1068 {
1069         ScrArea *sa = CTX_wm_area(C);
1070         char name[FILE_MAX];
1071         struct FSMenu *fsmenu = ED_fsmenu_get();
1072         
1073         while (ED_fsmenu_get_entry(fsmenu, FS_CATEGORY_RECENT, 0) != NULL) {
1074                 fsmenu_remove_entry(fsmenu, FS_CATEGORY_RECENT, 0);
1075         }
1076         BLI_make_file_string("/", name, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL), BLENDER_BOOKMARK_FILE);
1077         fsmenu_write_file(fsmenu, name);
1078         ED_area_tag_redraw(sa);
1079
1080         return OPERATOR_FINISHED;
1081 }
1082
1083 void FILE_OT_reset_recent(wmOperatorType *ot)
1084 {
1085         /* identifiers */
1086         ot->name = "Reset Recent";
1087         ot->description = "Reset Recent files";
1088         ot->idname = "FILE_OT_reset_recent";
1089         
1090         /* api callbacks */
1091         ot->exec = reset_recent_exec;
1092         ot->poll = ED_operator_file_active;
1093
1094 }
1095
1096 int file_highlight_set(SpaceFile *sfile, ARegion *ar, int mx, int my)
1097 {
1098         View2D *v2d = &ar->v2d;
1099         FileSelectParams *params;
1100         int numfiles, origfile;
1101
1102         if (sfile == NULL || sfile->files == NULL) return 0;
1103
1104         numfiles = filelist_files_ensure(sfile->files);
1105         params = ED_fileselect_get_params(sfile);
1106
1107         origfile = params->highlight_file;
1108
1109         mx -= ar->winrct.xmin;
1110         my -= ar->winrct.ymin;
1111
1112         if (BLI_rcti_isect_pt(&ar->v2d.mask, mx, my)) {
1113                 float fx, fy;
1114                 int highlight_file;
1115
1116                 UI_view2d_region_to_view(v2d, mx, my, &fx, &fy);
1117
1118                 highlight_file = ED_fileselect_layout_offset(sfile->layout, (int)(v2d->tot.xmin + fx), (int)(v2d->tot.ymax - fy));
1119
1120                 if ((highlight_file >= 0) && (highlight_file < numfiles))
1121                         params->highlight_file = highlight_file;
1122                 else
1123                         params->highlight_file = -1;
1124         }
1125         else
1126                 params->highlight_file = -1;
1127
1128         return (params->highlight_file != origfile);
1129 }
1130
1131 static int file_highlight_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1132 {
1133         ARegion *ar = CTX_wm_region(C);
1134         SpaceFile *sfile = CTX_wm_space_file(C);
1135
1136         if (!file_highlight_set(sfile, ar, event->x, event->y))
1137                 return OPERATOR_CANCELLED;
1138
1139         ED_area_tag_redraw(CTX_wm_area(C));
1140         
1141         return OPERATOR_FINISHED;
1142 }
1143
1144 void FILE_OT_highlight(struct wmOperatorType *ot)
1145 {
1146         /* identifiers */
1147         ot->name = "Highlight File";
1148         ot->description = "Highlight selected file(s)";
1149         ot->idname = "FILE_OT_highlight";
1150         
1151         /* api callbacks */
1152         ot->invoke = file_highlight_invoke;
1153         ot->poll = ED_operator_file_active;
1154 }
1155
1156 int file_cancel_exec(bContext *C, wmOperator *UNUSED(unused))
1157 {
1158         wmWindowManager *wm = CTX_wm_manager(C);
1159         SpaceFile *sfile = CTX_wm_space_file(C);
1160         wmOperator *op = sfile->op;
1161         
1162         sfile->op = NULL;
1163
1164         WM_event_fileselect_event(wm, op, EVT_FILESELECT_CANCEL);
1165         
1166         return OPERATOR_FINISHED;
1167 }
1168
1169 static int file_operator_poll(bContext *C)
1170 {
1171         int poll = ED_operator_file_active(C);
1172         SpaceFile *sfile = CTX_wm_space_file(C);
1173
1174         if (!sfile || !sfile->op) poll = 0;
1175
1176         return poll;
1177 }
1178
1179 void FILE_OT_cancel(struct wmOperatorType *ot)
1180 {
1181         /* identifiers */
1182         ot->name = "Cancel File Load";
1183         ot->description = "Cancel loading of selected file";
1184         ot->idname = "FILE_OT_cancel";
1185         
1186         /* api callbacks */
1187         ot->exec = file_cancel_exec;
1188         ot->poll = file_operator_poll;
1189 }
1190
1191
1192 void file_sfile_to_operator_ex(wmOperator *op, SpaceFile *sfile, char *filepath)
1193 {
1194         PropertyRNA *prop;
1195
1196         BLI_join_dirfile(filepath, FILE_MAX, sfile->params->dir, sfile->params->file); /* XXX, not real length */
1197
1198         if ((prop = RNA_struct_find_property(op->ptr, "relative_path"))) {
1199                 if (RNA_property_boolean_get(op->ptr, prop)) {
1200                         BLI_path_rel(filepath, G.main->name);
1201                 }
1202         }
1203
1204         if ((prop = RNA_struct_find_property(op->ptr, "filename"))) {
1205                 RNA_property_string_set(op->ptr, prop, sfile->params->file);
1206         }
1207         if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
1208                 RNA_property_string_set(op->ptr, prop, sfile->params->dir);
1209         }
1210         if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) {
1211                 RNA_property_string_set(op->ptr, prop, filepath);
1212         }
1213         
1214         /* some ops have multiple files to select */
1215         /* this is called on operators check() so clear collections first since
1216          * they may be already set. */
1217         {
1218                 int i, numfiles = filelist_files_ensure(sfile->files);
1219
1220                 if ((prop = RNA_struct_find_property(op->ptr, "files"))) {
1221                         PointerRNA itemptr;
1222                         int num_files = 0;
1223                         RNA_property_collection_clear(op->ptr, prop);
1224                         for (i = 0; i < numfiles; i++) {
1225                                 if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) {
1226                                         FileDirEntry *file = filelist_file(sfile->files, i);
1227                                         RNA_property_collection_add(op->ptr, prop, &itemptr);
1228                                         RNA_string_set(&itemptr, "name", file->relpath);
1229                                         num_files++;
1230                                 }
1231                         }
1232                         /* make sure the file specified in the filename button is added even if no files selected */
1233                         if (0 == num_files) {
1234                                 RNA_property_collection_add(op->ptr, prop, &itemptr);
1235                                 RNA_string_set(&itemptr, "name", sfile->params->file);
1236                         }
1237                 }
1238
1239                 if ((prop = RNA_struct_find_property(op->ptr, "dirs"))) {
1240                         PointerRNA itemptr;
1241                         int num_dirs = 0;
1242                         RNA_property_collection_clear(op->ptr, prop);
1243                         for (i = 0; i < numfiles; i++) {
1244                                 if (filelist_entry_select_index_get(sfile->files, i, CHECK_DIRS)) {
1245                                         FileDirEntry *file = filelist_file(sfile->files, i);
1246                                         RNA_property_collection_add(op->ptr, prop, &itemptr);
1247                                         RNA_string_set(&itemptr, "name", file->relpath);
1248                                         num_dirs++;
1249                                 }
1250                         }
1251                         
1252                         /* make sure the directory specified in the button is added even if no directory selected */
1253                         if (0 == num_dirs) {
1254                                 RNA_property_collection_add(op->ptr, prop, &itemptr);
1255                                 RNA_string_set(&itemptr, "name", sfile->params->dir);
1256                         }
1257                 }
1258
1259
1260         }
1261 }
1262 void file_sfile_to_operator(wmOperator *op, SpaceFile *sfile)
1263 {
1264         char filepath[FILE_MAX];
1265
1266         file_sfile_to_operator_ex(op, sfile, filepath);
1267 }
1268
1269 void file_operator_to_sfile(SpaceFile *sfile, wmOperator *op)
1270 {
1271         PropertyRNA *prop;
1272
1273         /* If neither of the above are set, split the filepath back */
1274         if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) {
1275                 char filepath[FILE_MAX];
1276                 RNA_property_string_get(op->ptr, prop, filepath);
1277                 BLI_split_dirfile(filepath, sfile->params->dir, sfile->params->file, sizeof(sfile->params->dir), sizeof(sfile->params->file));
1278         }
1279         else {
1280                 if ((prop = RNA_struct_find_property(op->ptr, "filename"))) {
1281                         RNA_property_string_get(op->ptr, prop, sfile->params->file);
1282                 }
1283                 if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
1284                         RNA_property_string_get(op->ptr, prop, sfile->params->dir);
1285                 }
1286         }
1287         
1288         /* we could check for relative_path property which is used when converting
1289          * in the other direction but doesnt hurt to do this every time */
1290         BLI_path_abs(sfile->params->dir, G.main->name);
1291
1292         /* XXX, files and dirs updates missing, not really so important though */
1293 }
1294
1295 /**
1296  * Use to set the file selector path from some arbitrary source.
1297  */
1298 void file_sfile_filepath_set(SpaceFile *sfile, const char *filepath)
1299 {
1300         BLI_assert(BLI_exists(filepath));
1301
1302         if (BLI_is_dir(filepath)) {
1303                 BLI_strncpy(sfile->params->dir, filepath, sizeof(sfile->params->dir));
1304                 sfile->params->file[0] = '\0';
1305         }
1306         else {
1307                 if ((sfile->params->flag & FILE_DIRSEL_ONLY) == 0) {
1308                         BLI_split_dirfile(filepath, sfile->params->dir, sfile->params->file,
1309                                           sizeof(sfile->params->dir), sizeof(sfile->params->file));
1310                 }
1311                 else {
1312                         BLI_split_dir_part(filepath, sfile->params->dir, sizeof(sfile->params->dir));
1313                 }
1314         }
1315 }
1316
1317 void file_draw_check(bContext *C)
1318 {
1319         SpaceFile *sfile = CTX_wm_space_file(C);
1320         wmOperator *op = sfile->op;
1321         if (op) { /* fail on reload */
1322                 if (op->type->check) {
1323                         file_sfile_to_operator(op, sfile);
1324                         
1325                         /* redraw */
1326                         if (op->type->check(C, op)) {
1327                                 file_operator_to_sfile(sfile, op);
1328         
1329                                 /* redraw, else the changed settings wont get updated */
1330                                 ED_area_tag_redraw(CTX_wm_area(C));
1331                         }
1332                 }
1333         }
1334 }
1335
1336 /* for use with; UI_block_func_set */
1337 void file_draw_check_cb(bContext *C, void *UNUSED(arg1), void *UNUSED(arg2))
1338 {
1339         file_draw_check(C);
1340 }
1341
1342 bool file_draw_check_exists(SpaceFile *sfile)
1343 {
1344         if (sfile->op) { /* fails on reload */
1345                 PropertyRNA *prop;
1346                 if ((prop = RNA_struct_find_property(sfile->op->ptr, "check_existing"))) {
1347                         if (RNA_property_boolean_get(sfile->op->ptr, prop)) {
1348                                 char filepath[FILE_MAX];
1349                                 BLI_join_dirfile(filepath, sizeof(filepath), sfile->params->dir, sfile->params->file);
1350                                 if (BLI_is_file(filepath)) {
1351                                         return true;
1352                                 }
1353                         }
1354                 }
1355         }
1356
1357         return false;
1358 }
1359
1360 int file_exec(bContext *C, wmOperator *exec_op)
1361 {
1362         wmWindowManager *wm = CTX_wm_manager(C);
1363         SpaceFile *sfile = CTX_wm_space_file(C);
1364         const struct FileDirEntry *file = filelist_file(sfile->files, sfile->params->active_file);
1365         char filepath[FILE_MAX];
1366
1367         /* directory change */
1368         if (file && (file->typeflag & FILE_TYPE_DIR)) {
1369                 if (!file->relpath) {
1370                         return OPERATOR_CANCELLED;
1371                 }
1372
1373                 if (FILENAME_IS_PARENT(file->relpath)) {
1374                         BLI_parent_dir(sfile->params->dir);
1375                 }
1376                 else {
1377                         BLI_cleanup_path(G.main->name, sfile->params->dir);
1378                         BLI_path_append(sfile->params->dir, sizeof(sfile->params->dir) - 1, file->relpath);
1379                         BLI_add_slash(sfile->params->dir);
1380                 }
1381
1382                 ED_file_change_dir(C);
1383         }
1384         /* opening file - sends events now, so things get handled on windowqueue level */
1385         else if (sfile->op) {
1386                 wmOperator *op = sfile->op;
1387         
1388                 /* when used as a macro, for doubleclick, 
1389                  * to prevent closing when doubleclicking on .. item */
1390                 if (RNA_boolean_get(exec_op->ptr, "need_active")) {
1391                         const int numfiles = filelist_files_ensure(sfile->files);
1392                         int i, active = 0;
1393                         
1394                         for (i = 0; i < numfiles; i++) {
1395                                 if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) {
1396                                         active = 1;
1397                                         break;
1398                                 }
1399                         }
1400                         if (active == 0)
1401                                 return OPERATOR_CANCELLED;
1402                 }
1403                 
1404                 sfile->op = NULL;
1405
1406                 file_sfile_to_operator_ex(op, sfile, filepath);
1407
1408                 if (BLI_exists(sfile->params->dir)) {
1409                         fsmenu_insert_entry(ED_fsmenu_get(), FS_CATEGORY_RECENT, sfile->params->dir, NULL,
1410                                             FS_INSERT_SAVE | FS_INSERT_FIRST);
1411                 }
1412
1413                 BLI_make_file_string(G.main->name, filepath, BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
1414                                      BLENDER_BOOKMARK_FILE);
1415                 fsmenu_write_file(ED_fsmenu_get(), filepath);
1416                 WM_event_fileselect_event(wm, op, EVT_FILESELECT_EXEC);
1417
1418         }
1419
1420         return OPERATOR_FINISHED;
1421 }
1422
1423 void FILE_OT_execute(struct wmOperatorType *ot)
1424 {
1425         PropertyRNA *prop;
1426
1427         /* identifiers */
1428         ot->name = "Execute File Window";
1429         ot->description = "Execute selected file";
1430         ot->idname = "FILE_OT_execute";
1431         
1432         /* api callbacks */
1433         ot->exec = file_exec;
1434         ot->poll = file_operator_poll; 
1435
1436         /* properties */
1437         prop = RNA_def_boolean(ot->srna, "need_active", 0, "Need Active",
1438                                "Only execute if there's an active selected file in the file list");
1439         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1440 }
1441
1442
1443 int file_parent_exec(bContext *C, wmOperator *UNUSED(unused))
1444 {
1445         SpaceFile *sfile = CTX_wm_space_file(C);
1446         
1447         if (sfile->params) {
1448                 if (BLI_parent_dir(sfile->params->dir)) {
1449                         BLI_cleanup_dir(G.main->name, sfile->params->dir);
1450                         ED_file_change_dir(C);
1451                         if (sfile->params->recursion_level > 1) {
1452                                 /* Disable 'dirtree' recursion when going up in tree. */
1453                                 sfile->params->recursion_level = 0;
1454                                 filelist_setrecursion(sfile->files, sfile->params->recursion_level);
1455                         }
1456                         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1457                 }
1458         }
1459         
1460         return OPERATOR_FINISHED;
1461
1462 }
1463
1464
1465 void FILE_OT_parent(struct wmOperatorType *ot)
1466 {
1467         /* identifiers */
1468         ot->name = "Parent File";
1469         ot->description = "Move to parent directory";
1470         ot->idname = "FILE_OT_parent";
1471         
1472         /* api callbacks */
1473         ot->exec = file_parent_exec;
1474         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
1475 }
1476
1477
1478 static int file_refresh_exec(bContext *C, wmOperator *UNUSED(unused))
1479 {
1480         wmWindowManager *wm = CTX_wm_manager(C);
1481         SpaceFile *sfile = CTX_wm_space_file(C);
1482         ScrArea *sa = CTX_wm_area(C);
1483         struct FSMenu *fsmenu = ED_fsmenu_get();
1484
1485         ED_fileselect_clear(wm, sa, sfile);
1486
1487         /* refresh system directory menu */
1488         fsmenu_refresh_system_category(fsmenu);
1489
1490         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1491
1492         return OPERATOR_FINISHED;
1493
1494 }
1495
1496 void FILE_OT_previous(struct wmOperatorType *ot)
1497 {
1498         /* identifiers */
1499         ot->name = "Previous Folder";
1500         ot->description = "Move to previous folder";
1501         ot->idname = "FILE_OT_previous";
1502         
1503         /* api callbacks */
1504         ot->exec = file_previous_exec;
1505         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
1506 }
1507
1508 int file_previous_exec(bContext *C, wmOperator *UNUSED(unused))
1509 {
1510         SpaceFile *sfile = CTX_wm_space_file(C);
1511
1512         if (sfile->params) {
1513                 if (!sfile->folders_next)
1514                         sfile->folders_next = folderlist_new();
1515
1516                 folderlist_pushdir(sfile->folders_next, sfile->params->dir);
1517                 folderlist_popdir(sfile->folders_prev, sfile->params->dir);
1518                 folderlist_pushdir(sfile->folders_next, sfile->params->dir);
1519
1520                 ED_file_change_dir(C);
1521         }
1522         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1523
1524         return OPERATOR_FINISHED;
1525 }
1526
1527 void FILE_OT_next(struct wmOperatorType *ot)
1528 {
1529         /* identifiers */
1530         ot->name = "Next Folder";
1531         ot->description = "Move to next folder";
1532         ot->idname = "FILE_OT_next";
1533         
1534         /* api callbacks */
1535         ot->exec = file_next_exec;
1536         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
1537 }
1538
1539 int file_next_exec(bContext *C, wmOperator *UNUSED(unused))
1540 {
1541         SpaceFile *sfile = CTX_wm_space_file(C);
1542         if (sfile->params) {
1543                 if (!sfile->folders_next)
1544                         sfile->folders_next = folderlist_new();
1545
1546                 folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
1547                 folderlist_popdir(sfile->folders_next, sfile->params->dir);
1548
1549                 // update folders_prev so we can check for it in folderlist_clear_next()
1550                 folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
1551
1552                 ED_file_change_dir(C);
1553         }
1554         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1555
1556         return OPERATOR_FINISHED;
1557 }
1558
1559
1560 /* only meant for timer usage */
1561 static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
1562 {
1563         ScrArea *sa = CTX_wm_area(C);
1564         SpaceFile *sfile = CTX_wm_space_file(C);
1565         ARegion *ar, *oldar = CTX_wm_region(C);
1566         int offset;
1567         int numfiles, numfiles_layout;
1568         int edit_idx = 0;
1569         int i;
1570
1571         /* escape if not our timer */
1572         if (sfile->smoothscroll_timer == NULL || sfile->smoothscroll_timer != event->customdata)
1573                 return OPERATOR_PASS_THROUGH;
1574         
1575         numfiles = filelist_files_ensure(sfile->files);
1576
1577         /* check if we are editing a name */
1578         for (i = 0; i < numfiles; ++i) {
1579                 if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL) ) {
1580                         edit_idx = i;
1581                         break;
1582                 }
1583         }
1584
1585         /* if we are not editing, we are done */
1586         if (0 == edit_idx) {
1587                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
1588                 sfile->smoothscroll_timer = NULL;
1589                 return OPERATOR_PASS_THROUGH;
1590         }
1591
1592         /* we need the correct area for scrolling */
1593         ar = BKE_area_find_region_type(sa, RGN_TYPE_WINDOW);
1594         if (!ar || ar->regiontype != RGN_TYPE_WINDOW) {
1595                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
1596                 sfile->smoothscroll_timer = NULL;
1597                 return OPERATOR_PASS_THROUGH;
1598         }
1599
1600         offset = ED_fileselect_layout_offset(sfile->layout, (int)ar->v2d.cur.xmin, (int)-ar->v2d.cur.ymax);
1601         if (offset < 0) offset = 0;
1602
1603         /* scroll offset is the first file in the row/column we are editing in */
1604         if (sfile->scroll_offset == 0) {
1605                 if (sfile->layout->flag & FILE_LAYOUT_HOR) {
1606                         sfile->scroll_offset = (edit_idx / sfile->layout->rows) * sfile->layout->rows;
1607                         if (sfile->scroll_offset <= offset) sfile->scroll_offset -= sfile->layout->rows;
1608                 }
1609                 else {
1610                         sfile->scroll_offset = (edit_idx / sfile->layout->columns) * sfile->layout->columns;
1611                         if (sfile->scroll_offset <= offset) sfile->scroll_offset -= sfile->layout->columns;
1612                 }
1613         }
1614         
1615         numfiles_layout = ED_fileselect_layout_numfiles(sfile->layout, ar);
1616         
1617         /* check if we have reached our final scroll position */
1618         if ( (sfile->scroll_offset >= offset) && (sfile->scroll_offset < offset + numfiles_layout) ) {
1619                 WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
1620                 sfile->smoothscroll_timer = NULL;
1621                 return OPERATOR_FINISHED;
1622         }
1623
1624         /* temporarily set context to the main window region, 
1625          * so the scroll operators work */
1626         CTX_wm_region_set(C, ar);
1627         
1628         /* scroll one step in the desired direction */
1629         if (sfile->scroll_offset < offset) {
1630                 if (sfile->layout->flag & FILE_LAYOUT_HOR) {
1631                         WM_operator_name_call(C, "VIEW2D_OT_scroll_left", 0, NULL);
1632                 }
1633                 else {
1634                         WM_operator_name_call(C, "VIEW2D_OT_scroll_up", 0, NULL);
1635                 }
1636                 
1637         }
1638         else {
1639                 if (sfile->layout->flag & FILE_LAYOUT_HOR) {
1640                         WM_operator_name_call(C, "VIEW2D_OT_scroll_right", 0, NULL);
1641                 }
1642                 else {
1643                         WM_operator_name_call(C, "VIEW2D_OT_scroll_down", 0, NULL);
1644                 }
1645         }
1646         
1647         ED_region_tag_redraw(ar);
1648         
1649         /* and restore context */
1650         CTX_wm_region_set(C, oldar);
1651         
1652         return OPERATOR_FINISHED;
1653 }
1654
1655
1656 void FILE_OT_smoothscroll(wmOperatorType *ot)
1657 {
1658         
1659         /* identifiers */
1660         ot->name = "Smooth Scroll";
1661         ot->idname = "FILE_OT_smoothscroll";
1662         ot->description = "Smooth scroll to make editable file visible";
1663         
1664         /* api callbacks */
1665         ot->invoke = file_smoothscroll_invoke;
1666         
1667         ot->poll = ED_operator_file_active;
1668 }
1669
1670
1671 static int filepath_drop_exec(bContext *C, wmOperator *op)
1672 {
1673         SpaceFile *sfile = CTX_wm_space_file(C);
1674
1675         if (sfile) {
1676                 char filepath[FILE_MAX];
1677
1678                 RNA_string_get(op->ptr, "filepath", filepath);
1679                 if (!BLI_exists(filepath)) {
1680                         BKE_report(op->reports, RPT_ERROR, "File does not exist");
1681                         return OPERATOR_CANCELLED;
1682                 }
1683
1684                 file_sfile_filepath_set(sfile, filepath);
1685
1686                 if (sfile->op) {
1687                         file_sfile_to_operator(sfile->op, sfile);
1688                         file_draw_check(C);
1689                 }
1690
1691                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
1692                 return OPERATOR_FINISHED;
1693         }
1694
1695         return OPERATOR_CANCELLED;
1696 }
1697
1698 void FILE_OT_filepath_drop(wmOperatorType *ot)
1699 {
1700         ot->name = "File Selector Drop";
1701         ot->description = "";
1702         ot->idname = "FILE_OT_filepath_drop";
1703
1704         ot->exec = filepath_drop_exec;
1705         ot->poll = WM_operator_winactive;
1706
1707         RNA_def_string_file_path(ot->srna, "filepath", "Path", FILE_MAX, "", "");
1708 }
1709
1710 /* create a new, non-existing folder name, returns 1 if successful, 0 if name couldn't be created.
1711  * The actual name is returned in 'name', 'folder' contains the complete path, including the new folder name.
1712  */
1713 static int new_folder_path(const char *parent, char *folder, char *name)
1714 {
1715         int i = 1;
1716         int len = 0;
1717
1718         BLI_strncpy(name, "New Folder", FILE_MAXFILE);
1719         BLI_join_dirfile(folder, FILE_MAX, parent, name); /* XXX, not real length */
1720         /* check whether folder with the name already exists, in this case
1721          * add number to the name. Check length of generated name to avoid
1722          * crazy case of huge number of folders each named 'New Folder (x)' */
1723         while (BLI_exists(folder) && (len < FILE_MAXFILE)) {
1724                 len = BLI_snprintf(name, FILE_MAXFILE, "New Folder(%d)", i);
1725                 BLI_join_dirfile(folder, FILE_MAX, parent, name); /* XXX, not real length */
1726                 i++;
1727         }
1728
1729         return (len < FILE_MAXFILE);
1730 }
1731
1732 int file_directory_new_exec(bContext *C, wmOperator *op)
1733 {
1734         char name[FILE_MAXFILE];
1735         char path[FILE_MAX];
1736         bool generate_name = true;
1737         PropertyRNA *prop;
1738
1739         wmWindowManager *wm = CTX_wm_manager(C);
1740         SpaceFile *sfile = CTX_wm_space_file(C);
1741         ScrArea *sa = CTX_wm_area(C);
1742         
1743         if (!sfile->params) {
1744                 BKE_report(op->reports, RPT_WARNING, "No parent directory given");
1745                 return OPERATOR_CANCELLED;
1746         }
1747         
1748         path[0] = '\0';
1749
1750         if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
1751                 RNA_property_string_get(op->ptr, prop, path);
1752                 if (path[0] != '\0') {
1753                         generate_name = false;
1754                 }
1755         }
1756
1757         if (generate_name) {
1758                 /* create a new, non-existing folder name */
1759                 if (!new_folder_path(sfile->params->dir, path, name)) {
1760                         BKE_report(op->reports, RPT_ERROR, "Could not create new folder name");
1761                         return OPERATOR_CANCELLED;
1762                 }
1763         }
1764         else { /* We assume we are able to generate a valid name! */
1765                 char org_path[FILE_MAX];
1766
1767                 BLI_strncpy(org_path, path, sizeof(org_path));
1768                 if (BLI_path_make_safe(path)) {
1769                         BKE_reportf(op->reports, RPT_WARNING, "'%s' given path is OS-invalid, creating '%s' path instead",
1770                                     org_path, path);
1771                 }
1772         }
1773
1774         /* create the file */
1775         errno = 0;
1776         if (!BLI_dir_create_recursive(path) ||
1777             /* Should no more be needed,
1778              * now that BLI_dir_create_recursive returns a success state - but kept just in case. */
1779             !BLI_exists(path))
1780         {
1781                 BKE_reportf(op->reports, RPT_ERROR,
1782                             "Could not create new folder: %s",
1783                             errno ? strerror(errno) : "unknown error");
1784                 return OPERATOR_CANCELLED;
1785         }
1786
1787
1788         /* now remember file to jump into editing */
1789         BLI_strncpy(sfile->params->renamefile, name, FILE_MAXFILE);
1790
1791         /* set timer to smoothly view newly generated file */
1792         sfile->smoothscroll_timer = WM_event_add_timer(wm, CTX_wm_window(C), TIMER1, 1.0 / 1000.0);  /* max 30 frs/sec */
1793         sfile->scroll_offset = 0;
1794
1795         /* reload dir to make sure we're seeing what's in the directory */
1796         ED_fileselect_clear(wm, sa, sfile);
1797
1798         if (RNA_boolean_get(op->ptr, "open")) {
1799                 BLI_strncpy(sfile->params->dir, path, sizeof(sfile->params->dir));
1800                 ED_file_change_dir(C);
1801         }
1802
1803         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1804
1805         return OPERATOR_FINISHED;
1806 }
1807
1808
1809 void FILE_OT_directory_new(struct wmOperatorType *ot)
1810 {
1811         PropertyRNA *prop;
1812
1813         /* identifiers */
1814         ot->name = "Create New Directory";
1815         ot->description = "Create a new directory";
1816         ot->idname = "FILE_OT_directory_new";
1817         
1818         /* api callbacks */
1819         ot->invoke = WM_operator_confirm;
1820         ot->exec = file_directory_new_exec;
1821         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
1822
1823         prop = RNA_def_string_dir_path(ot->srna, "directory", NULL, FILE_MAX, "Directory", "Name of new directory");
1824         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1825         prop = RNA_def_boolean(ot->srna, "open", false, "Open", "Open new directory");
1826         RNA_def_property_flag(prop, PROP_SKIP_SAVE);
1827 }
1828
1829
1830 /* TODO This should go to BLI_path_utils. */
1831 static void file_expand_directory(bContext *C)
1832 {
1833         SpaceFile *sfile = CTX_wm_space_file(C);
1834         
1835         if (sfile->params) {
1836                 /* TODO, what about // when relbase isn't valid? */
1837                 if (G.relbase_valid && BLI_path_is_rel(sfile->params->dir)) {
1838                         BLI_path_abs(sfile->params->dir, G.main->name);
1839                 }
1840                 else if (sfile->params->dir[0] == '~') {
1841                         char tmpstr[sizeof(sfile->params->dir) - 1];
1842                         BLI_strncpy(tmpstr, sfile->params->dir + 1, sizeof(tmpstr));
1843                         BLI_join_dirfile(sfile->params->dir, sizeof(sfile->params->dir), BKE_appdir_folder_default(), tmpstr);
1844                 }
1845
1846                 else if (sfile->params->dir[0] == '\0')
1847 #ifndef WIN32
1848                 {
1849                         sfile->params->dir[0] = '/';
1850                         sfile->params->dir[1] = '\0';
1851                 }
1852 #else
1853                 {
1854                         get_default_root(sfile->params->dir);
1855                 }
1856                 /* change "C:" --> "C:\", [#28102] */
1857                 else if ((isalpha(sfile->params->dir[0]) &&
1858                           (sfile->params->dir[1] == ':')) &&
1859                          (sfile->params->dir[2] == '\0'))
1860                 {
1861                         sfile->params->dir[2] = '\\';
1862                         sfile->params->dir[3] = '\0';
1863                 }
1864                 else if (BLI_path_is_unc(sfile->params->dir)) {
1865                         BLI_cleanup_unc(sfile->params->dir, FILE_MAX_LIBEXTRA);
1866                 }
1867 #endif
1868         }
1869 }
1870
1871 /* TODO check we still need this, it's annoying to have OS-specific code here... :/ */
1872 #if defined(WIN32)
1873 static bool can_create_dir(const char *dir)
1874 {
1875         /* for UNC paths we need to check whether the parent of the new
1876          * directory is a proper directory itself and not a share or the
1877          * UNC root (server name) itself. Calling BLI_is_dir does this
1878          */
1879         if (BLI_path_is_unc(dir)) {
1880                 char parent[PATH_MAX];
1881                 BLI_strncpy(parent, dir, PATH_MAX);
1882                 BLI_parent_dir(parent);
1883                 return BLI_is_dir(parent);
1884         }
1885         return true;
1886 }
1887 #endif
1888
1889 void file_directory_enter_handle(bContext *C, void *UNUSED(arg_unused), void *UNUSED(arg_but))
1890 {
1891         SpaceFile *sfile = CTX_wm_space_file(C);
1892         
1893         if (sfile->params) {
1894                 file_expand_directory(C);
1895
1896                 /* special case, user may have pasted a filepath into the directory */
1897                 if (!filelist_is_dir(sfile->files, sfile->params->dir)) {
1898                         char tdir[FILE_MAX_LIBEXTRA];
1899                         char *group, *name;
1900
1901                         if (BLI_is_file(sfile->params->dir)) {
1902                                 char path[sizeof(sfile->params->dir)];
1903                                 BLI_strncpy(path, sfile->params->dir, sizeof(path));
1904                                 BLI_split_dirfile(path, sfile->params->dir, sfile->params->file,
1905                                                   sizeof(sfile->params->dir), sizeof(sfile->params->file));
1906                         }
1907                         else if (BLO_library_path_explode(sfile->params->dir, tdir, &group, &name)) {
1908                                 if (group) {
1909                                         BLI_path_append(tdir, sizeof(tdir), group);
1910                                 }
1911                                 BLI_strncpy(sfile->params->dir, tdir, sizeof(sfile->params->dir));
1912                                 if (name) {
1913                                         BLI_strncpy(sfile->params->file, name, sizeof(sfile->params->file));
1914                                 }
1915                                 else {
1916                                         sfile->params->file[0] = '\0';
1917                                 }
1918                         }
1919                 }
1920
1921                 BLI_cleanup_dir(G.main->name, sfile->params->dir);
1922
1923                 if (filelist_is_dir(sfile->files, sfile->params->dir)) {
1924                         /* if directory exists, enter it immediately */
1925                         ED_file_change_dir(C);
1926
1927                         /* don't do for now because it selects entire text instead of
1928                          * placing cursor at the end */
1929                         /* UI_textbutton_activate_but(C, but); */
1930                 }
1931 #if defined(WIN32)
1932                 else if (!can_create_dir(sfile->params->dir)) {
1933                         const char *lastdir = folderlist_peeklastdir(sfile->folders_prev);
1934                         if (lastdir)
1935                                 BLI_strncpy(sfile->params->dir, lastdir, sizeof(sfile->params->dir));
1936                 }
1937 #endif
1938                 else {
1939                         const char *lastdir = folderlist_peeklastdir(sfile->folders_prev);
1940                         char tdir[FILE_MAX_LIBEXTRA];
1941
1942                         /* If we are 'inside' a blend library, we cannot do anything... */
1943                         if (lastdir && BLO_library_path_explode(lastdir, tdir, NULL, NULL)) {
1944                                 BLI_strncpy(sfile->params->dir, lastdir, sizeof(sfile->params->dir));
1945                         }
1946                         else {
1947                                 /* if not, ask to create it and enter if confirmed */
1948                                 wmOperatorType *ot = WM_operatortype_find("FILE_OT_directory_new", false);
1949                                 PointerRNA ptr;
1950                                 WM_operator_properties_create_ptr(&ptr, ot);
1951                                 RNA_string_set(&ptr, "directory", sfile->params->dir);
1952                                 RNA_boolean_set(&ptr, "open", true);
1953
1954                                 if (lastdir)
1955                                         BLI_strncpy(sfile->params->dir, lastdir, sizeof(sfile->params->dir));
1956
1957                                 WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr);
1958                                 WM_operator_properties_free(&ptr);
1959                         }
1960                 }
1961
1962                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
1963         }
1964 }
1965
1966 void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg_but)
1967 {
1968         SpaceFile *sfile = CTX_wm_space_file(C);
1969         uiBut *but = arg_but;
1970         char matched_file[FILE_MAX];
1971         char filepath[sizeof(sfile->params->dir)];
1972
1973         if (sfile->params) {
1974                 int matches;
1975                 matched_file[0] = '\0';
1976                 filepath[0] = '\0';
1977
1978                 file_expand_directory(C);
1979
1980                 matches = file_select_match(sfile, sfile->params->file, matched_file);
1981
1982                 /* *After* file_select_match! */
1983                 BLI_filename_make_safe(sfile->params->file);
1984
1985                 if (matches) {
1986                         /* replace the pattern (or filename that the user typed in, with the first selected file of the match */
1987                         BLI_strncpy(sfile->params->file, matched_file, sizeof(sfile->params->file));
1988                         
1989                         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
1990                 }
1991
1992                 if (matches == 1) {
1993                         BLI_join_dirfile(filepath, sizeof(sfile->params->dir), sfile->params->dir, sfile->params->file);
1994
1995                         /* if directory, open it and empty filename field */
1996                         if (filelist_is_dir(sfile->files, filepath)) {
1997                                 BLI_cleanup_dir(G.main->name, filepath);
1998                                 BLI_strncpy(sfile->params->dir, filepath, sizeof(sfile->params->dir));
1999                                 sfile->params->file[0] = '\0';
2000                                 ED_file_change_dir(C);
2001                                 UI_textbutton_activate_but(C, but);
2002                                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
2003                         }
2004                 }
2005                 else if (matches > 1) {
2006                         file_draw_check(C);
2007                 }
2008         }
2009 }
2010
2011 void FILE_OT_refresh(struct wmOperatorType *ot)
2012 {
2013         /* identifiers */
2014         ot->name = "Refresh Filelist";
2015         ot->description = "Refresh the file list";
2016         ot->idname = "FILE_OT_refresh";
2017         
2018         /* api callbacks */
2019         ot->exec = file_refresh_exec;
2020         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
2021 }
2022
2023 static int file_hidedot_exec(bContext *C, wmOperator *UNUSED(unused))
2024 {
2025         wmWindowManager *wm = CTX_wm_manager(C);
2026         SpaceFile *sfile = CTX_wm_space_file(C);
2027         ScrArea *sa = CTX_wm_area(C);
2028         
2029         if (sfile->params) {
2030                 sfile->params->flag ^= FILE_HIDE_DOT;
2031                 ED_fileselect_clear(wm, sa, sfile);
2032                 WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
2033         }
2034         
2035         return OPERATOR_FINISHED;
2036 }
2037
2038
2039 void FILE_OT_hidedot(struct wmOperatorType *ot)
2040 {
2041         /* identifiers */
2042         ot->name = "Toggle Hide Dot Files";
2043         ot->description = "Toggle hide hidden dot files";
2044         ot->idname = "FILE_OT_hidedot";
2045         
2046         /* api callbacks */
2047         ot->exec = file_hidedot_exec;
2048         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
2049 }
2050
2051 ARegion *file_tools_region(ScrArea *sa)
2052 {
2053         ARegion *ar, *arnew;
2054
2055         if ((ar = BKE_area_find_region_type(sa, RGN_TYPE_TOOLS)) != NULL)
2056                 return ar;
2057
2058         /* add subdiv level; after header */
2059         ar = BKE_area_find_region_type(sa, RGN_TYPE_HEADER);
2060         
2061         /* is error! */
2062         if (ar == NULL)
2063                 return NULL;
2064         
2065         arnew = MEM_callocN(sizeof(ARegion), "tools for file");
2066         BLI_insertlinkafter(&sa->regionbase, ar, arnew);
2067         arnew->regiontype = RGN_TYPE_TOOLS;
2068         arnew->alignment = RGN_ALIGN_LEFT;
2069
2070         ar = MEM_callocN(sizeof(ARegion), "tool props for file");
2071         BLI_insertlinkafter(&sa->regionbase, arnew, ar);
2072         ar->regiontype = RGN_TYPE_TOOL_PROPS;
2073         ar->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV;
2074
2075         return arnew;
2076 }
2077
2078 static int file_bookmark_toggle_exec(bContext *C, wmOperator *UNUSED(unused))
2079 {
2080         ScrArea *sa = CTX_wm_area(C);
2081         ARegion *ar = file_tools_region(sa);
2082         
2083         if (ar)
2084                 ED_region_toggle_hidden(C, ar);
2085
2086         return OPERATOR_FINISHED;
2087 }
2088
2089 void FILE_OT_bookmark_toggle(struct wmOperatorType *ot)
2090 {
2091         /* identifiers */
2092         ot->name = "Toggle Bookmarks";
2093         ot->description = "Toggle bookmarks display";
2094         ot->idname = "FILE_OT_bookmark_toggle";
2095         
2096         /* api callbacks */
2097         ot->exec = file_bookmark_toggle_exec;
2098         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
2099 }
2100
2101
2102 /**
2103  * Looks for a string of digits within name (using BLI_stringdec) and adjusts it by add.
2104  */
2105 static void filenum_newname(char *name, size_t name_size, int add)
2106 {
2107         char head[FILE_MAXFILE], tail[FILE_MAXFILE];
2108         char name_temp[FILE_MAXFILE];
2109         int pic;
2110         unsigned short digits;
2111
2112         pic = BLI_stringdec(name, head, tail, &digits);
2113
2114         /* are we going from 100 -> 99 or from 10 -> 9 */
2115         if (add < 0 && digits > 0) {
2116                 int i, exp;
2117                 exp = 1;
2118                 for (i = digits; i > 1; i--) {
2119                         exp *= 10;
2120                 }
2121                 if (pic >= exp && (pic + add) < exp) {
2122                         digits--;
2123                 }
2124         }
2125
2126         pic += add;
2127         if (pic < 0)
2128                 pic = 0;
2129         BLI_stringenc(name_temp, head, tail, digits, pic);
2130         BLI_strncpy(name, name_temp, name_size);
2131 }
2132
2133 static int file_filenum_exec(bContext *C, wmOperator *op)
2134 {
2135         SpaceFile *sfile = CTX_wm_space_file(C);
2136         ScrArea *sa = CTX_wm_area(C);
2137         
2138         int inc = RNA_int_get(op->ptr, "increment");
2139         if (sfile->params && (inc != 0)) {
2140                 filenum_newname(sfile->params->file, sizeof(sfile->params->file), inc);
2141                 ED_area_tag_redraw(sa);
2142                 file_draw_check(C);
2143                 // WM_event_add_notifier(C, NC_WINDOW, NULL);
2144         }
2145         
2146         return OPERATOR_FINISHED;
2147
2148 }
2149
2150 void FILE_OT_filenum(struct wmOperatorType *ot)
2151 {
2152         /* identifiers */
2153         ot->name = "Increment Number in Filename";
2154         ot->description = "Increment number in filename";
2155         ot->idname = "FILE_OT_filenum";
2156         
2157         /* api callbacks */
2158         ot->exec = file_filenum_exec;
2159         ot->poll = ED_operator_file_active; /* <- important, handler is on window level */
2160
2161         /* props */
2162         RNA_def_int(ot->srna, "increment", 1, -100, 100, "Increment", "", -100, 100);
2163 }
2164
2165 static int file_rename_exec(bContext *C, wmOperator *UNUSED(op))
2166 {
2167         ScrArea *sa = CTX_wm_area(C);
2168         SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C);
2169         
2170         if (sfile->params) {
2171                 int idx = sfile->params->highlight_file;
2172                 int numfiles = filelist_files_ensure(sfile->files);
2173                 if ((0 <= idx) && (idx < numfiles)) {
2174                         FileDirEntry *file = filelist_file(sfile->files, idx);
2175                         filelist_entry_select_index_set(sfile->files, idx, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL);
2176                         BLI_strncpy(sfile->params->renameedit, file->relpath, FILE_MAXFILE);
2177                         sfile->params->renamefile[0] = '\0';
2178                 }
2179                 ED_area_tag_redraw(sa);
2180         }
2181         
2182         return OPERATOR_FINISHED;
2183
2184 }
2185
2186 static int file_rename_poll(bContext *C)
2187 {
2188         bool poll = ED_operator_file_active(C);
2189         SpaceFile *sfile = CTX_wm_space_file(C);
2190
2191         if (sfile && sfile->params) {
2192                 int idx = sfile->params->highlight_file;
2193                 int numfiles = filelist_files_ensure(sfile->files);
2194
2195                 if ((0 <= idx) && (idx < numfiles)) {
2196                         FileDirEntry *file = filelist_file(sfile->files, idx);
2197                         if (FILENAME_IS_CURRPAR(file->relpath)) {
2198                                 poll = false;
2199                         }
2200                 }
2201
2202                 if (sfile->params->highlight_file < 0) {
2203                         poll = false;
2204                 }
2205                 else {
2206                         char dir[FILE_MAX];
2207                         if (filelist_islibrary(sfile->files, dir, NULL)) {
2208                                 poll = false;
2209                         }
2210                 }
2211         }
2212         else {
2213                 poll = false;
2214         }
2215
2216         return poll;
2217 }
2218
2219 void FILE_OT_rename(struct wmOperatorType *ot)
2220 {
2221         /* identifiers */
2222         ot->name = "Rename File or Directory";
2223         ot->description = "Rename file or file directory";
2224         ot->idname = "FILE_OT_rename";
2225         
2226         /* api callbacks */
2227         ot->exec = file_rename_exec;
2228         ot->poll = file_rename_poll; 
2229
2230 }
2231
2232 static int file_delete_poll(bContext *C)
2233 {
2234         int poll = ED_operator_file_active(C);
2235         SpaceFile *sfile = CTX_wm_space_file(C);
2236
2237         if (sfile && sfile->params) {
2238                 char dir[FILE_MAX];
2239                 int numfiles = filelist_files_ensure(sfile->files);
2240                 int i;
2241                 int num_selected = 0;
2242
2243                 if (filelist_islibrary(sfile->files, dir, NULL)) poll = 0;
2244                 for (i = 0; i < numfiles; i++) {
2245                         if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) {
2246                                 num_selected++;
2247                         }
2248                 }
2249                 if (num_selected <= 0) {
2250                         poll = 0;
2251                 }
2252         }
2253         else
2254                 poll = 0;
2255                 
2256         return poll;
2257 }
2258
2259 int file_delete_exec(bContext *C, wmOperator *op)
2260 {
2261         char str[FILE_MAX];
2262         wmWindowManager *wm = CTX_wm_manager(C);
2263         SpaceFile *sfile = CTX_wm_space_file(C);
2264         ScrArea *sa = CTX_wm_area(C);
2265         FileDirEntry *file;
2266         int numfiles = filelist_files_ensure(sfile->files);
2267         int i;
2268
2269         bool report_error = false;
2270         errno = 0;
2271         for (i = 0; i < numfiles; i++) {
2272                 if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) {
2273                         file = filelist_file(sfile->files, i);
2274                         BLI_make_file_string(G.main->name, str, sfile->params->dir, file->relpath);
2275                         if (BLI_delete(str, false, false) != 0 ||
2276                             BLI_exists(str))
2277                         {
2278                                 report_error = true;
2279                         }
2280                 }
2281         }
2282         
2283         if (report_error) {
2284                 BKE_reportf(op->reports, RPT_ERROR,
2285                             "Could not delete file: %s",
2286                             errno ? strerror(errno) : "unknown error");
2287         }
2288
2289         ED_fileselect_clear(wm, sa, sfile);
2290         WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
2291         
2292         return OPERATOR_FINISHED;
2293
2294 }
2295
2296 void FILE_OT_delete(struct wmOperatorType *ot)
2297 {
2298         /* identifiers */
2299         ot->name = "Delete Selected Files";
2300         ot->description = "Delete selected files";
2301         ot->idname = "FILE_OT_delete";
2302         
2303         /* api callbacks */
2304         ot->invoke = WM_operator_confirm;
2305         ot->exec = file_delete_exec;
2306         ot->poll = file_delete_poll; /* <- important, handler is on window level */
2307 }
2308
2309
2310 void ED_operatormacros_file(void)
2311 {
2312 //      wmOperatorType *ot;
2313 //      wmOperatorTypeMacro *otmacro;
2314         
2315         /* future macros */
2316 }