Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Eisel <eiseljulian@gmail.com>2015-06-11 18:20:29 +0300
committerJulian Eisel <eiseljulian@gmail.com>2015-06-11 18:20:29 +0300
commit939948c23384119dbcc7d94614cabf76dde22391 (patch)
treeee70ead880a234ab45bd21b6591f1ae629762880 /source/blender/editors/space_file/file_ops.c
parent356afe0085718cd326f4e830c943537923c2e976 (diff)
File Browser Arrow Keys Navigation
Adds support for selecting/deselecting files in File Browser using the arrow keys. All directions (up, down, left, right) are possible. When to Select, When to Deselect? Standard behaviour is selecting, however if we move into a block of already selected files (meaning 2+ files are selected) we start deselecting Possible Selection Methods Simple selection (arrow-key): All other files are deselected Expand selection (Shift+arrow key): Add to/remove from existing selection ill-Expand selection (Ctrl+Shift+arrow key): Add to/remove from existing selection and fill everything in-between From which file do we start navigating? From each available selection method (Mouse-, Walk-, All-, Border Select), we use the last selected file. If there's no selection at all we use the first (down/right arrow) or last (up/left arrow) file. (Ideally, the view would automatically be set to the new selection, but this behaviour overlaps with an other patch I've been working on, so prefer to do that separately) (Also tweaks color for highlighted file for better feedback) D1297, Review done by @campbellbarton, thx a lot :)
Diffstat (limited to 'source/blender/editors/space_file/file_ops.c')
-rw-r--r--source/blender/editors/space_file/file_ops.c309
1 files changed, 281 insertions, 28 deletions
diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c
index f8d13bb6adb..680a28f5e47 100644
--- a/source/blender/editors/space_file/file_ops.c
+++ b/source/blender/editors/space_file/file_ops.c
@@ -176,7 +176,8 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen)
(selected_idx < numfiles) &&
(file = filelist_file(sfile->files, selected_idx)))
{
- params->active_file = selected_idx;
+ params->highlight_file = selected_idx;
+ sfile->params->active_file = selected_idx;
if (S_ISDIR(file->type)) {
const bool is_parent_dir = FILENAME_IS_PARENT(file->relname);
@@ -242,6 +243,53 @@ static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select,
return retval;
}
+/**
+ * \warning: loops over all files so better use cautiously
+ */
+static bool file_is_any_selected(struct FileList *files)
+{
+ const int numfiles = filelist_numfiles(files);
+ int i;
+
+ for (i = 0; i < numfiles; ++i) {
+ if (filelist_is_selected(files, i, CHECK_ALL)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int file_border_select_find_last_selected(
+ SpaceFile *sfile, ARegion *ar, const FileSelection *sel,
+ const int mouse_xy[2])
+{
+ FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
+ rcti bounds_first, bounds_last;
+ int dist_first, dist_last;
+
+ file_tile_boundbox(ar, layout, sel->first, &bounds_first);
+ file_tile_boundbox(ar, 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 = mouse_xy[1] - ar->winrct.ymin;
+ 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 = mouse_xy[0] - ar->winrct.xmin;
+ 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_border_select_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
ARegion *ar = CTX_wm_region(C);
@@ -276,17 +324,17 @@ static int file_border_select_modal(bContext *C, wmOperator *op, const wmEvent *
file->selflag &= ~FILE_SEL_HIGHLIGHTED;
}
- /* active_file gets highlighted as well - make sure it is no readonly file */
+ /* make sure highlight_file is no readonly file */
if (sel.last == idx) {
- params->active_file = idx;
+ params->highlight_file = idx;
}
}
}
params->sel_first = sel.first; params->sel_last = sel.last;
-
+ params->active_file = file_border_select_find_last_selected(sfile, ar, &sel, &event->x);
}
else {
- params->active_file = -1;
+ params->highlight_file = -1;
params->sel_first = params->sel_last = -1;
file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL);
@@ -361,7 +409,7 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
return OPERATOR_CANCELLED;
if (sfile && sfile->params) {
- int idx = sfile->params->active_file;
+ int idx = sfile->params->highlight_file;
if (idx >= 0) {
struct direntry *file = filelist_file(sfile->files, idx);
@@ -409,35 +457,240 @@ void FILE_OT_select(wmOperatorType *ot)
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
+/**
+ * \returns true if selection has changed
+ */
+static bool file_walk_select_selection_set(
+ bContext *C, 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 = sfile->params;
+ 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;
+
+ if (has_selection) {
+ if (extend &&
+ filelist_is_selected(files, active_old, FILE_SEL_SELECTED) &&
+ filelist_is_selected(files, active_new, FILE_SEL_SELECTED))
+ {
+ /* 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_is_selected(files, other_site, FILE_SEL_SELECTED));
+
+ /* don't change active 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, FILE_SELECT_WALK_UP, FILE_SELECT_WALK_LEFT)) {
+ params->active_file = active = numfiles - 1;
+ }
+ /* select first file */
+ else if (ELEM(direction, FILE_SELECT_WALK_DOWN, FILE_SELECT_WALK_RIGHT)) {
+ params->active_file = active = 1;
+ }
+ else {
+ BLI_assert(0);
+ }
+ }
+
+ if (!params || active < 0) return false;
+
+ /* highlight the active walker file for extended selection for better visual feedback */
+ if (extend) {
+ params->highlight_file = params->active_file;
+ }
+ else {
+ /* deselect all first */
+ file_deselect_all(sfile, FILE_SEL_SELECTED);
+
+ /* highlight file under mouse pos */
+ params->highlight_file = -1;
+ WM_event_add_mousemove(C);
+ }
+
+ /* 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_select(sfile->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_select_file(sfile->files, active, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
+ }
+ }
+ else {
+ filelist_select_file(sfile->files, active, deselect ? FILE_SEL_REMOVE : FILE_SEL_ADD,
+ FILE_SEL_SELECTED, CHECK_ALL);
+ }
+
+ BLI_assert(IN_RANGE(active, 0, numfiles));
+
+ /* 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)
+{
+ const int numfiles = filelist_numfiles(sfile->files);
+ const bool has_selection = file_is_any_selected(sfile->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 (has_selection) {
+ ARegion *ar = CTX_wm_region(C);
+ FileLayout *layout = ED_fileselect_get_layout(sfile, ar);
+ const int idx_shift = (layout->flag & FILE_LAYOUT_HOR) ? layout->rows : layout->columns;
+
+ if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_UP) ||
+ (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_LEFT))
+ {
+ active_new = active_old - 1;
+ other_site = active_old + 1;
+ }
+ else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_DOWN) ||
+ (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_RIGHT))
+ {
+ active_new = active_old + 1;
+ other_site = active_old - 1;
+ }
+ else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_LEFT) ||
+ (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_UP))
+ {
+ active_new = active_old - idx_shift;
+ other_site = active_old + idx_shift;
+ }
+ else if ((layout->flag & FILE_LAYOUT_HOR && direction == FILE_SELECT_WALK_RIGHT) ||
+ (layout->flag & FILE_LAYOUT_VER && direction == FILE_SELECT_WALK_DOWN))
+ {
+
+ active_new = active_old + idx_shift;
+ other_site = active_old - idx_shift;
+ }
+ else {
+ BLI_assert(0);
+ }
+
+ if (!IN_RANGE(active_new, 0, numfiles)) {
+ /* extend to invalid file -> abort */
+ if (extend) return false;
+ /* select initial file */
+ else active_new = active_old;
+ }
+ if (!IN_RANGE(other_site, 0, numfiles)) {
+ other_site = -1;
+ }
+ }
+
+ return file_walk_select_selection_set(C, 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 = sfile->params;
+ 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)
+{
+ static EnumPropertyItem direction_items[] = {
+ {FILE_SELECT_WALK_UP, "UP", 0, "Prev", ""},
+ {FILE_SELECT_WALK_DOWN, "DOWN", 0, "Next", ""},
+ {FILE_SELECT_WALK_LEFT, "LEFT", 0, "Left", ""},
+ {FILE_SELECT_WALK_RIGHT, "RIGHT", 0, "Right", ""},
+ {0, NULL, 0, NULL, NULL}
+ };
+ 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;
+ ot->poll = ED_operator_file_active;
+
+ /* properties */
+ prop = RNA_def_enum(ot->srna, "direction", direction_items, 0, "Walk Direction",
+ "Select/Deselect file in this direction");
+ RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+ 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);
+}
+
static int file_select_all_exec(bContext *C, wmOperator *UNUSED(op))
{
ScrArea *sa = CTX_wm_area(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelection sel;
- int numfiles = filelist_numfiles(sfile->files);
- int i;
- bool is_selected = false;
+ const int numfiles = filelist_numfiles(sfile->files);
+ const bool has_selection = file_is_any_selected(sfile->files);
sel.first = 0;
sel.last = numfiles - 1;
- /* Is any file selected ? */
- for (i = 0; i < numfiles; ++i) {
- if (filelist_is_selected(sfile->files, i, CHECK_ALL)) {
- is_selected = true;
- break;
- }
- }
/* select all only if previously no file was selected */
- if (is_selected) {
+ if (has_selection) {
filelist_select(sfile->files, &sel, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL);
}
else {
const FileCheckType check_type = (sfile->params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_FILES;
+ int i;
+
filelist_select(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_SELECTED, check_type);
+
+ /* set active_file to first selected */
+ for (i = 0; i < numfiles; i++) {
+ if (filelist_is_selected(sfile->files, i, check_type)) {
+ sfile->params->active_file = i;
+ break;
+ }
+ }
}
+
file_draw_check(C);
+ WM_event_add_mousemove(C);
ED_area_tag_redraw(sa);
+
return OPERATOR_FINISHED;
}
@@ -748,28 +1001,28 @@ int file_highlight_set(SpaceFile *sfile, ARegion *ar, int mx, int my)
numfiles = filelist_numfiles(sfile->files);
params = ED_fileselect_get_params(sfile);
- origfile = params->active_file;
+ origfile = params->highlight_file;
mx -= ar->winrct.xmin;
my -= ar->winrct.ymin;
if (BLI_rcti_isect_pt(&ar->v2d.mask, mx, my)) {
float fx, fy;
- int active_file;
+ int highlight_file;
UI_view2d_region_to_view(v2d, mx, my, &fx, &fy);
- active_file = ED_fileselect_layout_offset(sfile->layout, (int)(v2d->tot.xmin + fx), (int)(v2d->tot.ymax - fy));
+ highlight_file = ED_fileselect_layout_offset(sfile->layout, (int)(v2d->tot.xmin + fx), (int)(v2d->tot.ymax - fy));
- if ((active_file >= 0) && (active_file < numfiles))
- params->active_file = active_file;
+ if ((highlight_file >= 0) && (highlight_file < numfiles))
+ params->highlight_file = highlight_file;
else
- params->active_file = -1;
+ params->highlight_file = -1;
}
else
- params->active_file = -1;
+ params->highlight_file = -1;
- return (params->active_file != origfile);
+ return (params->highlight_file != origfile);
}
static int file_highlight_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
@@ -1669,7 +1922,7 @@ static int file_rename_exec(bContext *C, wmOperator *UNUSED(op))
SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C);
if (sfile->params) {
- int idx = sfile->params->active_file;
+ int idx = sfile->params->highlight_file;
int numfiles = filelist_numfiles(sfile->files);
if ( (0 <= idx) && (idx < numfiles) ) {
struct direntry *file = filelist_file(sfile->files, idx);
@@ -1690,7 +1943,7 @@ static int file_rename_poll(bContext *C)
SpaceFile *sfile = CTX_wm_space_file(C);
if (sfile && sfile->params) {
- int idx = sfile->params->active_file;
+ int idx = sfile->params->highlight_file;
if (idx >= 0) {
struct direntry *file = filelist_file(sfile->files, idx);
@@ -1699,7 +1952,7 @@ static int file_rename_poll(bContext *C)
}
}
- if (sfile->params->active_file < 0) {
+ if (sfile->params->highlight_file < 0) {
poll = 0;
}
else {