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