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