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 = ®ion->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 = ®ion->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(®ion->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(®ion->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, ®ion->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, ®ion->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, ®ion->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 = ®ion->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 ®ion->v2d, sfile->layout, event->mval[0], event->mval[1])) {
1348 const FileAttributeColumnType column_type = file_attribute_column_type_find_isect(
1349 ®ion->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, ®ion->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