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