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