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