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