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 2d6a69ae4e Asset System: New Asset Browser editor
This introduces the User Interface part of the Asset Browser, based on the
design in T54642.

Additions:
* New Asset Browser (internally a sub-editor of the File Browser).
* Navigation region showing asset categories.
* Main region showing the assets of the selected asset library with previews.
  The assets may be stored over multiple .blends in the directory that's
  "mounted" as asset library in the Preferences. They will all be shown in this
  list.
* Header with an asset library dropdown, allowing to choose the active asset
  library to show. Options are the "Current File" as asset library and all
  custom libraries.
* Display popover, filter popover and search box (partially dummies, see
  T82680).
* Sidebar showing the metadata of the currently active file (name, preview,
  description and tags), which can be edited for assets in the "Current File"
  asset library. (For others it will reset on reload.)
* The sidebar includes a button to load a custom preview image from a file.
* Make asset files draggable (with preview image).
* If a library with invalid path is selected, a message is drawn in the main
  region to help the user understand what's wrong.
* Operators to add and remove asset tags. Exposed in the sidebar.
* "Only Assets" option for Link/Append.
* Internal utilities for asset UI scripts.

For screenshots or demo videos, please see D9725. Or the 2.92 release notes.

Note that there are many things to be tweaked and polished in the Asset Browser
UI still. For example, the filter and display popovers are mostly dummies. See
T82680.

Part of the first Asset Browser milestone. Check the #asset_browser_milestone_1
project milestone on developer.blender.org.

Differential Revision: https://developer.blender.org/D9725

Reviewed by: Brecht Van Lommel, Hans Goudey
2020-12-15 17:03:48 +01:00

