This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/editors/space_file/file_ops.c
Julian Eisel ea93e5df6c Asset: Merge asset library/list refresh operators
In rBdcdbaf89bd11, I introduced a new operator
(`file.asset_library_refresh()`) to handle Asset Browser refreshing more
separate from File Browser refreshing. However, there already was
`asset.asset_list_refresh()`, which at this point only works for asset
view templates, but was intended to cover the Asset Browser case in
future too. This would happen once the Asset Browser uses the asset list
design of the asset view template.

So rather than having two operators for refreshing asset library data,
have one that just handles both cases, until they converge into one.
This avoids changes to the Python API in future (deprecating/changing
operators).

Differential Revision: https://developer.blender.org/D13239
2021-11-23 18:57:25 +01:00

2977 lines
91 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2008 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup spfile
*/
#include "BLI_utildefines.h"
#include "BLI_blenlib.h"
#include "BLI_linklist.h"
#include "BLI_math.h"
#include "BLO_readfile.h"
#include "BKE_appdir.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_screen.h"
#ifdef WIN32
# include "BLI_winstuff.h"
#endif
#include "ED_asset.h"
#include "ED_fileselect.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "UI_interface.h"
#include "UI_interface_icons.h"
#include "UI_resources.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "UI_view2d.h"
#include "WM_api.h"
#include "WM_types.h"
#include "file_intern.h"
#include "filelist.h"
#include "fsmenu.h"
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* -------------------------------------------------------------------- */
/** \name File Selection Utilities
* \{ */
static FileSelection find_file_mouse_rect(SpaceFile *sfile,
ARegion *region,
const rcti *rect_region)
{
FileSelection sel;
View2D *v2d = &region->v2d;
rcti rect_view;
rctf rect_view_fl;
rctf rect_region_fl;
BLI_rctf_rcti_copy(&rect_region_fl, rect_region);
/* Okay, manipulating v2d rects here is hacky... */
v2d->mask.ymax -= sfile->layout->offset_top;
v2d->cur.ymax -= sfile->layout->offset_top;
UI_view2d_region_to_view_rctf(v2d, &rect_region_fl, &rect_view_fl);
v2d->mask.ymax += sfile->layout->offset_top;
v2d->cur.ymax += sfile->layout->offset_top;
BLI_rcti_init(&rect_view,
(int)(v2d->tot.xmin + rect_view_fl.xmin),
(int)(v2d->tot.xmin + rect_view_fl.xmax),
(int)(v2d->tot.ymax - rect_view_fl.ymin),
(int)(v2d->tot.ymax - rect_view_fl.ymax));
sel = ED_fileselect_layout_offset_rect(sfile->layout, &rect_view);
return sel;
}
typedef enum FileSelect {
FILE_SELECT_NOTHING = 0,
FILE_SELECT_DIR = 1,
FILE_SELECT_FILE = 2,
} FileSelect;
static void clamp_to_filelist(int numfiles, FileSelection *sel)
{
/* box select before the first file */
if ((sel->first < 0) && (sel->last >= 0)) {
sel->first = 0;
}
/* don't select if everything is outside filelist */
if ((sel->first >= numfiles) && ((sel->last < 0) || (sel->last >= numfiles))) {
sel->first = -1;
sel->last = -1;
}
/* fix if last file invalid */
if ((sel->first > 0) && (sel->last < 0)) {
sel->last = numfiles - 1;
}
/* clamp */
if ((sel->first >= numfiles)) {
sel->first = numfiles - 1;
}
if ((sel->last >= numfiles)) {
sel->last = numfiles - 1;
}
}
static FileSelection file_selection_get(bContext *C, const rcti *rect, bool fill)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
int numfiles = filelist_files_ensure(sfile->files);
FileSelection sel;
sel = find_file_mouse_rect(sfile, region, rect);
if (!((sel.first == -1) && (sel.last == -1))) {
clamp_to_filelist(numfiles, &sel);
}
/* if desired, fill the selection up from the last selected file to the current one */
if (fill && (sel.last >= 0) && (sel.last < numfiles)) {
int f;
/* Try to find a smaller-index selected item. */
for (f = sel.last; f >= 0; f--) {
if (filelist_entry_select_index_get(sfile->files, f, CHECK_ALL)) {
break;
}
}
if (f >= 0) {
sel.first = f + 1;
}
/* If none found, try to find a higher-index selected item. */
else {
for (f = sel.first; f < numfiles; f++) {
if (filelist_entry_select_index_get(sfile->files, f, CHECK_ALL)) {
break;
}
}
if (f < numfiles) {
sel.last = f - 1;
}
}
}
return sel;
}
static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen)
{
Main *bmain = CTX_data_main(C);
FileSelect retval = FILE_SELECT_NOTHING;
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
int numfiles = filelist_files_ensure(sfile->files);
const FileDirEntry *file;
/* make the selected file active */
if ((selected_idx >= 0) && (selected_idx < numfiles) &&
(file = filelist_file(sfile->files, selected_idx))) {
params->highlight_file = selected_idx;
params->active_file = selected_idx;
if (file->typeflag & FILE_TYPE_DIR) {
const bool is_parent_dir = FILENAME_IS_PARENT(file->relpath);
if (do_diropen == false) {
retval = FILE_SELECT_DIR;
}
/* the path is too long and we are not going up! */
else if (!is_parent_dir && strlen(params->dir) + strlen(file->relpath) >= FILE_MAX) {
// XXX error("Path too long, cannot enter this directory");
}
else {
if (is_parent_dir) {
/* avoids /../../ */
BLI_path_parent_dir(params->dir);
if (params->recursion_level > 1) {
/* Disable 'dirtree' recursion when going up in tree. */
params->recursion_level = 0;
filelist_setrecursion(sfile->files, params->recursion_level);
}
}
else {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir);
strcat(params->dir, file->relpath);
BLI_path_slash_ensure(params->dir);
}
ED_file_change_dir(C);
retval = FILE_SELECT_DIR;
}
}
else {
retval = FILE_SELECT_FILE;
}
fileselect_file_set(sfile, selected_idx);
}
return retval;
}
/**
* \warning Loops over all files so better use cautiously.
*/
static bool file_is_any_selected(struct FileList *files)
{
const int numfiles = filelist_files_ensure(files);
int i;
/* Is any file selected ? */
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(files, i, CHECK_ALL)) {
return true;
}
}
return false;
}
static FileSelection file_current_selection_range_get(struct FileList *files)
{
const int numfiles = filelist_files_ensure(files);
FileSelection selection = {-1, -1};
/* Iterate over the files once but in two loops, one to find the first selected file, and the
* other to find the last. */
int file_index;
for (file_index = 0; file_index < numfiles; file_index++) {
if (filelist_entry_is_selected(files, file_index)) {
/* First selected entry found. */
selection.first = file_index;
break;
}
}
for (; file_index < numfiles; file_index++) {
if (filelist_entry_is_selected(files, file_index)) {
selection.last = file_index;
/* Keep looping, we may find more selected files. */
}
}
return selection;
}
/**
* If \a file is outside viewbounds, this adjusts view to make sure it's inside
*/
static void file_ensure_inside_viewbounds(ARegion *region, SpaceFile *sfile, const int file)
{
FileLayout *layout = ED_fileselect_get_layout(sfile, region);
rctf *cur = &region->v2d.cur;
rcti rect;
bool changed = true;
file_tile_boundbox(region, layout, file, &rect);
/* down - also use if tile is higher than viewbounds so view is aligned to file name */
if (cur->ymin > rect.ymin || layout->tile_h > region->winy) {
cur->ymin = rect.ymin - (2 * layout->tile_border_y);
cur->ymax = cur->ymin + region->winy;
}
/* up */
else if ((cur->ymax - layout->offset_top) < rect.ymax) {
cur->ymax = rect.ymax + layout->tile_border_y + layout->offset_top;
cur->ymin = cur->ymax - region->winy;
}
/* left - also use if tile is wider than viewbounds so view is aligned to file name */
else if (cur->xmin > rect.xmin || layout->tile_w > region->winx) {
cur->xmin = rect.xmin - layout->tile_border_x;
cur->xmax = cur->xmin + region->winx;
}
/* right */
else if (cur->xmax < rect.xmax) {
cur->xmax = rect.xmax + (2 * layout->tile_border_x);
cur->xmin = cur->xmax - region->winx;
}
else {
BLI_assert(cur->xmin <= rect.xmin && cur->xmax >= rect.xmax && cur->ymin <= rect.ymin &&
(cur->ymax - layout->offset_top) >= rect.ymax);
changed = false;
}
if (changed) {
UI_view2d_curRect_validate(&region->v2d);
}
}
static void file_ensure_selection_inside_viewbounds(ARegion *region,
SpaceFile *sfile,
FileSelection *sel)
{
const FileLayout *layout = ED_fileselect_get_layout(sfile, region);
if (((layout->flag & FILE_LAYOUT_HOR) && region->winx <= (1.2f * layout->tile_w)) &&
((layout->flag & FILE_LAYOUT_VER) && region->winy <= (2.0f * layout->tile_h))) {
return;
}
/* Adjust view to display selection. Doing iterations for first and last
* selected item makes view showing as much of the selection possible.
* Not really useful if tiles are (almost) bigger than viewbounds though. */
file_ensure_inside_viewbounds(region, sfile, sel->last);
file_ensure_inside_viewbounds(region, sfile, sel->first);
}
static FileSelect file_select(
bContext *C, const rcti *rect, FileSelType select, bool fill, bool do_diropen)
{
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
FileSelect retval = FILE_SELECT_NOTHING;
FileSelection sel = file_selection_get(C, rect, fill); /* get the selection */
const FileCheckType check_type = (params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_ALL;
/* flag the files as selected in the filelist */
filelist_entries_select_index_range_set(
sfile->files, &sel, select, FILE_SEL_SELECTED, check_type);
/* Don't act on multiple selected files */
if (sel.first != sel.last) {
select = 0;
}
/* Do we have a valid selection and are we actually selecting */
if ((sel.last >= 0) && (select != FILE_SEL_REMOVE)) {
/* Check last selection, if selected, act on the file or dir */
if (filelist_entry_select_index_get(sfile->files, sel.last, check_type)) {
retval = file_select_do(C, sel.last, do_diropen);
}
}
if (select != FILE_SEL_ADD && !file_is_any_selected(sfile->files)) {
params->active_file = -1;
}
else if (sel.last >= 0) {
ARegion *region = CTX_wm_region(C);
file_ensure_selection_inside_viewbounds(region, sfile, &sel);
}
/* update operator for name change event */
file_draw_check(C);
return retval;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Box Select Operator
* \{ */
static int file_box_select_find_last_selected(SpaceFile *sfile,
ARegion *region,
const FileSelection *sel,
const int mouse_xy[2])
{
FileLayout *layout = ED_fileselect_get_layout(sfile, region);
rcti bounds_first, bounds_last;
int dist_first, dist_last;
float mouseco_view[2];
UI_view2d_region_to_view(&region->v2d, UNPACK2(mouse_xy), &mouseco_view[0], &mouseco_view[1]);
file_tile_boundbox(region, layout, sel->first, &bounds_first);
file_tile_boundbox(region, layout, sel->last, &bounds_last);
/* are first and last in the same column (horizontal layout)/row (vertical layout)? */
if ((layout->flag & FILE_LAYOUT_HOR && bounds_first.xmin == bounds_last.xmin) ||
(layout->flag & FILE_LAYOUT_VER && bounds_first.ymin != bounds_last.ymin)) {
/* use vertical distance */
const int my_loc = (int)mouseco_view[1];
dist_first = BLI_rcti_length_y(&bounds_first, my_loc);
dist_last = BLI_rcti_length_y(&bounds_last, my_loc);
}
else {
/* use horizontal distance */
const int mx_loc = (int)mouseco_view[0];
dist_first = BLI_rcti_length_x(&bounds_first, mx_loc);
dist_last = BLI_rcti_length_x(&bounds_last, mx_loc);
}
return (dist_first < dist_last) ? sel->first : sel->last;
}
static int file_box_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
FileSelection sel;
rcti rect;
int result;
result = WM_gesture_box_modal(C, op, event);
if (result == OPERATOR_RUNNING_MODAL) {
WM_operator_properties_border_to_rcti(op, &rect);
ED_fileselect_layout_isect_rect(sfile->layout, &region->v2d, &rect, &rect);
sel = file_selection_get(C, &rect, 0);
if ((sel.first != params->sel_first) || (sel.last != params->sel_last)) {
int idx;
file_select_deselect_all(sfile, FILE_SEL_HIGHLIGHTED);
filelist_entries_select_index_range_set(
sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
for (idx = sel.last; idx >= 0; idx--) {
const FileDirEntry *file = filelist_file(sfile->files, idx);
/* Don't highlight read-only file (".." or ".") on box select. */
if (FILENAME_IS_CURRPAR(file->relpath)) {
filelist_entry_select_set(
sfile->files, file, FILE_SEL_REMOVE, FILE_SEL_HIGHLIGHTED, CHECK_ALL);
}
/* make sure highlight_file is no readonly file */
if (sel.last == idx) {
params->highlight_file = idx;
}
}
}
params->sel_first = sel.first;
params->sel_last = sel.last;
params->active_file = file_box_select_find_last_selected(sfile, region, &sel, event->mval);
}
else {
params->highlight_file = -1;
params->sel_first = params->sel_last = -1;
fileselect_file_set(sfile, params->active_file);
file_select_deselect_all(sfile, FILE_SEL_HIGHLIGHTED);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
return result;
}
static int file_box_select_exec(bContext *C, wmOperator *op)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
rcti rect;
FileSelect ret;
WM_operator_properties_border_to_rcti(op, &rect);
const eSelectOp sel_op = RNA_enum_get(op->ptr, "mode");
const bool select = (sel_op != SEL_OP_SUB);
if (SEL_OP_USE_PRE_DESELECT(sel_op)) {
file_select_deselect_all(sfile, FILE_SEL_SELECTED);
}
ED_fileselect_layout_isect_rect(sfile->layout, &region->v2d, &rect, &rect);
ret = file_select(C, &rect, select ? FILE_SEL_ADD : FILE_SEL_REMOVE, false, false);
/* unselect '..' parent entry - it's not supposed to be selected if more than
* one file is selected */
filelist_entry_parent_select_set(sfile->files, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
if (FILE_SELECT_DIR == ret) {
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
else if (FILE_SELECT_FILE == ret) {
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
return OPERATOR_FINISHED;
}
void FILE_OT_select_box(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Box Select";
ot->description = "Activate/select the file(s) contained in the border";
ot->idname = "FILE_OT_select_box";
/* api callbacks */
ot->invoke = WM_gesture_box_invoke;
ot->exec = file_box_select_exec;
ot->modal = file_box_select_modal;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
ot->cancel = WM_gesture_box_cancel;
/* properties */
WM_operator_properties_gesture_box(ot);
WM_operator_properties_select_operation_simple(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Pick Operator
* \{ */
static rcti file_select_mval_to_select_rect(const int mval[2])
{
rcti rect;
rect.xmin = rect.xmax = mval[0];
rect.ymin = rect.ymax = mval[1];
return rect;
}
static int file_select_exec(bContext *C, wmOperator *op)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelect ret;
rcti rect;
const bool extend = RNA_boolean_get(op->ptr, "extend");
const bool fill = RNA_boolean_get(op->ptr, "fill");
const bool do_diropen = RNA_boolean_get(op->ptr, "open");
const bool deselect_all = RNA_boolean_get(op->ptr, "deselect_all");
const bool only_activate_if_selected = RNA_boolean_get(op->ptr, "only_activate_if_selected");
/* Used so right mouse clicks can do both, activate and spawn the context menu. */
const bool pass_through = RNA_boolean_get(op->ptr, "pass_through");
bool wait_to_deselect_others = RNA_boolean_get(op->ptr, "wait_to_deselect_others");
if (region->regiontype != RGN_TYPE_WINDOW) {
return OPERATOR_CANCELLED;
}
int mval[2];
mval[0] = RNA_int_get(op->ptr, "mouse_x");
mval[1] = RNA_int_get(op->ptr, "mouse_y");
rect = file_select_mval_to_select_rect(mval);
if (!ED_fileselect_layout_is_inside_pt(sfile->layout, &region->v2d, rect.xmin, rect.ymin)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
if (extend || fill) {
wait_to_deselect_others = false;
}
int ret_val = OPERATOR_FINISHED;
const FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (sfile && params) {
int idx = params->highlight_file;
int numfiles = filelist_files_ensure(sfile->files);
if ((idx >= 0) && (idx < numfiles)) {
const bool is_selected = filelist_entry_select_index_get(sfile->files, idx, CHECK_ALL) &
FILE_SEL_SELECTED;
if (only_activate_if_selected && is_selected) {
/* Don't deselect other items. */
}
else if (wait_to_deselect_others && is_selected) {
ret_val = OPERATOR_RUNNING_MODAL;
}
/* single select, deselect all selected first */
else if (!extend) {
file_select_deselect_all(sfile, FILE_SEL_SELECTED);
}
}
}
ret = file_select(C, &rect, extend ? FILE_SEL_TOGGLE : FILE_SEL_ADD, fill, do_diropen);
if (extend) {
/* unselect '..' parent entry - it's not supposed to be selected if more
* than one file is selected */
filelist_entry_parent_select_set(sfile->files, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
}
if (ret == FILE_SELECT_NOTHING) {
if (deselect_all) {
file_select_deselect_all(sfile, FILE_SEL_SELECTED);
}
}
else if (ret == FILE_SELECT_DIR) {
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
else if (ret == FILE_SELECT_FILE) {
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
WM_event_add_mousemove(CTX_wm_window(C)); /* for directory changes */
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
if ((ret_val == OPERATOR_FINISHED) && pass_through) {
ret_val |= OPERATOR_PASS_THROUGH;
}
return ret_val;
}
void FILE_OT_select(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Select";
ot->idname = "FILE_OT_select";
ot->description = "Handle mouse clicks to select and activate items";
/* api callbacks */
ot->invoke = WM_generic_select_invoke;
ot->exec = file_select_exec;
ot->modal = WM_generic_select_modal;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
/* properties */
WM_operator_properties_generic_select(ot);
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "fill", false, "Fill", "Select everything beginning with the last selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "open", true, "Open", "Open a directory when selecting it");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"deselect_all",
false,
"Deselect On Nothing",
"Deselect all when nothing under the cursor");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"only_activate_if_selected",
false,
"Only Activate if Selected",
"Do not change selection if the item under the cursor is already "
"selected, only activate it");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,
"pass_through",
false,
"Pass Through",
"Even on successful execution, pass the event on so other operators can "
"execute on it as well");
RNA_def_property_flag(prop, PROP_SKIP_SAVE | PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Walk Operator
* \{ */
/**
* \returns true if selection has changed
*/
static bool 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)
{
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
struct FileList *files = sfile->files;
const int last_sel = params->active_file; /* store old value */
int active = active_old; /* could use active_old instead, just for readability */
bool deselect = false;
BLI_assert(params);
if (numfiles == 0) {
/* No files visible, nothing to do. */
return false;
}
if (has_selection) {
if (extend && filelist_entry_select_index_get(files, active_old, CHECK_ALL) &&
filelist_entry_select_index_get(files, active_new, CHECK_ALL)) {
/* conditions for deselecting: initial file is selected, new file is
* selected and either other_side isn't selected/found or we use fill */
deselect = (fill || other_site == -1 ||
!filelist_entry_select_index_get(files, other_site, CHECK_ALL));
/* don't change highlight_file here since we either want to deselect active or we want
* to walk through a block of selected files without selecting/deselecting anything */
params->active_file = active_new;
/* but we want to change active if we use fill
* (needed to get correct selection bounds) */
if (deselect && fill) {
active = active_new;
}
}
else {
/* regular selection change */
params->active_file = active = active_new;
}
}
else {
/* select last file */
if (ELEM(direction, UI_SELECT_WALK_UP, UI_SELECT_WALK_LEFT)) {
params->active_file = active = numfiles - 1;
}
/* select first file */
else if (ELEM(direction, UI_SELECT_WALK_DOWN, UI_SELECT_WALK_RIGHT)) {
params->active_file = active = 0;
}
else {
BLI_assert(0);
}
}
if (active < 0) {
return false;
}
if (extend) {
/* highlight the active walker file for extended selection for better visual feedback */
params->highlight_file = params->active_file;
/* unselect '..' parent entry - it's not supposed to be selected if more
* than one file is selected */
filelist_entry_parent_select_set(files, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
}
else {
/* deselect all first */
file_select_deselect_all(sfile, FILE_SEL_SELECTED);
/* highlight file under mouse pos */
params->highlight_file = -1;
WM_event_add_mousemove(win);
}
/* do the actual selection */
if (fill) {
FileSelection sel = {MIN2(active, last_sel), MAX2(active, last_sel)};
/* fill selection between last and first selected file */
filelist_entries_select_index_range_set(
files, &sel, deselect ? FILE_SEL_REMOVE : FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
/* entire sel is cleared here, so select active again */
if (deselect) {
filelist_entry_select_index_set(files, active, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
}
/* unselect '..' parent entry - it's not supposed to be selected if more
* than one file is selected */
if ((sel.last - sel.first) > 1) {
filelist_entry_parent_select_set(files, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
}
}
else {
filelist_entry_select_index_set(
files, active, deselect ? FILE_SEL_REMOVE : FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
}
BLI_assert(IN_RANGE(active, -1, numfiles));
fileselect_file_set(sfile, params->active_file);
/* ensure newly selected file is inside viewbounds */
file_ensure_inside_viewbounds(region, sfile, params->active_file);
/* selection changed */
return true;
}
/**
* \returns true if selection has changed
*/
static bool file_walk_select_do(bContext *C,
SpaceFile *sfile,
FileSelectParams *params,
const int direction,
const bool extend,
const bool fill)
{
wmWindow *win = CTX_wm_window(C);
ARegion *region = CTX_wm_region(C);
struct FileList *files = sfile->files;
const int numfiles = filelist_files_ensure(files);
const bool has_selection = file_is_any_selected(files);
const int active_old = params->active_file;
int active_new = -1;
int other_site = -1; /* file on the other site of active_old */
/* *** get all needed files for handling selection *** */
if (numfiles == 0) {
/* No files visible, nothing to do. */
return false;
}
if (has_selection) {
FileLayout *layout = ED_fileselect_get_layout(sfile, region);
const int idx_shift = (layout->flag & FILE_LAYOUT_HOR) ? layout->rows : layout->flow_columns;
if ((layout->flag & FILE_LAYOUT_HOR && direction == UI_SELECT_WALK_UP) ||
(layout->flag & FILE_LAYOUT_VER && direction == UI_SELECT_WALK_LEFT)) {
active_new = active_old - 1;
other_site = active_old + 1;
}
else if ((layout->flag & FILE_LAYOUT_HOR && direction == UI_SELECT_WALK_DOWN) ||
(layout->flag & FILE_LAYOUT_VER && direction == UI_SELECT_WALK_RIGHT)) {
active_new = active_old + 1;
other_site = active_old - 1;
}
else if ((layout->flag & FILE_LAYOUT_HOR && direction == UI_SELECT_WALK_LEFT) ||
(layout->flag & FILE_LAYOUT_VER && direction == UI_SELECT_WALK_UP)) {
active_new = active_old - idx_shift;
other_site = active_old + idx_shift;
}
else if ((layout->flag & FILE_LAYOUT_HOR && direction == UI_SELECT_WALK_RIGHT) ||
(layout->flag & FILE_LAYOUT_VER && direction == UI_SELECT_WALK_DOWN)) {
active_new = active_old + idx_shift;
other_site = active_old - idx_shift;
}
else {
BLI_assert(0);
}
if (!IN_RANGE(active_new, -1, numfiles)) {
if (extend) {
/* extend to invalid file -> abort */
return false;
}
/* if we don't extend, selecting '..' (index == 0) is allowed so
* using key selection to go to parent directory is possible */
if (active_new != 0) {
/* select initial file */
active_new = active_old;
}
}
if (!IN_RANGE(other_site, 0, numfiles)) {
other_site = -1;
}
}
return file_walk_select_selection_set(win,
region,
sfile,
direction,
numfiles,
active_old,
active_new,
other_site,
has_selection,
extend,
fill);
}
static int file_walk_select_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
const int direction = RNA_enum_get(op->ptr, "direction");
const bool extend = RNA_boolean_get(op->ptr, "extend");
const bool fill = RNA_boolean_get(op->ptr, "fill");
if (file_walk_select_do(C, sfile, params, direction, extend, fill)) {
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void FILE_OT_select_walk(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Walk Select/Deselect File";
ot->description = "Select/Deselect files by walking through them";
ot->idname = "FILE_OT_select_walk";
/* api callbacks */
ot->invoke = file_walk_select_invoke;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
/* properties */
WM_operator_properties_select_walk_direction(ot);
prop = RNA_def_boolean(ot->srna,
"extend",
false,
"Extend",
"Extend selection instead of deselecting everything first");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(
ot->srna, "fill", false, "Fill", "Select everything beginning with the last selection");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select All Operator
* \{ */
static int file_select_all_exec(bContext *C, wmOperator *op)
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
FileSelection sel;
const int numfiles = filelist_files_ensure(sfile->files);
int action = RNA_enum_get(op->ptr, "action");
if (action == SEL_TOGGLE) {
action = file_is_any_selected(sfile->files) ? SEL_DESELECT : SEL_SELECT;
}
sel.first = 0;
sel.last = numfiles - 1;
FileCheckType check_type;
FileSelType filesel_type;
switch (action) {
case SEL_SELECT:
case SEL_INVERT: {
check_type = (params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_FILES;
filesel_type = (action == SEL_INVERT) ? FILE_SEL_TOGGLE : FILE_SEL_ADD;
break;
}
case SEL_DESELECT: {
check_type = CHECK_ALL;
filesel_type = FILE_SEL_REMOVE;
break;
}
default: {
BLI_assert(0);
return OPERATOR_CANCELLED;
}
}
filelist_entries_select_index_range_set(
sfile->files, &sel, filesel_type, FILE_SEL_SELECTED, check_type);
params->active_file = -1;
if (action != SEL_DESELECT) {
for (int i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, check_type)) {
params->active_file = i;
break;
}
}
}
file_draw_check(C);
WM_event_add_mousemove(CTX_wm_window(C));
ED_area_tag_redraw(area);
return OPERATOR_FINISHED;
}
void FILE_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "(De)select All Files";
ot->description = "Select or deselect all files";
ot->idname = "FILE_OT_select_all";
/* api callbacks */
ot->exec = file_select_all_exec;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
/* properties */
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name View Selected Operator
* \{ */
static int file_view_selected_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelection sel = file_current_selection_range_get(sfile->files);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (sel.first == -1 && sel.last == -1 && params->active_file == -1) {
/* Nothing was selected. */
return OPERATOR_CANCELLED;
}
/* Extend the selection area with the active file, as it may not be selected but still is
* important to have in view. */
if (sel.first == -1 || params->active_file < sel.first) {
sel.first = params->active_file;
}
if (sel.last == -1 || params->active_file > sel.last) {
sel.last = params->active_file;
}
ScrArea *area = CTX_wm_area(C);
ARegion *region = CTX_wm_region(C);
file_ensure_selection_inside_viewbounds(region, sfile, &sel);
file_draw_check(C);
WM_event_add_mousemove(CTX_wm_window(C));
ED_area_tag_redraw(area);
return OPERATOR_FINISHED;
}
void FILE_OT_view_selected(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Frame Selected";
ot->description = "Scroll the selected files into view";
ot->idname = "FILE_OT_view_selected";
/* api callbacks */
ot->exec = file_view_selected_exec;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Select Bookmark Operator
* \{ */
/* Note we could get rid of this one, but it's used by some addon so...
* Does not hurt keeping it around for now. */
static int bookmark_select_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
PropertyRNA *prop;
if ((prop = RNA_struct_find_property(op->ptr, "dir"))) {
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
char entry[256];
RNA_property_string_get(op->ptr, prop, entry);
BLI_strncpy(params->dir, entry, sizeof(params->dir));
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir);
ED_file_change_dir(C);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
return OPERATOR_FINISHED;
}
void FILE_OT_select_bookmark(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Select Directory";
ot->description = "Select a bookmarked directory";
ot->idname = "FILE_OT_select_bookmark";
/* api callbacks */
ot->exec = bookmark_select_exec;
/* Bookmarks are for file browsing only (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
/* properties */
prop = RNA_def_string(ot->srna, "dir", NULL, FILE_MAXDIR, "Directory", "");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Add Bookmark Operator
* \{ */
static int bookmark_add_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
struct FSMenu *fsmenu = ED_fsmenu_get();
struct FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params->dir[0] != '\0') {
char name[FILE_MAX];
fsmenu_insert_entry(
fsmenu, FS_CATEGORY_BOOKMARKS, params->dir, NULL, ICON_FILE_FOLDER, FS_INSERT_SAVE);
BLI_join_dirfile(name,
sizeof(name),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(fsmenu, name);
}
ED_area_tag_refresh(area);
ED_area_tag_redraw(area);
return OPERATOR_FINISHED;
}
void FILE_OT_bookmark_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Bookmark";
ot->description = "Add a bookmark for the selected/active directory";
ot->idname = "FILE_OT_bookmark_add";
/* api callbacks */
ot->exec = bookmark_add_exec;
/* Bookmarks are for file browsing only (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete Bookmark Operator
* \{ */
static int bookmark_delete_exec(bContext *C, wmOperator *op)
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
struct FSMenu *fsmenu = ED_fsmenu_get();
int nentries = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS);
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "index");
if (prop) {
int index;
if (RNA_property_is_set(op->ptr, prop)) {
index = RNA_property_int_get(op->ptr, prop);
}
else { /* if index unset, use active bookmark... */
index = sfile->bookmarknr;
}
if ((index > -1) && (index < nentries)) {
char name[FILE_MAX];
fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index);
BLI_join_dirfile(name,
sizeof(name),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(fsmenu, name);
ED_area_tag_refresh(area);
ED_area_tag_redraw(area);
}
}
return OPERATOR_FINISHED;
}
void FILE_OT_bookmark_delete(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Delete Bookmark";
ot->description = "Delete selected bookmark";
ot->idname = "FILE_OT_bookmark_delete";
/* api callbacks */
ot->exec = bookmark_delete_exec;
/* Bookmarks are for file browsing only (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
/* properties */
prop = RNA_def_int(ot->srna, "index", -1, -1, 20000, "Index", "", -1, 20000);
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cleanup Bookmark Operator
* \{ */
static int bookmark_cleanup_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *area = CTX_wm_area(C);
struct FSMenu *fsmenu = ED_fsmenu_get();
struct FSMenuEntry *fsme_next, *fsme = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS);
int index;
bool changed = false;
for (index = 0; fsme; fsme = fsme_next) {
fsme_next = fsme->next;
if (!BLI_is_dir(fsme->path)) {
fsmenu_remove_entry(fsmenu, FS_CATEGORY_BOOKMARKS, index);
changed = true;
}
else {
index++;
}
}
if (changed) {
char name[FILE_MAX];
BLI_join_dirfile(name,
sizeof(name),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(fsmenu, name);
fsmenu_refresh_bookmarks_status(CTX_wm_manager(C), fsmenu);
ED_area_tag_refresh(area);
ED_area_tag_redraw(area);
}
return OPERATOR_FINISHED;
}
void FILE_OT_bookmark_cleanup(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Cleanup Bookmarks";
ot->description = "Delete all invalid bookmarks";
ot->idname = "FILE_OT_bookmark_cleanup";
/* api callbacks */
ot->exec = bookmark_cleanup_exec;
/* Bookmarks are for file browsing only (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
/* properties */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reorder Bookmark Operator
* \{ */
enum {
FILE_BOOKMARK_MOVE_TOP = -2,
FILE_BOOKMARK_MOVE_UP = -1,
FILE_BOOKMARK_MOVE_DOWN = 1,
FILE_BOOKMARK_MOVE_BOTTOM = 2,
};
static int bookmark_move_exec(bContext *C, wmOperator *op)
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
struct FSMenu *fsmenu = ED_fsmenu_get();
struct FSMenuEntry *fsmentry = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS);
const struct FSMenuEntry *fsmentry_org = fsmentry;
char fname[FILE_MAX];
const int direction = RNA_enum_get(op->ptr, "direction");
const int totitems = ED_fsmenu_get_nentries(fsmenu, FS_CATEGORY_BOOKMARKS);
const int act_index = sfile->bookmarknr;
int new_index;
if (totitems < 2) {
return OPERATOR_CANCELLED;
}
switch (direction) {
case FILE_BOOKMARK_MOVE_TOP:
new_index = 0;
break;
case FILE_BOOKMARK_MOVE_BOTTOM:
new_index = totitems - 1;
break;
case FILE_BOOKMARK_MOVE_UP:
case FILE_BOOKMARK_MOVE_DOWN:
default:
new_index = (totitems + act_index + direction) % totitems;
break;
}
if (new_index == act_index) {
return OPERATOR_CANCELLED;
}
BLI_linklist_move_item((LinkNode **)&fsmentry, act_index, new_index);
if (fsmentry != fsmentry_org) {
ED_fsmenu_set_category(fsmenu, FS_CATEGORY_BOOKMARKS, fsmentry);
}
/* Need to update active bookmark number. */
sfile->bookmarknr = new_index;
BLI_join_dirfile(fname,
sizeof(fname),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(fsmenu, fname);
ED_area_tag_redraw(area);
return OPERATOR_FINISHED;
}
void FILE_OT_bookmark_move(wmOperatorType *ot)
{
static const EnumPropertyItem slot_move[] = {
{FILE_BOOKMARK_MOVE_TOP, "TOP", 0, "Top", "Top of the list"},
{FILE_BOOKMARK_MOVE_UP, "UP", 0, "Up", ""},
{FILE_BOOKMARK_MOVE_DOWN, "DOWN", 0, "Down", ""},
{FILE_BOOKMARK_MOVE_BOTTOM, "BOTTOM", 0, "Bottom", "Bottom of the list"},
{0, NULL, 0, NULL, NULL}};
/* identifiers */
ot->name = "Move Bookmark";
ot->idname = "FILE_OT_bookmark_move";
ot->description = "Move the active bookmark up/down in the list";
/* api callbacks */
ot->exec = bookmark_move_exec;
/* Bookmarks are for file browsing only (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
/* flags */
ot->flag = OPTYPE_REGISTER; /* No undo! */
RNA_def_enum(ot->srna,
"direction",
slot_move,
0,
"Direction",
"Direction to move the active bookmark towards");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Reset Recent Blend Files Operator
* \{ */
static int reset_recent_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *area = CTX_wm_area(C);
char name[FILE_MAX];
struct FSMenu *fsmenu = ED_fsmenu_get();
while (ED_fsmenu_get_entry(fsmenu, FS_CATEGORY_RECENT, 0) != NULL) {
fsmenu_remove_entry(fsmenu, FS_CATEGORY_RECENT, 0);
}
BLI_join_dirfile(name,
sizeof(name),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(fsmenu, name);
ED_area_tag_redraw(area);
return OPERATOR_FINISHED;
}
void FILE_OT_reset_recent(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reset Recent";
ot->description = "Reset recent files";
ot->idname = "FILE_OT_reset_recent";
/* api callbacks */
ot->exec = reset_recent_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Highlight File Operator
* \{ */
int file_highlight_set(SpaceFile *sfile, ARegion *region, int mx, int my)
{
View2D *v2d = &region->v2d;
FileSelectParams *params;
int numfiles, origfile;
/* In case blender starts where the mouse is over a File browser,
* this operator can be invoked when the `sfile` or `sfile->layout` isn't initialized yet. */
if (sfile == NULL || sfile->files == NULL || sfile->layout == NULL) {
return 0;
}
numfiles = filelist_files_ensure(sfile->files);
params = ED_fileselect_get_active_params(sfile);
origfile = params->highlight_file;
mx -= region->winrct.xmin;
my -= region->winrct.ymin;
if (ED_fileselect_layout_is_inside_pt(sfile->layout, v2d, mx, my)) {
float fx, fy;
int highlight_file;
UI_view2d_region_to_view(v2d, mx, my, &fx, &fy);
highlight_file = ED_fileselect_layout_offset(
sfile->layout, (int)(v2d->tot.xmin + fx), (int)(v2d->tot.ymax - fy));
if ((highlight_file >= 0) && (highlight_file < numfiles)) {
params->highlight_file = highlight_file;
}
else {
params->highlight_file = -1;
}
}
else {
params->highlight_file = -1;
}
return (params->highlight_file != origfile);
}
static int file_highlight_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
if (!file_highlight_set(sfile, region, event->xy[0], event->xy[1])) {
return OPERATOR_PASS_THROUGH;
}
ED_area_tag_redraw(CTX_wm_area(C));
return OPERATOR_PASS_THROUGH;
}
void FILE_OT_highlight(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Highlight File";
ot->description = "Highlight selected file(s)";
ot->idname = "FILE_OT_highlight";
/* api callbacks */
ot->invoke = file_highlight_invoke;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Sort from Column Operator
* \{ */
static int file_column_sort_ui_context_invoke(bContext *C,
wmOperator *UNUSED(op),
const wmEvent *event)
{
const ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
if (file_attribute_column_header_is_inside(
&region->v2d, sfile->layout, event->mval[0], event->mval[1])) {
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
const FileAttributeColumnType column_type = file_attribute_column_type_find_isect(
&region->v2d, params, sfile->layout, event->mval[0]);
if (column_type != COLUMN_NONE) {
const FileAttributeColumn *column = &sfile->layout->attribute_columns[column_type];
BLI_assert(column->sort_type != FILE_SORT_DEFAULT);
if (params->sort == column->sort_type) {
/* Already sorting by selected column -> toggle sort invert (three state logic). */
params->flag ^= FILE_SORT_INVERT;
}
else {
params->sort = column->sort_type;
params->flag &= ~FILE_SORT_INVERT;
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
}
return OPERATOR_PASS_THROUGH;
}
void FILE_OT_sort_column_ui_context(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Sort from Column";
ot->description = "Change sorting to use column under cursor";
ot->idname = "FILE_OT_sort_column_ui_context";
/* api callbacks */
ot->invoke = file_column_sort_ui_context_invoke;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Cancel File Selector Operator
* \{ */
static bool file_operator_poll(bContext *C)
{
bool poll = ED_operator_file_browsing_active(C);
SpaceFile *sfile = CTX_wm_space_file(C);
if (!sfile || !sfile->op) {
poll = 0;
}
return poll;
}
static int file_cancel_exec(bContext *C, wmOperator *UNUSED(unused))
{
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
wmOperator *op = sfile->op;
sfile->op = NULL;
WM_event_fileselect_event(wm, op, EVT_FILESELECT_CANCEL);
return OPERATOR_FINISHED;
}
void FILE_OT_cancel(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Cancel File Load";
ot->description = "Cancel loading of selected file";
ot->idname = "FILE_OT_cancel";
/* api callbacks */
ot->exec = file_cancel_exec;
ot->poll = file_operator_poll;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Operator Utilities
* \{ */
void file_sfile_to_operator_ex(Main *bmain, wmOperator *op, SpaceFile *sfile, char *filepath)
{
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
PropertyRNA *prop;
/* XXX, not real length */
BLI_join_dirfile(filepath, FILE_MAX, params->dir, params->file);
if ((prop = RNA_struct_find_property(op->ptr, "relative_path"))) {
if (RNA_property_boolean_get(op->ptr, prop)) {
BLI_path_rel(filepath, BKE_main_blendfile_path(bmain));
}
}
if ((prop = RNA_struct_find_property(op->ptr, "filename"))) {
RNA_property_string_set(op->ptr, prop, params->file);
}
if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
RNA_property_string_set(op->ptr, prop, params->dir);
}
if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) {
RNA_property_string_set(op->ptr, prop, filepath);
}
/* some ops have multiple files to select */
/* this is called on operators check() so clear collections first since
* they may be already set. */
{
int i, numfiles = filelist_files_ensure(sfile->files);
if ((prop = RNA_struct_find_property(op->ptr, "files"))) {
PointerRNA itemptr;
int num_files = 0;
RNA_property_collection_clear(op->ptr, prop);
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) {
FileDirEntry *file = filelist_file(sfile->files, i);
/* Cannot (currently) mix regular items and alias/shortcuts in multiple selection. */
if (!file->redirection_path) {
RNA_property_collection_add(op->ptr, prop, &itemptr);
RNA_string_set(&itemptr, "name", file->relpath);
num_files++;
}
}
}
/* make sure the file specified in the filename button is added even if no
* files selected */
if (0 == num_files) {
RNA_property_collection_add(op->ptr, prop, &itemptr);
RNA_string_set(&itemptr, "name", params->file);
}
}
if ((prop = RNA_struct_find_property(op->ptr, "dirs"))) {
PointerRNA itemptr;
int num_dirs = 0;
RNA_property_collection_clear(op->ptr, prop);
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_DIRS)) {
FileDirEntry *file = filelist_file(sfile->files, i);
RNA_property_collection_add(op->ptr, prop, &itemptr);
RNA_string_set(&itemptr, "name", file->relpath);
num_dirs++;
}
}
/* make sure the directory specified in the button is added even if no
* directory selected */
if (0 == num_dirs) {
RNA_property_collection_add(op->ptr, prop, &itemptr);
RNA_string_set(&itemptr, "name", params->dir);
}
}
}
}
void file_sfile_to_operator(Main *bmain, wmOperator *op, SpaceFile *sfile)
{
char filepath_dummy[FILE_MAX];
file_sfile_to_operator_ex(bmain, op, sfile, filepath_dummy);
}
void file_operator_to_sfile(Main *bmain, SpaceFile *sfile, wmOperator *op)
{
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
PropertyRNA *prop;
/* If neither of the above are set, split the filepath back */
if ((prop = RNA_struct_find_property(op->ptr, "filepath"))) {
char filepath[FILE_MAX];
RNA_property_string_get(op->ptr, prop, filepath);
BLI_split_dirfile(
filepath, params->dir, params->file, sizeof(params->dir), sizeof(params->file));
}
else {
if ((prop = RNA_struct_find_property(op->ptr, "filename"))) {
RNA_property_string_get(op->ptr, prop, params->file);
}
if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
RNA_property_string_get(op->ptr, prop, params->dir);
}
}
/* we could check for relative_path property which is used when converting
* in the other direction but doesn't hurt to do this every time */
BLI_path_abs(params->dir, BKE_main_blendfile_path(bmain));
/* XXX, files and dirs updates missing, not really so important though */
}
/**
* Use to set the file selector path from some arbitrary source.
*/
void file_sfile_filepath_set(SpaceFile *sfile, const char *filepath)
{
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
BLI_assert(BLI_exists(filepath));
if (BLI_is_dir(filepath)) {
BLI_strncpy(params->dir, filepath, sizeof(params->dir));
}
else {
if ((params->flag & FILE_DIRSEL_ONLY) == 0) {
BLI_split_dirfile(
filepath, params->dir, params->file, sizeof(params->dir), sizeof(params->file));
}
else {
BLI_split_dir_part(filepath, params->dir, sizeof(params->dir));
}
}
}
void file_draw_check_ex(bContext *C, ScrArea *area)
{
/* May happen when manipulating non-active spaces. */
if (UNLIKELY(area->spacetype != SPACE_FILE)) {
return;
}
SpaceFile *sfile = area->spacedata.first;
wmOperator *op = sfile->op;
if (op) { /* fail on reload */
if (op->type->check) {
Main *bmain = CTX_data_main(C);
file_sfile_to_operator(bmain, op, sfile);
/* redraw */
if (op->type->check(C, op)) {
file_operator_to_sfile(bmain, sfile, op);
/* redraw, else the changed settings won't get updated */
ED_area_tag_redraw(area);
}
}
}
}
void file_draw_check(bContext *C)
{
ScrArea *area = CTX_wm_area(C);
file_draw_check_ex(C, area);
}
/* for use with; UI_block_func_set */
void file_draw_check_cb(bContext *C, void *UNUSED(arg1), void *UNUSED(arg2))
{
file_draw_check(C);
}
bool file_draw_check_exists(SpaceFile *sfile)
{
if (sfile->op) { /* fails on reload */
const FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params && (params->flag & FILE_CHECK_EXISTING)) {
char filepath[FILE_MAX];
BLI_join_dirfile(filepath, sizeof(filepath), params->dir, params->file);
if (BLI_is_file(filepath)) {
return true;
}
}
}
return false;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Execute File Window Operator
* \{ */
/**
* Execute the active file, as set in the file select params.
*/
static bool file_execute(bContext *C, SpaceFile *sfile)
{
Main *bmain = CTX_data_main(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
FileDirEntry *file = filelist_file(sfile->files, params->active_file);
if (file && file->redirection_path) {
/* redirection_path is an absolute path that takes precedence
* over using params->dir + params->file. */
BLI_split_dirfile(file->redirection_path,
params->dir,
params->file,
sizeof(params->dir),
sizeof(params->file));
/* Update relpath with redirected filename as well so that the alternative
* combination of params->dir + relpath remains valid as well. */
MEM_freeN(file->relpath);
file->relpath = BLI_strdup(params->file);
}
/* directory change */
if (file && (file->typeflag & FILE_TYPE_DIR)) {
if (!file->relpath) {
return false;
}
if (FILENAME_IS_PARENT(file->relpath)) {
BLI_path_parent_dir(params->dir);
}
else {
BLI_path_normalize(BKE_main_blendfile_path(bmain), params->dir);
BLI_path_append(params->dir, sizeof(params->dir) - 1, file->relpath);
BLI_path_slash_ensure(params->dir);
}
ED_file_change_dir(C);
}
/* opening file - sends events now, so things get handled on windowqueue level */
else if (sfile->op) {
wmOperator *op = sfile->op;
char filepath[FILE_MAX];
sfile->op = NULL;
file_sfile_to_operator_ex(bmain, op, sfile, filepath);
if (BLI_exists(params->dir)) {
fsmenu_insert_entry(ED_fsmenu_get(),
FS_CATEGORY_RECENT,
params->dir,
NULL,
ICON_FILE_FOLDER,
FS_INSERT_SAVE | FS_INSERT_FIRST);
}
BLI_join_dirfile(filepath,
sizeof(filepath),
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
BLENDER_BOOKMARK_FILE);
fsmenu_write_file(ED_fsmenu_get(), filepath);
WM_event_fileselect_event(CTX_wm_manager(C), op, EVT_FILESELECT_EXEC);
}
return true;
}
static int file_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceFile *sfile = CTX_wm_space_file(C);
if (!file_execute(C, sfile)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
void FILE_OT_execute(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Execute File Window";
ot->description = "Execute selected file";
ot->idname = "FILE_OT_execute";
/* api callbacks */
ot->exec = file_exec;
/* Important since handler is on window level.
*
* Avoid using #file_operator_poll since this is also used for entering directories
* which is used even when the file manager doesn't have an operator. */
ot->poll = ED_operator_file_browsing_active;
}
/**
* \returns false if the mouse doesn't hover a selectable item.
*/
static bool file_ensure_hovered_is_active(bContext *C, const wmEvent *event)
{
rcti rect = file_select_mval_to_select_rect(event->mval);
if (file_select(C, &rect, FILE_SEL_ADD, false, false) == FILE_SELECT_NOTHING) {
return false;
}
return true;
}
static int file_execute_mouse_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
ARegion *region = CTX_wm_region(C);
SpaceFile *sfile = CTX_wm_space_file(C);
if (!ED_fileselect_layout_is_inside_pt(
sfile->layout, &region->v2d, event->mval[0], event->mval[1])) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
/* Note that this isn't needed practically, because the keymap already activates the hovered item
* on mouse-press. This execute operator is called afterwards on the double-click event then.
* However relying on this would be fragile and could break with keymap changes, so better to
* have this mouse-execute operator that makes sure once more that the hovered file is active. */
if (!file_ensure_hovered_is_active(C, event)) {
return OPERATOR_CANCELLED;
}
if (!file_execute(C, sfile)) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
/**
* Variation of #FILE_OT_execute that accounts for some mouse specific handling. Otherwise calls
* the same logic.
*/
void FILE_OT_mouse_execute(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Execute File";
ot->description =
"Perform the current execute action for the file under the cursor (e.g. open the file)";
ot->idname = "FILE_OT_mouse_execute";
/* api callbacks */
ot->invoke = file_execute_mouse_invoke;
ot->poll = ED_operator_file_browsing_active;
ot->flag = OPTYPE_INTERNAL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Refresh File List Operator
* \{ */
static int file_refresh_exec(bContext *C, wmOperator *UNUSED(unused))
{
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
struct FSMenu *fsmenu = ED_fsmenu_get();
ED_fileselect_clear(wm, sfile);
/* refresh system directory menu */
fsmenu_refresh_system_category(fsmenu);
/* Update bookmarks 'valid' state. */
fsmenu_refresh_bookmarks_status(wm, fsmenu);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
return OPERATOR_FINISHED;
}
void FILE_OT_refresh(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Refresh File List";
ot->description = "Refresh the file list";
ot->idname = "FILE_OT_refresh";
/* api callbacks */
ot->exec = file_refresh_exec;
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Navigate Parent Operator
* \{ */
static int file_parent_exec(bContext *C, wmOperator *UNUSED(unused))
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
if (BLI_path_parent_dir(params->dir)) {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir);
ED_file_change_dir(C);
if (params->recursion_level > 1) {
/* Disable 'dirtree' recursion when going up in tree. */
params->recursion_level = 0;
filelist_setrecursion(sfile->files, params->recursion_level);
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
}
return OPERATOR_FINISHED;
}
void FILE_OT_parent(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Parent File";
ot->description = "Move to parent directory";
ot->idname = "FILE_OT_parent";
/* api callbacks */
ot->exec = file_parent_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Navigate Previous Operator
* \{ */
static int file_previous_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
folderlist_pushdir(sfile->folders_next, params->dir);
folderlist_popdir(sfile->folders_prev, params->dir);
folderlist_pushdir(sfile->folders_next, params->dir);
ED_file_change_dir(C);
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
return OPERATOR_FINISHED;
}
void FILE_OT_previous(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Previous Folder";
ot->description = "Move to previous folder";
ot->idname = "FILE_OT_previous";
/* api callbacks */
ot->exec = file_previous_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Navigate Next Operator
* \{ */
static int file_next_exec(bContext *C, wmOperator *UNUSED(unused))
{
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
folderlist_pushdir(sfile->folders_prev, params->dir);
folderlist_popdir(sfile->folders_next, params->dir);
/* update folders_prev so we can check for it in #folderlist_clear_next() */
folderlist_pushdir(sfile->folders_prev, params->dir);
ED_file_change_dir(C);
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
return OPERATOR_FINISHED;
}
void FILE_OT_next(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Next Folder";
ot->description = "Move to next folder";
ot->idname = "FILE_OT_next";
/* api callbacks */
ot->exec = file_next_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Smooth Scroll Operator
* \{ */
/* only meant for timer usage */
static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
ARegion *region, *region_ctx = CTX_wm_region(C);
const bool is_horizontal = (sfile->layout->flag & FILE_LAYOUT_HOR) != 0;
int i;
/* escape if not our timer */
if (sfile->smoothscroll_timer == NULL || sfile->smoothscroll_timer != event->customdata) {
return OPERATOR_PASS_THROUGH;
}
const int numfiles = filelist_files_ensure(sfile->files);
/* Due to async nature of file listing, we may execute this code before `file_refresh()`
* editing entry is available in our listing,
* so we also have to handle switching to rename mode here. */
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if ((params->rename_flag &
(FILE_PARAMS_RENAME_PENDING | FILE_PARAMS_RENAME_POSTSCROLL_PENDING)) != 0) {
file_params_renamefile_activate(sfile, params);
}
/* check if we are editing a name */
int edit_idx = -1;
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL) &
(FILE_SEL_EDITING | FILE_SEL_HIGHLIGHTED)) {
edit_idx = i;
break;
}
}
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
/* if we are not editing, we are done */
if (edit_idx == -1) {
/* Do not invalidate timer if filerename is still pending,
* we might still be building the filelist and yet have to find edited entry. */
if (params->rename_flag == 0) {
file_params_smoothscroll_timer_clear(wm, win, sfile);
}
return OPERATOR_PASS_THROUGH;
}
/* we need the correct area for scrolling */
region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
if (!region || region->regiontype != RGN_TYPE_WINDOW) {
file_params_smoothscroll_timer_clear(wm, win, sfile);
return OPERATOR_PASS_THROUGH;
}
/* Number of items in a block (i.e. lines in a column in horizontal layout, or columns in a line
* in vertical layout).
*/
const int items_block_size = is_horizontal ? sfile->layout->rows : sfile->layout->flow_columns;
/* Scroll offset is the first file in the row/column we are editing in. */
if (sfile->scroll_offset == 0) {
sfile->scroll_offset = (edit_idx / items_block_size) * items_block_size;
}
const int numfiles_layout = ED_fileselect_layout_numfiles(sfile->layout, region);
const int first_visible_item = ED_fileselect_layout_offset(
sfile->layout, (int)region->v2d.cur.xmin, (int)-region->v2d.cur.ymax);
const int last_visible_item = first_visible_item + numfiles_layout + 1;
/* NOTE: the special case for vertical layout is because filename is at the bottom of items then,
* so we artificially move current row back one step, to ensure we show bottom of
* active item rather than its top (important in case visible height is low). */
const int middle_offset = max_ii(
0, (first_visible_item + last_visible_item) / 2 - (is_horizontal ? 0 : items_block_size));
const int min_middle_offset = numfiles_layout / 2;
const int max_middle_offset = ((numfiles / items_block_size) * items_block_size +
((numfiles % items_block_size) != 0 ? items_block_size : 0)) -
(numfiles_layout / 2);
/* Actual (physical) scrolling info, in pixels, used to detect whether we are fully at the
* beginning/end of the view. */
/* Note that there is a weird glitch, that sometimes tot rctf is smaller than cur rctf...
* that is why we still need to keep the min/max_middle_offset checks too. :( */
const float min_tot_scroll = is_horizontal ? region->v2d.tot.xmin : -region->v2d.tot.ymax;
const float max_tot_scroll = is_horizontal ? region->v2d.tot.xmax : -region->v2d.tot.ymin;
const float min_curr_scroll = is_horizontal ? region->v2d.cur.xmin : -region->v2d.cur.ymax;
const float max_curr_scroll = is_horizontal ? region->v2d.cur.xmax : -region->v2d.cur.ymin;
/* Check if we have reached our final scroll position. */
/* Filelist has to be ready, otherwise it makes no sense to stop scrolling yet. */
const bool is_ready = filelist_is_ready(sfile->files);
/* Edited item must be in the 'middle' of shown area (kind of approximated).
* Note that we have to do the check in 'block space', not in 'item space' here. */
const bool is_centered = (abs(middle_offset / items_block_size -
sfile->scroll_offset / items_block_size) == 0);
/* OR edited item must be towards the beginning, and we are scrolled fully to the start. */
const bool is_full_start = ((sfile->scroll_offset < min_middle_offset) &&
(min_curr_scroll - min_tot_scroll < 1.0f) &&
(middle_offset - min_middle_offset < items_block_size));
/* OR edited item must be towards the end, and we are scrolled fully to the end.
* This one is crucial (unlike the one for the beginning), because without it we won't scroll
* fully to the end, and last column or row will end up only partially drawn. */
const bool is_full_end = ((sfile->scroll_offset > max_middle_offset) &&
(max_tot_scroll - max_curr_scroll < 1.0f) &&
(max_middle_offset - middle_offset < items_block_size));
if (is_ready && (is_centered || is_full_start || is_full_end)) {
file_params_smoothscroll_timer_clear(wm, win, sfile);
/* Post-scroll (after rename has been validated by user) is done,
* rename process is totally finished, cleanup. */
if ((params->rename_flag & FILE_PARAMS_RENAME_POSTSCROLL_ACTIVE) != 0) {
file_params_renamefile_clear(params);
}
return OPERATOR_FINISHED;
}
/* Temporarily set context to the main window region,
* so that the pan operator works. */
CTX_wm_region_set(C, region);
/* scroll one step in the desired direction */
PointerRNA op_ptr;
int deltax = 0;
int deltay = 0;
/* We adjust speed of scrolling to avoid tens of seconds of it in e.g. directories with tens of
* thousands of folders... See T65782. */
/* This will slow down scrolling when approaching final goal, also avoids going too far and
* having to bounce back... */
/* Number of blocks (columns in horizontal layout, rows otherwise) between current middle of
* screen, and final goal position. */
const int diff_offset = sfile->scroll_offset / items_block_size -
middle_offset / items_block_size;
/* convert diff_offset into pixels. */
const int diff_offset_delta = abs(diff_offset) *
(is_horizontal ?
sfile->layout->tile_w + 2 * sfile->layout->tile_border_x :
sfile->layout->tile_h + 2 * sfile->layout->tile_border_y);
const int scroll_delta = max_ii(2, diff_offset_delta / 15);
if (diff_offset < 0) {
if (is_horizontal) {
deltax = -scroll_delta;
}
else {
deltay = scroll_delta;
}
}
else {
if (is_horizontal) {
deltax = scroll_delta;
}
else {
deltay = -scroll_delta;
}
}
WM_operator_properties_create(&op_ptr, "VIEW2D_OT_pan");
RNA_int_set(&op_ptr, "deltax", deltax);
RNA_int_set(&op_ptr, "deltay", deltay);
WM_operator_name_call(C, "VIEW2D_OT_pan", WM_OP_EXEC_DEFAULT, &op_ptr);
WM_operator_properties_free(&op_ptr);
ED_region_tag_redraw(region);
/* and restore context */
CTX_wm_region_set(C, region_ctx);
return OPERATOR_FINISHED;
}
void FILE_OT_smoothscroll(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Smooth Scroll";
ot->idname = "FILE_OT_smoothscroll";
ot->description = "Smooth scroll to make editable file visible";
/* api callbacks */
ot->invoke = file_smoothscroll_invoke;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name File Selector Drop Operator
* \{ */
static int filepath_drop_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
if (sfile) {
char filepath[FILE_MAX];
RNA_string_get(op->ptr, "filepath", filepath);
if (!BLI_exists(filepath)) {
BKE_report(op->reports, RPT_ERROR, "File does not exist");
return OPERATOR_CANCELLED;
}
file_sfile_filepath_set(sfile, filepath);
if (sfile->op) {
file_sfile_to_operator(bmain, sfile->op, sfile);
file_draw_check(C);
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void FILE_OT_filepath_drop(wmOperatorType *ot)
{
ot->name = "File Selector Drop";
ot->idname = "FILE_OT_filepath_drop";
ot->exec = filepath_drop_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
RNA_def_string_file_path(ot->srna, "filepath", "Path", FILE_MAX, "", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name New Directory Operator
* \{ */
/**
* Create a new, non-existing folder name, returns true if successful,
* false if name couldn't be created.
* The actual name is returned in 'name', 'folder' contains the complete path,
* including the new folder name.
*/
static bool new_folder_path(const char *parent, char folder[FILE_MAX], char name[FILE_MAXFILE])
{
int i = 1;
int len = 0;
BLI_strncpy(name, "New Folder", FILE_MAXFILE);
BLI_join_dirfile(folder, FILE_MAX, parent, name);
/* check whether folder with the name already exists, in this case
* add number to the name. Check length of generated name to avoid
* crazy case of huge number of folders each named 'New Folder (x)' */
while (BLI_exists(folder) && (len < FILE_MAXFILE)) {
len = BLI_snprintf(name, FILE_MAXFILE, "New Folder(%d)", i);
BLI_join_dirfile(folder, FILE_MAX, parent, name);
i++;
}
return (len < FILE_MAXFILE);
}
static int file_directory_new_exec(bContext *C, wmOperator *op)
{
char name[FILE_MAXFILE];
char path[FILE_MAX];
bool generate_name = true;
PropertyRNA *prop;
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
const bool do_diropen = RNA_boolean_get(op->ptr, "open");
if (!params) {
BKE_report(op->reports, RPT_WARNING, "No parent directory given");
return OPERATOR_CANCELLED;
}
path[0] = '\0';
if ((prop = RNA_struct_find_property(op->ptr, "directory"))) {
RNA_property_string_get(op->ptr, prop, path);
if (path[0] != '\0') {
generate_name = false;
}
}
if (generate_name) {
/* create a new, non-existing folder name */
if (!new_folder_path(params->dir, path, name)) {
BKE_report(op->reports, RPT_ERROR, "Could not create new folder name");
return OPERATOR_CANCELLED;
}
}
else { /* We assume we are able to generate a valid name! */
char org_path[FILE_MAX];
BLI_strncpy(org_path, path, sizeof(org_path));
if (BLI_path_make_safe(path)) {
BKE_reportf(op->reports,
RPT_WARNING,
"'%s' given path is OS-invalid, creating '%s' path instead",
org_path,
path);
}
}
/* create the file */
errno = 0;
if (!BLI_dir_create_recursive(path) ||
/* Should no more be needed,
* now that BLI_dir_create_recursive returns a success state - but kept just in case. */
!BLI_exists(path)) {
BKE_reportf(op->reports,
RPT_ERROR,
"Could not create new folder: %s",
errno ? strerror(errno) : "unknown error");
return OPERATOR_CANCELLED;
}
eFileSel_Params_RenameFlag rename_flag = params->rename_flag;
/* If we don't enter the directory directly, remember file to jump into editing. */
if (do_diropen == false) {
BLI_assert_msg(params->rename_id == NULL,
"File rename handling should immediately clear rename_id when done, "
"because otherwise it will keep taking precedence over renamefile.");
BLI_strncpy(params->renamefile, name, FILE_MAXFILE);
rename_flag = FILE_PARAMS_RENAME_PENDING;
}
file_params_invoke_rename_postscroll(wm, CTX_wm_window(C), sfile);
params->rename_flag = rename_flag;
/* reload dir to make sure we're seeing what's in the directory */
ED_fileselect_clear(wm, sfile);
if (do_diropen) {
BLI_strncpy(params->dir, path, sizeof(params->dir));
ED_file_change_dir(C);
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
return OPERATOR_FINISHED;
}
void FILE_OT_directory_new(struct wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Create New Directory";
ot->description = "Create a new directory";
ot->idname = "FILE_OT_directory_new";
/* api callbacks */
ot->invoke = WM_operator_confirm_or_exec;
ot->exec = file_directory_new_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
prop = RNA_def_string_dir_path(
ot->srna, "directory", NULL, FILE_MAX, "Directory", "Name of new directory");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna, "open", false, "Open", "Open new directory");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
WM_operator_properties_confirm_or_exec(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Refresh File List Operator
* \{ */
/* TODO: This should go to BLI_path_utils. */
static void file_expand_directory(bContext *C)
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
if (BLI_path_is_rel(params->dir)) {
/* Use of 'default' folder here is just to avoid an error message on '//' prefix. */
BLI_path_abs(params->dir,
G.relbase_valid ? BKE_main_blendfile_path(bmain) :
BKE_appdir_folder_default_or_root());
}
else if (params->dir[0] == '~') {
char tmpstr[sizeof(params->dir) - 1];
BLI_strncpy(tmpstr, params->dir + 1, sizeof(tmpstr));
BLI_path_join(
params->dir, sizeof(params->dir), BKE_appdir_folder_default_or_root(), tmpstr, NULL);
}
else if (params->dir[0] == '\0')
#ifndef WIN32
{
params->dir[0] = '/';
params->dir[1] = '\0';
}
#else
{
BLI_windows_get_default_root_dir(params->dir);
}
/* change "C:" --> "C:\", T28102. */
else if ((isalpha(params->dir[0]) && (params->dir[1] == ':')) && (params->dir[2] == '\0')) {
params->dir[2] = '\\';
params->dir[3] = '\0';
}
else if (BLI_path_is_unc(params->dir)) {
BLI_path_normalize_unc(params->dir, FILE_MAX_LIBEXTRA);
}
#endif
}
}
/* TODO: check we still need this, it's annoying to have OS-specific code here... :/. */
#if defined(WIN32)
static bool can_create_dir(const char *dir)
{
/* for UNC paths we need to check whether the parent of the new
* directory is a proper directory itself and not a share or the
* UNC root (server name) itself. Calling BLI_is_dir does this
*/
if (BLI_path_is_unc(dir)) {
char parent[PATH_MAX];
BLI_strncpy(parent, dir, PATH_MAX);
BLI_path_parent_dir(parent);
return BLI_is_dir(parent);
}
return true;
}
#endif
void file_directory_enter_handle(bContext *C, void *UNUSED(arg_unused), void *UNUSED(arg_but))
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
char old_dir[sizeof(params->dir)];
BLI_strncpy(old_dir, params->dir, sizeof(old_dir));
file_expand_directory(C);
/* special case, user may have pasted a filepath into the directory */
if (!filelist_is_dir(sfile->files, params->dir)) {
char tdir[FILE_MAX_LIBEXTRA];
char *group, *name;
if (BLI_is_file(params->dir)) {
char path[sizeof(params->dir)];
BLI_strncpy(path, params->dir, sizeof(path));
BLI_split_dirfile(
path, params->dir, params->file, sizeof(params->dir), sizeof(params->file));
}
else if (BLO_library_path_explode(params->dir, tdir, &group, &name)) {
if (group) {
BLI_path_append(tdir, sizeof(tdir), group);
}
BLI_strncpy(params->dir, tdir, sizeof(params->dir));
if (name) {
BLI_strncpy(params->file, name, sizeof(params->file));
}
else {
params->file[0] = '\0';
}
}
}
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), params->dir);
if (filelist_is_dir(sfile->files, params->dir)) {
if (!STREQ(params->dir, old_dir)) { /* Avoids flickering when nothing's changed. */
/* if directory exists, enter it immediately */
ED_file_change_dir(C);
}
/* don't do for now because it selects entire text instead of
* placing cursor at the end */
// UI_textbutton_activate_but(C, but);
}
#if defined(WIN32)
else if (!can_create_dir(params->dir)) {
const char *lastdir = folderlist_peeklastdir(sfile->folders_prev);
if (lastdir) {
BLI_strncpy(params->dir, lastdir, sizeof(params->dir));
}
}
#endif
else {
const char *lastdir = folderlist_peeklastdir(sfile->folders_prev);
char tdir[FILE_MAX_LIBEXTRA];
/* If we are 'inside' a blend library, we cannot do anything... */
if (lastdir && BLO_library_path_explode(lastdir, tdir, NULL, NULL)) {
BLI_strncpy(params->dir, lastdir, sizeof(params->dir));
}
else {
/* if not, ask to create it and enter if confirmed */
wmOperatorType *ot = WM_operatortype_find("FILE_OT_directory_new", false);
PointerRNA ptr;
WM_operator_properties_create_ptr(&ptr, ot);
RNA_string_set(&ptr, "directory", params->dir);
RNA_boolean_set(&ptr, "open", true);
/* Enable confirmation prompt, else it's too easy
* to accidentally create new directories. */
RNA_boolean_set(&ptr, "confirm", true);
if (lastdir) {
BLI_strncpy(params->dir, lastdir, sizeof(params->dir));
}
WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr);
WM_operator_properties_free(&ptr);
}
}
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
}
void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg_but)
{
Main *bmain = CTX_data_main(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
uiBut *but = arg_but;
char matched_file[FILE_MAX];
if (params) {
char filepath[sizeof(params->dir)];
int matches;
matched_file[0] = '\0';
filepath[0] = '\0';
file_expand_directory(C);
matches = file_select_match(sfile, params->file, matched_file);
/* *After* file_select_match! */
BLI_filename_make_safe(params->file);
if (matches) {
/* replace the pattern (or filename that the user typed in,
* with the first selected file of the match */
BLI_strncpy(params->file, matched_file, sizeof(params->file));
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
if (matches == 1) {
BLI_join_dirfile(filepath, sizeof(params->dir), params->dir, params->file);
/* if directory, open it and empty filename field */
if (filelist_is_dir(sfile->files, filepath)) {
BLI_path_normalize_dir(BKE_main_blendfile_path(bmain), filepath);
BLI_strncpy(params->dir, filepath, sizeof(params->dir));
params->file[0] = '\0';
ED_file_change_dir(C);
UI_textbutton_activate_but(C, but);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
}
}
else if (matches > 1) {
file_draw_check(C);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Toggle Show Hidden Files Operator
* \{ */
static int file_hidedot_exec(bContext *C, wmOperator *UNUSED(unused))
{
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
params->flag ^= FILE_HIDE_DOT;
ED_fileselect_clear(wm, sfile);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
}
return OPERATOR_FINISHED;
}
void FILE_OT_hidedot(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Hide Dot Files";
ot->description = "Toggle hide hidden dot files";
ot->idname = "FILE_OT_hidedot";
/* api callbacks */
ot->exec = file_hidedot_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Increment Filename Operator
* \{ */
static bool file_filenum_poll(bContext *C)
{
SpaceFile *sfile = CTX_wm_space_file(C);
/* File browsing only operator (not asset browsing). */
if (!ED_operator_file_browsing_active(C)) {
return false;
}
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
return params && (params->flag & FILE_CHECK_EXISTING);
}
/**
* Looks for a string of digits within name (using BLI_path_sequence_decode) and adjusts it by add.
*/
static void filenum_newname(char *name, size_t name_size, int add)
{
char head[FILE_MAXFILE], tail[FILE_MAXFILE];
char name_temp[FILE_MAXFILE];
int pic;
ushort digits;
pic = BLI_path_sequence_decode(name, head, tail, &digits);
/* are we going from 100 -> 99 or from 10 -> 9 */
if (add < 0 && digits > 0) {
int i, exp;
exp = 1;
for (i = digits; i > 1; i--) {
exp *= 10;
}
if (pic >= exp && (pic + add) < exp) {
digits--;
}
}
pic += add;
if (pic < 0) {
pic = 0;
}
BLI_path_sequence_encode(name_temp, head, tail, digits, pic);
BLI_strncpy(name, name_temp, name_size);
}
static int file_filenum_exec(bContext *C, wmOperator *op)
{
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
ScrArea *area = CTX_wm_area(C);
int inc = RNA_int_get(op->ptr, "increment");
if (params && (inc != 0)) {
filenum_newname(params->file, sizeof(params->file), inc);
ED_area_tag_redraw(area);
file_draw_check(C);
// WM_event_add_notifier(C, NC_WINDOW, NULL);
}
return OPERATOR_FINISHED;
}
void FILE_OT_filenum(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Increment Number in Filename";
ot->description = "Increment number in filename";
ot->idname = "FILE_OT_filenum";
/* api callbacks */
ot->exec = file_filenum_exec;
ot->poll = file_filenum_poll;
/* props */
RNA_def_int(ot->srna, "increment", 1, -100, 100, "Increment", "", -100, 100);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Rename File/Directory Operator
* \{ */
static void file_rename_state_activate(SpaceFile *sfile, int file_idx, bool require_selected)
{
const int numfiles = filelist_files_ensure(sfile->files);
if ((file_idx >= 0) && (file_idx < numfiles)) {
FileDirEntry *file = filelist_file(sfile->files, file_idx);
if ((require_selected == false) ||
(filelist_entry_select_get(sfile->files, file, CHECK_ALL) & FILE_SEL_SELECTED)) {
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
filelist_entry_select_index_set(
sfile->files, file_idx, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL);
BLI_strncpy(params->renamefile, file->relpath, FILE_MAXFILE);
/* We can skip the pending state,
* as we can directly set FILE_SEL_EDITING on the expected entry here. */
params->rename_flag = FILE_PARAMS_RENAME_ACTIVE;
}
}
}
static int file_rename_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (params) {
file_rename_state_activate(sfile, params->active_file, false);
ED_area_tag_redraw(area);
}
return OPERATOR_FINISHED;
}
void FILE_OT_rename(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Rename File or Directory";
ot->description = "Rename file or file directory";
ot->idname = "FILE_OT_rename";
/* api callbacks */
ot->exec = file_rename_exec;
/* File browsing only operator (not asset browsing). */
ot->poll = ED_operator_file_browsing_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete File Operator
* \{ */
static bool file_delete_poll(bContext *C)
{
if (!ED_operator_file_browsing_active(C)) {
return false;
}
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (!sfile || !params) {
return false;
}
char dir[FILE_MAX_LIBEXTRA];
if (filelist_islibrary(sfile->files, dir, NULL)) {
return false;
}
int numfiles = filelist_files_ensure(sfile->files);
for (int i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) {
/* Has a selected file -> the operator can run. */
return true;
}
}
return false;
}
static bool file_delete_single(const FileSelectParams *params,
FileDirEntry *file,
const char **r_error_message)
{
char str[FILE_MAX];
BLI_join_dirfile(str, sizeof(str), params->dir, file->relpath);
if (BLI_delete_soft(str, r_error_message) != 0 || BLI_exists(str)) {
return false;
}
return true;
}
static int file_delete_exec(bContext *C, wmOperator *op)
{
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
int numfiles = filelist_files_ensure(sfile->files);
const char *error_message = NULL;
bool report_error = false;
errno = 0;
for (int i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) {
FileDirEntry *file = filelist_file(sfile->files, i);
if (!file_delete_single(params, file, &error_message)) {
report_error = true;
}
}
}
if (report_error) {
if (error_message != NULL) {
BKE_reportf(op->reports, RPT_ERROR, "Could not delete file or directory: %s", error_message);
}
else {
BKE_reportf(op->reports,
RPT_ERROR,
"Could not delete file or directory: %s",
errno ? strerror(errno) : "unknown error");
}
}
ED_fileselect_clear(wm, sfile);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL);
return OPERATOR_FINISHED;
}
void FILE_OT_delete(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete Selected Files";
ot->description = "Move selected files to the trash or recycle bin";
ot->idname = "FILE_OT_delete";
/* api callbacks */
ot->invoke = WM_operator_confirm;
ot->exec = file_delete_exec;
ot->poll = file_delete_poll; /* <- important, handler is on window level */
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Enter Filter Text Operator
* \{ */
static int file_start_filter_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *area = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
ARegion *region_ctx = CTX_wm_region(C);
if (area) {
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
CTX_wm_region_set(C, region);
if (UI_textbutton_activate_rna(C, region, params, "filter_search")) {
break;
}
}
}
CTX_wm_region_set(C, region_ctx);
return OPERATOR_FINISHED;
}
void FILE_OT_start_filter(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Filter";
ot->description = "Start entering filter text";
ot->idname = "FILE_OT_start_filter";
/* api callbacks */
ot->exec = file_start_filter_exec;
/* Operator works for file or asset browsing */
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Macro Operators
* \{ */
void ED_operatormacros_file(void)
{
// wmOperatorType *ot;
// wmOperatorTypeMacro *otmacro;
/* future macros */
}
/** \} */