Asset Browser: Show current file assets in other asset libraries if contained

If the current file is saved within an asset library, showing that asset
library in the Asset Browser will also display the assets from this current
file now. In fact, it's the latest state of the open file, including all
unsaved modifications.
These assets will show a little Blender icon in the preview image, which is our
usual icon for current file data.

Note that this means an important design change: The "Current File" asset
library isn't the only place to edit assets from anymore. From now on assets
from the current file can also be edited in the context of the full asset
library. See T90193 for more info.

Technical info:
Besides just including the assets from the current `Main`, this requires
partial clearing and reading of file-lists, so that asset operations (e.g.
removing an asset data-block) doesn't require a full reload of the asset
library.

Maniphest Task: https://developer.blender.org/T90193
This commit is contained in:
Julian Eisel 2021-10-20 13:09:40 +02:00
parent 4f15c24705
commit d28aaf6139
6 changed files with 293 additions and 96 deletions

View File

@ -187,7 +187,7 @@ void AssetList::fetch(const bContext &C)
if (filelist_needs_force_reset(files)) {
filelist_readjob_stop(files, CTX_wm_manager(&C));
filelist_clear(files);
filelist_clear_from_reset_tag(files);
}
if (filelist_needs_reading(files)) {
@ -295,9 +295,7 @@ int AssetList::size() const
void AssetList::tagMainDataDirty() const
{
if (filelist_needs_reset_on_main_changes(filelist_)) {
/* Full refresh of the file list if local asset data was changed. Refreshing this view
* is cheap and users expect this to be updated immediately. */
filelist_tag_force_reset(filelist_);
filelist_tag_force_reset_mainfiles(filelist_);
}
}

View File

@ -742,6 +742,9 @@ static bool ui_but_equals_old(const uiBut *but, const uiBut *oldbut)
if (but->optype != oldbut->optype) {
return false;
}
if (but->dragtype != oldbut->dragtype) {
return false;
}
if ((but->type == UI_BTYPE_TREEROW) && (oldbut->type == UI_BTYPE_TREEROW)) {
uiButTreeRow *but_treerow = (uiButTreeRow *)but;

View File

@ -465,6 +465,17 @@ static void file_draw_preview(const SpaceFile *sfile,
UI_icon_draw_ex(icon_x, icon_y, icon, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false);
}
const bool is_current_main_data = filelist_file_get_id(file) != NULL;
if (is_current_main_data) {
/* Smaller, fainter icon at the top-right indicating that the file represents data from the
* current file (from current #Main in fact). */
float icon_x, icon_y;
const uchar light[4] = {255, 255, 255, 255};
icon_x = xco + ex - UI_UNIT_X;
icon_y = yco + ey - UI_UNIT_Y;
UI_icon_draw_ex(icon_x, icon_y, ICON_FILE_BLEND, 1.0f / U.dpi_fac, 0.6f, 0.0f, light, false);
}
/* Contrasting outline around some preview types. */
if (show_outline) {
GPUVertFormat *format = immVertexFormat();

View File

@ -435,11 +435,14 @@ typedef struct FileList {
/* FileList.flags */
enum {
FL_FORCE_RESET = 1 << 0,
FL_IS_READY = 1 << 1,
FL_IS_PENDING = 1 << 2,
FL_NEED_SORTING = 1 << 3,
FL_NEED_FILTERING = 1 << 4,
FL_SORT_INVERT = 1 << 5,
/* Don't do a full reset (unless #FL_FORCE_RESET is also set), only reset files representing main
* data (assets from the current file/#Main). */
FL_FORCE_RESET_MAIN_FILES = 1 << 1,
FL_IS_READY = 1 << 2,
FL_IS_PENDING = 1 << 3,
FL_NEED_SORTING = 1 << 4,
FL_NEED_FILTERING = 1 << 5,
FL_SORT_INVERT = 1 << 6,
};
/* FileList.tags */
@ -1375,6 +1378,11 @@ int ED_file_icon(const FileDirEntry *file)
filelist_geticon_ex(file, NULL, false, false);
}
static bool filelist_intern_entry_is_main_file(const FileListInternEntry *intern_entry)
{
return intern_entry->local_data.id != NULL;
}
/* ********** Main ********** */
static void parent_dir_until_exists_or_default_root(char *dir)
@ -1503,6 +1511,26 @@ static void filelist_intern_free(FileListIntern *filelist_intern)
MEM_SAFE_FREE(filelist_intern->filtered);
}
/**
* \return the number of main files removed.
*/
static int filelist_intern_free_main_files(FileListIntern *filelist_intern)
{
int removed_counter = 0;
LISTBASE_FOREACH_MUTABLE (FileListInternEntry *, entry, &filelist_intern->entries) {
if (!filelist_intern_entry_is_main_file(entry)) {
continue;
}
BLI_remlink(&filelist_intern->entries, entry);
filelist_intern_entry_free(entry);
removed_counter++;
}
MEM_SAFE_FREE(filelist_intern->filtered);
return removed_counter;
}
static void filelist_cache_preview_runf(TaskPool *__restrict pool, void *taskdata)
{
FileListEntryCache *cache = BLI_task_pool_user_data(pool);
@ -1803,6 +1831,7 @@ void filelist_settype(FileList *filelist, short type)
filelist->read_job_fn = filelist_readjob_asset_library;
filelist->prepare_filter_fn = prepare_filter_asset_library;
filelist->filter_fn = is_filtered_asset_library;
filelist->tags |= FILELIST_TAGS_USES_MAIN_DATA;
break;
case FILE_MAIN_ASSET:
filelist->check_dir_fn = filelist_checkdir_main_assets;
@ -1821,6 +1850,12 @@ void filelist_settype(FileList *filelist, short type)
filelist->flags |= FL_FORCE_RESET;
}
static void filelist_clear_asset_library(FileList *filelist)
{
/* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */
filelist->asset_library = NULL;
}
void filelist_clear_ex(struct FileList *filelist,
const bool do_asset_library,
const bool do_cache,
@ -1844,17 +1879,64 @@ void filelist_clear_ex(struct FileList *filelist,
BLI_ghash_clear(filelist->selection_state, NULL, NULL);
}
if (do_asset_library && (filelist->asset_library != NULL)) {
/* The AssetLibraryService owns the AssetLibrary pointer, so no need for us to free it. */
filelist->asset_library = NULL;
if (do_asset_library) {
filelist_clear_asset_library(filelist);
}
}
void filelist_clear(struct FileList *filelist)
static void filelist_clear_main_files(FileList *filelist,
const bool do_asset_library,
const bool do_cache,
const bool do_selection)
{
if (!filelist || !(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) {
return;
}
filelist_tag_needs_filtering(filelist);
if (do_cache) {
filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size);
}
const int removed_files = filelist_intern_free_main_files(&filelist->filelist_intern);
filelist->filelist.nbr_entries -= removed_files;
filelist->filelist.nbr_entries_filtered = FILEDIR_NBR_ENTRIES_UNSET;
BLI_assert(filelist->filelist.nbr_entries > FILEDIR_NBR_ENTRIES_UNSET);
if (do_selection && filelist->selection_state) {
BLI_ghash_clear(filelist->selection_state, NULL, NULL);
}
if (do_asset_library) {
filelist_clear_asset_library(filelist);
}
}
void filelist_clear(FileList *filelist)
{
filelist_clear_ex(filelist, true, true, true);
}
/**
* A "smarter" version of #filelist_clear() that calls partial clearing based on the filelist
* force-reset flags.
*/
void filelist_clear_from_reset_tag(FileList *filelist)
{
/* Do a full clear if needed. */
if (filelist->flags & FL_FORCE_RESET) {
filelist_clear(filelist);
return;
}
if (filelist->flags & FL_FORCE_RESET_MAIN_FILES) {
filelist_clear_main_files(filelist, false, true, false);
return;
}
}
void filelist_free(struct FileList *filelist)
{
if (!filelist) {
@ -1977,7 +2059,7 @@ void filelist_setrecursion(struct FileList *filelist, const int recursion_level)
bool filelist_needs_force_reset(FileList *filelist)
{
return (filelist->flags & FL_FORCE_RESET) != 0;
return (filelist->flags & (FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES)) != 0;
}
void filelist_tag_force_reset(FileList *filelist)
@ -1985,6 +2067,14 @@ void filelist_tag_force_reset(FileList *filelist)
filelist->flags |= FL_FORCE_RESET;
}
void filelist_tag_force_reset_mainfiles(FileList *filelist)
{
if (!(filelist->tags & FILELIST_TAGS_USES_MAIN_DATA)) {
return;
}
filelist->flags |= FL_FORCE_RESET_MAIN_FILES;
}
bool filelist_is_ready(struct FileList *filelist)
{
return (filelist->flags & FL_IS_READY) != 0;
@ -2714,9 +2804,10 @@ int ED_file_extension_icon(const char *path)
}
}
int filelist_needs_reading(struct FileList *filelist)
int filelist_needs_reading(FileList *filelist)
{
return (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET);
return (filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET) ||
filelist_needs_force_reset(filelist);
}
uint filelist_entry_select_set(const FileList *filelist,
@ -3279,6 +3370,9 @@ typedef struct FileListReadJob {
char main_name[FILE_MAX];
Main *current_main;
struct FileList *filelist;
/** Set to request a partial read that only adds files representing #Main data (IDs). Used when
* #Main may have received changes of interest (e.g. asset removed or renamed). */
bool only_main_data;
/** Shallow copy of #filelist for thread-safe access.
*
@ -3293,6 +3387,26 @@ typedef struct FileListReadJob {
struct FileList *tmp_filelist;
} FileListReadJob;
static void filelist_readjob_append_entries(FileListReadJob *job_params,
ListBase *from_entries,
int nbr_from_entries,
short *do_update)
{
BLI_assert(BLI_listbase_count(from_entries) == nbr_from_entries);
if (nbr_from_entries <= 0) {
*do_update = false;
return;
}
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
BLI_mutex_lock(&job_params->lock);
BLI_movelisttolist(&filelist->filelist.entries, from_entries);
filelist->filelist.nbr_entries += nbr_from_entries;
BLI_mutex_unlock(&job_params->lock);
*do_update = true;
}
static bool filelist_readjob_should_recurse_into_entry(const int max_recursion,
const bool is_lib,
const int current_recursion_level,
@ -3329,11 +3443,11 @@ static bool filelist_readjob_should_recurse_into_entry(const int max_recursion,
return true;
}
static void filelist_readjob_do(const bool do_lib,
FileListReadJob *job_params,
const short *stop,
short *do_update,
float *progress)
static void filelist_readjob_recursive_dir_add_items(const bool do_lib,
FileListReadJob *job_params,
const short *stop,
short *do_update,
float *progress)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
ListBase entries = {0};
@ -3345,13 +3459,6 @@ static void filelist_readjob_do(const bool do_lib,
const int max_recursion = filelist->max_recursion;
int nbr_done_dirs = 0, nbr_todo_dirs = 1;
// BLI_assert(filelist->filtered == NULL);
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
/* A valid, but empty directory from now. */
filelist->filelist.nbr_entries = 0;
todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__);
td_dir = BLI_stack_push_r(todo_dirs);
td_dir->level = 1;
@ -3439,16 +3546,7 @@ static void filelist_readjob_do(const bool do_lib,
}
}
if (nbr_entries) {
BLI_mutex_lock(&job_params->lock);
*do_update = true;
BLI_movelisttolist(&filelist->filelist.entries, &entries);
filelist->filelist.nbr_entries += nbr_entries;
BLI_mutex_unlock(&job_params->lock);
}
filelist_readjob_append_entries(job_params, &entries, nbr_entries, do_update);
nbr_done_dirs++;
*progress = (float)nbr_done_dirs / (float)nbr_todo_dirs;
@ -3465,6 +3563,24 @@ static void filelist_readjob_do(const bool do_lib,
BLI_stack_free(todo_dirs);
}
static void filelist_readjob_do(const bool do_lib,
FileListReadJob *job_params,
const short *stop,
short *do_update,
float *progress)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
// BLI_assert(filelist->filtered == NULL);
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
/* A valid, but empty directory from now. */
filelist->filelist.nbr_entries = 0;
filelist_readjob_recursive_dir_add_items(do_lib, job_params, stop, do_update, progress);
}
static void filelist_readjob_dir(FileListReadJob *job_params,
short *stop,
short *do_update,
@ -3502,57 +3618,41 @@ static void filelist_readjob_load_asset_library_data(FileListReadJob *job_params
{
FileList *tmp_filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
if (job_params->filelist->asset_library_ref != NULL) {
char library_root_path[FILE_MAX];
filelist_asset_library_path(job_params, library_root_path);
*do_update = false;
/* Load asset catalogs, into the temp filelist for thread-safety.
* #filelist_readjob_endjob() will move it into the real filelist. */
tmp_filelist->asset_library = BKE_asset_library_load(library_root_path);
*do_update = true;
if (job_params->filelist->asset_library_ref == NULL) {
return;
}
if (tmp_filelist->asset_library != NULL) {
/* Asset library already loaded. */
return;
}
char library_root_path[FILE_MAX];
filelist_asset_library_path(job_params, library_root_path);
/* Load asset catalogs, into the temp filelist for thread-safety.
* #filelist_readjob_endjob() will move it into the real filelist. */
tmp_filelist->asset_library = BKE_asset_library_load(library_root_path);
*do_update = true;
}
static void filelist_readjob_asset_library(FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress)
{
filelist_readjob_load_asset_library_data(job_params, do_update);
filelist_readjob_lib(job_params, stop, do_update, progress);
}
static void filelist_readjob_main(FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress)
{
/* TODO! */
filelist_readjob_dir(job_params, stop, do_update, progress);
}
/**
* \warning Acts on main, so NOT thread-safe!
*/
static void filelist_readjob_main_assets(FileListReadJob *job_params,
short *UNUSED(stop),
short *do_update,
float *UNUSED(progress))
static void filelist_readjob_main_assets_add_items(FileListReadJob *job_params,
short *UNUSED(stop),
short *do_update,
float *UNUSED(progress))
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
filelist_readjob_load_asset_library_data(job_params, do_update);
/* A valid, but empty directory from now. */
filelist->filelist.nbr_entries = 0;
FileListInternEntry *entry;
ListBase tmp_entries = {0};
ID *id_iter;
int nbr_entries = 0;
/* Make sure no IDs are added/removed/reallocated in the main thread while this is running in
* parallel. */
BKE_main_lock(job_params->current_main);
FOREACH_MAIN_ID_BEGIN (job_params->current_main, id_iter) {
if (!id_iter->asset_data) {
continue;
@ -3575,6 +3675,8 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params,
}
FOREACH_MAIN_ID_END;
BKE_main_unlock(job_params->current_main);
if (nbr_entries) {
*do_update = true;
@ -3584,6 +3686,82 @@ static void filelist_readjob_main_assets(FileListReadJob *job_params,
}
}
/**
* Check if \a bmain is stored within the root path of \a filelist. This means either directly or
* in some nested directory. In other words, it checks if the \a filelist root path is contained in
* the path to \a bmain.
* This is irrespective of the recursion level displayed, it basically assumes unlimited recursion
* levels.
*/
static bool filelist_contains_main(const FileList *filelist, const Main *bmain)
{
const char *main_path = BKE_main_blendfile_path(bmain);
return main_path[0] && BLI_path_contains(filelist->filelist.root, main_path);
}
static void filelist_readjob_asset_library(FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
/* A valid, but empty file-list from now. */
filelist->filelist.nbr_entries = 0;
/* NOP if already read. */
filelist_readjob_load_asset_library_data(job_params, do_update);
if (filelist_contains_main(filelist, job_params->current_main)) {
filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress);
}
if (!job_params->only_main_data) {
filelist_readjob_recursive_dir_add_items(true, job_params, stop, do_update, progress);
}
}
static void filelist_readjob_main(FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress)
{
/* TODO! */
filelist_readjob_dir(job_params, stop, do_update, progress);
}
static void filelist_readjob_main_assets(FileListReadJob *job_params,
short *stop,
short *do_update,
float *progress)
{
FileList *filelist = job_params->tmp_filelist; /* Use the thread-safe filelist queue. */
BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) &&
(filelist->filelist.nbr_entries == FILEDIR_NBR_ENTRIES_UNSET));
filelist_readjob_load_asset_library_data(job_params, do_update);
/* A valid, but empty file-list from now. */
filelist->filelist.nbr_entries = 0;
filelist_readjob_main_assets_add_items(job_params, stop, do_update, progress);
}
/**
* Check if the read-job is requesting a partial reread of the file list only.
*/
static bool filelist_readjob_is_partial_read(const FileListReadJob *read_job)
{
return read_job->only_main_data;
}
/**
* \note This may trigger partial filelist reading. If the #FL_FORCE_RESET_MAIN_FILES flag is set,
* some current entries are kept and we just call the readjob to update the main files (see
* #FileListReadJob.only_main_data).
*/
static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress)
{
FileListReadJob *flrj = flrjv;
@ -3594,8 +3772,6 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update
BLI_mutex_lock(&flrj->lock);
BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist);
BLI_assert_msg(flrj->filelist->asset_library == NULL,
"Asset library should not yet be assigned at start of read job");
flrj->tmp_filelist = MEM_dupallocN(flrj->filelist);
@ -3604,7 +3780,12 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update
flrj->tmp_filelist->filelist_intern.filtered = NULL;
BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries);
filelist_uid_unset(&flrj->tmp_filelist->filelist_intern.curr_uid);
if (filelist_readjob_is_partial_read(flrj)) {
/* Don't unset the current UID on partial read, would give duplicates otherwise. */
}
else {
filelist_uid_unset(&flrj->tmp_filelist->filelist_intern.curr_uid);
}
flrj->tmp_filelist->libfiledata = NULL;
memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache));
@ -3617,6 +3798,11 @@ static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update
flrj->tmp_filelist->read_job_fn(flrj, stop, do_update, progress);
}
/**
* \note This may update for a partial filelist reading job. If the #FL_FORCE_RESET_MAIN_FILES flag
* is set, some current entries are kept and we just call the readjob to update the main
* files (see #FileListReadJob.only_main_data).
*/
static void filelist_readjob_update(void *flrjv)
{
FileListReadJob *flrj = flrjv;
@ -3638,7 +3824,11 @@ static void filelist_readjob_update(void *flrjv)
if (flrj->tmp_filelist->asset_library) {
flrj->filelist->asset_library = flrj->tmp_filelist->asset_library;
flrj->tmp_filelist->asset_library = NULL; /* MUST be NULL to avoid double-free. */
}
/* Important for partial reads: Copy increased UID counter back to the real list. */
if (flrj->tmp_filelist->filelist_intern.curr_uid > fl_intern->curr_uid) {
fl_intern->curr_uid = flrj->tmp_filelist->filelist_intern.curr_uid;
}
BLI_mutex_unlock(&flrj->lock);
@ -3703,8 +3893,11 @@ void filelist_readjob_start(FileList *filelist, const int space_notifier, const
flrj->filelist = filelist;
flrj->current_main = bmain;
BLI_strncpy(flrj->main_name, BKE_main_blendfile_path(bmain), sizeof(flrj->main_name));
if ((filelist->flags & FL_FORCE_RESET_MAIN_FILES) && !(filelist->flags & FL_FORCE_RESET)) {
flrj->only_main_data = true;
}
filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY);
filelist->flags &= ~(FL_FORCE_RESET | FL_FORCE_RESET_MAIN_FILES | FL_IS_READY);
filelist->flags |= FL_IS_PENDING;
/* Init even for single threaded execution. Called functions use it. */

View File

@ -96,6 +96,7 @@ void filelist_clear_ex(struct FileList *filelist,
const bool do_asset_library,
const bool do_cache,
const bool do_selection);
void filelist_clear_from_reset_tag(struct FileList *filelist);
void filelist_free(struct FileList *filelist);
const char *filelist_dir(struct FileList *filelist);
@ -117,6 +118,7 @@ bool filelist_file_cache_block(struct FileList *filelist, const int index);
bool filelist_needs_force_reset(struct FileList *filelist);
void filelist_tag_force_reset(struct FileList *filelist);
void filelist_tag_force_reset_mainfiles(struct FileList *filelist);
bool filelist_pending(struct FileList *filelist);
bool filelist_needs_reset_on_main_changes(const struct FileList *filelist);
bool filelist_is_ready(struct FileList *filelist);

View File

@ -303,15 +303,6 @@ static void file_ensure_valid_region_state(bContext *C,
}
}
/**
* Tag the space to recreate the file-list.
*/
static void file_tag_reset_list(ScrArea *area, SpaceFile *sfile)
{
filelist_tag_force_reset(sfile->files);
ED_area_tag_refresh(area);
}
static void file_refresh(const bContext *C, ScrArea *area)
{
wmWindowManager *wm = CTX_wm_manager(C);
@ -326,7 +317,7 @@ static void file_refresh(const bContext *C, ScrArea *area)
if (sfile->files && (sfile->tags & FILE_TAG_REBUILD_MAIN_FILES) &&
filelist_needs_reset_on_main_changes(sfile->files)) {
filelist_tag_force_reset(sfile->files);
filelist_tag_force_reset_mainfiles(sfile->files);
}
sfile->tags &= ~FILE_TAG_REBUILD_MAIN_FILES;
@ -369,7 +360,7 @@ static void file_refresh(const bContext *C, ScrArea *area)
if (filelist_needs_force_reset(sfile->files)) {
filelist_readjob_stop(sfile->files, wm);
filelist_clear(sfile->files);
filelist_clear_from_reset_tag(sfile->files);
}
if (filelist_needs_reading(sfile->files)) {
@ -431,9 +422,8 @@ static void file_on_reload_callback_call(SpaceFile *sfile)
static void file_reset_filelist_showing_main_data(ScrArea *area, SpaceFile *sfile)
{
if (sfile->files && filelist_needs_reset_on_main_changes(sfile->files)) {
/* Full refresh of the file list if local asset data was changed. Refreshing this view
* is cheap and users expect this to be updated immediately. */
file_tag_reset_list(area, sfile);
filelist_tag_force_reset_mainfiles(sfile->files);
ED_area_tag_refresh(area);
}
}