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