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