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