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