2820 lines
85 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;
}
static void file_deselect_all(SpaceFile *sfile, uint flag)
{
FileSelection sel;
sel.first = 0;
sel.last = filelist_files_ensure(sfile->files) - 1;
filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL);
}
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;
}
/**
* 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 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);
const FileLayout *layout = ED_fileselect_get_layout(sfile, region);
/* 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. */
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))) {
file_ensure_inside_viewbounds(region, sfile, sel.last);
file_ensure_inside_viewbounds(region, sfile, sel.first);
}
}
/* 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_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);
/* dont highlight readonly 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_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_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;
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 int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
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");
if (region->regiontype != RGN_TYPE_WINDOW) {
return OPERATOR_CANCELLED;
}
rect.xmin = rect.xmax = event->mval[0];
rect.ymin = rect.ymax = event->mval[1];
if (!ED_fileselect_layout_is_inside_pt(sfile->layout, &region->v2d, rect.xmin, rect.ymin)) {
return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH;
}
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)) {
/* single select, deselect all selected first */
if (!extend) {
file_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_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);
return OPERATOR_FINISHED;
}
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 = file_select_invoke;
ot->poll = ED_operator_file_active;
/* properties */
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);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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_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;
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;
ot->poll = ED_operator_file_active;
/* properties */
WM_operator_properties_select_all(ot);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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. */
/* TODO disallow bookmark editing in assets mode? */
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;
ot->poll = ED_operator_file_active;
/* properties */
prop = RNA_def_string(ot->srna, "dir", NULL, FILE_MAXDIR, "Dir", "");
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;
ot->poll = ED_operator_file_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;
ot->poll = ED_operator_file_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;
ot->poll = ED_operator_file_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->poll = ED_operator_file_active;
ot->exec = bookmark_move_exec;
/* 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;
ot->poll = ED_operator_file_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;
if (sfile == NULL || sfile->files == 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->x, event->y)) {
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;
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;
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_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 wont 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
* \{ */
static int file_exec(bContext *C, wmOperator *exec_op)
{
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = CTX_wm_manager(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
struct FileDirEntry *file = filelist_file(sfile->files, params->active_file);
char filepath[FILE_MAX];
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 OPERATOR_CANCELLED;
}
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;
/* When used as a macro, for double-click, to prevent closing when double-clicking on item. */
if (RNA_boolean_get(exec_op->ptr, "need_active")) {
const int numfiles = filelist_files_ensure(sfile->files);
int i, active = 0;
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) {
active = 1;
break;
}
}
if (active == 0) {
return OPERATOR_CANCELLED;
}
}
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(wm, op, EVT_FILESELECT_EXEC);
}
return OPERATOR_FINISHED;
}
static int file_exec_invoke(bContext *C, wmOperator *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;
}
return file_exec(C, op);
}
void FILE_OT_execute(struct wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Execute File Window";
ot->description = "Execute selected file";
ot->idname = "FILE_OT_execute";
/* api callbacks */
ot->invoke = file_exec_invoke;
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_active;
/* properties */
prop = RNA_def_boolean(ot->srna,
"need_active",
0,
"Need Active",
"Only execute if there's an active selected file in the file list");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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, CTX_data_scene(C), 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 Filelist";
ot->description = "Refresh the file list";
ot->idname = "FILE_OT_refresh";
/* api callbacks */
ot->exec = file_refresh_exec;
ot->poll = ED_operator_file_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;
ot->poll = ED_operator_file_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;
ot->poll = ED_operator_file_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;
ot->poll = ED_operator_file_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;
}
}
/* 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) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
sfile->smoothscroll_timer = NULL;
}
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) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
sfile->smoothscroll_timer = NULL;
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)) {
WM_event_remove_timer(CTX_wm_manager(C), CTX_wm_window(C), sfile->smoothscroll_timer);
sfile->smoothscroll_timer = NULL;
/* 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) {
params->renamefile[0] = '\0';
params->rename_flag = 0;
}
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;
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;
ot->poll = WM_operator_winactive;
RNA_def_string_file_path(ot->srna, "filepath", "Path", FILE_MAX, "", "");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name New Directory Operator
* \{ */
/**
* Create a new, non-existing folder name, returns 1 if successful, 0 if name couldn't be created.
* The actual name is returned in 'name', 'folder' contains the complete path,
* including the new folder name.
*/
static int new_folder_path(const char *parent, char *folder, char *name)
{
int i = 1;
int len = 0;
BLI_strncpy(name, "New Folder", FILE_MAXFILE);
BLI_join_dirfile(folder, FILE_MAX, parent, name); /* XXX, not real length */
/* 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); /* XXX, not real length */
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;
}
/* If we don't enter the directory directly, remember file to jump into editing. */
if (do_diropen == false) {
BLI_strncpy(params->renamefile, name, FILE_MAXFILE);
params->rename_flag = FILE_PARAMS_RENAME_PENDING;
}
/* set timer to smoothly view newly generated file */
/* max 30 frs/sec */
if (sfile->smoothscroll_timer != NULL) {
WM_event_remove_timer(wm, CTX_wm_window(C), sfile->smoothscroll_timer);
}
sfile->smoothscroll_timer = WM_event_add_timer(wm, CTX_wm_window(C), TIMER1, 1.0 / 1000.0);
sfile->scroll_offset = 0;
/* reload dir to make sure we're seeing what's in the directory */
ED_fileselect_clear(wm, CTX_data_scene(C), 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;
ot->poll = ED_operator_file_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());
}
else if (params->dir[0] == '~') {
char tmpstr[sizeof(params->dir) - 1];
BLI_strncpy(tmpstr, params->dir + 1, sizeof(tmpstr));
BLI_join_dirfile(params->dir, sizeof(params->dir), BKE_appdir_folder_default(), tmpstr);
}
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, CTX_data_scene(C), 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;
ot->poll = ED_operator_file_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);
if (!ED_operator_file_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_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *UNUSED(event))
{
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, true);
ED_area_tag_redraw(area);
}
return OPERATOR_FINISHED;
}
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->highlight_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->invoke = file_rename_invoke;
ot->exec = file_rename_exec;
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete File Operator
* \{ */
static bool file_delete_poll(bContext *C)
{
bool poll = ED_operator_file_active(C);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
if (sfile && params) {
char dir[FILE_MAX_LIBEXTRA];
int numfiles = filelist_files_ensure(sfile->files);
int i;
int num_selected = 0;
if (filelist_islibrary(sfile->files, dir, NULL)) {
poll = 0;
}
for (i = 0; i < numfiles; i++) {
if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) {
num_selected++;
}
}
if (num_selected <= 0) {
poll = 0;
}
}
else {
poll = 0;
}
return poll;
}
static bool file_delete_single(const FileSelectParams *params,
FileDirEntry *file,
const char **r_error_message)
{
if (file->typeflag & FILE_TYPE_ASSET) {
ID *id = filelist_file_get_id(file);
if (!id) {
*r_error_message = "File is not a local data-block asset.";
return false;
}
ED_asset_clear_id(id);
}
else {
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, CTX_data_scene(C), 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);
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_UI);
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
ARegion *region_ctx = CTX_wm_region(C);
CTX_wm_region_set(C, region);
UI_textbutton_activate_rna(C, region, params, "filter_search");
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;
ot->poll = ED_operator_file_active;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Macro Operators
* \{ */
void ED_operatormacros_file(void)
{
// wmOperatorType *ot;
// wmOperatorTypeMacro *otmacro;
/* future macros */
}
/** \} */