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