diff options
Diffstat (limited to 'source/blender/editors/space_file')
-rw-r--r-- | source/blender/editors/space_file/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/editors/space_file/SConscript | 5 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_draw.c | 243 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_intern.h | 2 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_ops.c | 302 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_panels.c | 4 | ||||
-rw-r--r-- | source/blender/editors/space_file/file_utils.c | 3 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.c | 2438 | ||||
-rw-r--r-- | source/blender/editors/space_file/filelist.h | 50 | ||||
-rw-r--r-- | source/blender/editors/space_file/filesel.c | 97 | ||||
-rw-r--r-- | source/blender/editors/space_file/fsmenu.c | 53 | ||||
-rw-r--r-- | source/blender/editors/space_file/space_file.c | 69 |
12 files changed, 2388 insertions, 880 deletions
diff --git a/source/blender/editors/space_file/CMakeLists.txt b/source/blender/editors/space_file/CMakeLists.txt index 4c33499da41..fd701a8be4c 100644 --- a/source/blender/editors/space_file/CMakeLists.txt +++ b/source/blender/editors/space_file/CMakeLists.txt @@ -24,12 +24,14 @@ set(INC ../../blenkernel ../../blenlib ../../blenloader + ../../blentranslation ../../imbuf ../../gpu ../../makesdna ../../makesrna ../../render/extern/include ../../windowmanager + ../../../../intern/atomic ../../../../intern/guardedalloc ../../../../intern/glew-mx ) diff --git a/source/blender/editors/space_file/SConscript b/source/blender/editors/space_file/SConscript index 85c3e073922..a66a14a32de 100644 --- a/source/blender/editors/space_file/SConscript +++ b/source/blender/editors/space_file/SConscript @@ -29,6 +29,7 @@ Import ('env') sources = env.Glob('*.c') incs = [ + '#/intern/atomic', '#/intern/guardedalloc', env['BF_GLEW_INC'], '#/intern/glew-mx', @@ -37,6 +38,7 @@ incs = [ '../../blenkernel', '../../blenlib', '../../blenloader', + '../../blentranslation', '../../gpu', '../../imbuf', '../../makesdna', @@ -46,7 +48,8 @@ incs = [ ] incs = ' '.join(incs) -defs = env['BF_GL_DEFINITIONS'] +defs = [] +defs += env['BF_GL_DEFINITIONS'] if env['WITH_BF_OPENJPEG']: defs.append('WITH_OPENJPEG') diff --git a/source/blender/editors/space_file/file_draw.c b/source/blender/editors/space_file/file_draw.c index 78638334338..2d9ecbdf415 100644 --- a/source/blender/editors/space_file/file_draw.c +++ b/source/blender/editors/space_file/file_draw.c @@ -35,6 +35,7 @@ #include "BLI_blenlib.h" #include "BLI_utildefines.h" #include "BLI_fileops_types.h" +#include "BLI_math.h" #ifdef WIN32 # include "BLI_winstuff.h" @@ -47,7 +48,9 @@ #include "BKE_global.h" #include "BKE_main.h" -#include "BLF_translation.h" +#include "BLO_readfile.h" + +#include "BLT_translation.h" #include "IMB_imbuf_types.h" @@ -64,12 +67,20 @@ #include "UI_resources.h" #include "UI_view2d.h" +#include "WM_api.h" #include "WM_types.h" #include "filelist.h" #include "file_intern.h" // own include +/* Dummy helper - we need dynamic tooltips here. */ +static char *file_draw_tooltip_func(bContext *UNUSED(C), void *argN, const char *UNUSED(tip)) +{ + char *dyn_tooltip = argN; + return BLI_strdup(dyn_tooltip); +} + /* Note: This function uses pixelspace (0, 0, winx, winy), not view2d. * The controls are laid out as follows: * @@ -157,9 +168,9 @@ void file_draw_buttons(const bContext *C, ARegion *ar) /* Text input fields for directory and file. */ if (available_w > 0) { - const struct direntry *file = sfile->files ? filelist_file(sfile->files, params->active_file) : NULL; + const struct FileDirEntry *file = sfile->files ? filelist_file(sfile->files, params->active_file) : NULL; int overwrite_alert = file_draw_check_exists(sfile); - const bool is_active_dir = file && file->path && BLI_is_dir(file->path); + const bool is_active_dir = file && (file->typeflag & FILE_TYPE_FOLDER); /* callbacks for operator check functions */ UI_block_func_set(block, file_draw_check_cb, NULL, NULL); @@ -220,10 +231,18 @@ void file_draw_buttons(const bContext *C, ARegion *ar) /* Execute / cancel buttons. */ if (loadbutton) { - const struct direntry *file = filelist_file(sfile->files, params->active_file); - const char *str_exec = (file && file->path && BLI_is_dir(file->path)) ? - /* params->title is already translated! */ - IFACE_("Open Directory") : params->title; + const struct FileDirEntry *file = sfile->files ? filelist_file(sfile->files, params->active_file) : NULL; + char const *str_exec; + + if (file && FILENAME_IS_PARENT(file->relpath)) { + str_exec = IFACE_("Parent Directory"); + } + else if (file && file->typeflag & FILE_TYPE_DIR) { + str_exec = IFACE_("Open Directory"); + } + else { + str_exec = params->title; /* params->title is already translated! */ + } uiDefButO(block, UI_BTYPE_BUT, "FILE_OT_execute", WM_OP_EXEC_REGION_WIN, str_exec, max_x - loadbutton, line1_y, loadbutton, btn_h, ""); @@ -244,44 +263,6 @@ static void draw_tile(int sx, int sy, int width, int height, int colorid, int sh } -static int get_file_icon(struct direntry *file) -{ - if (file->type & S_IFDIR) { - if (FILENAME_IS_PARENT(file->relname)) { - return ICON_FILE_PARENT; - } - if (file->flags & FILE_TYPE_APPLICATIONBUNDLE) { - return ICON_UGLYPACKAGE; - } - if (file->flags & FILE_TYPE_BLENDER) { - return ICON_FILE_BLEND; - } - return ICON_FILE_FOLDER; - } - else if (file->flags & FILE_TYPE_BLENDER) - return ICON_FILE_BLEND; - else if (file->flags & FILE_TYPE_BLENDER_BACKUP) - return ICON_FILE_BACKUP; - else if (file->flags & FILE_TYPE_IMAGE) - return ICON_FILE_IMAGE; - else if (file->flags & FILE_TYPE_MOVIE) - return ICON_FILE_MOVIE; - else if (file->flags & FILE_TYPE_PYSCRIPT) - return ICON_FILE_SCRIPT; - else if (file->flags & FILE_TYPE_SOUND) - return ICON_FILE_SOUND; - else if (file->flags & FILE_TYPE_FTFONT) - return ICON_FILE_FONT; - else if (file->flags & FILE_TYPE_BTX) - return ICON_FILE_BLANK; - else if (file->flags & FILE_TYPE_COLLADA) - return ICON_FILE_BLANK; - else if (file->flags & FILE_TYPE_TEXT) - return ICON_FILE_TEXT; - else - return ICON_FILE_BLANK; -} - static void file_draw_icon(uiBlock *block, const char *path, int sx, int sy, int icon, int width, int height, bool drag) { uiBut *but; @@ -293,10 +274,12 @@ static void file_draw_icon(uiBlock *block, const char *path, int sx, int sy, int /*if (icon == ICON_FILE_BLANK) alpha = 0.375f;*/ - but = uiDefIconBut(block, UI_BTYPE_LABEL, 0, icon, x, y, width, height, NULL, 0.0f, 0.0f, 0.0f, 0.0f, ""); + but = uiDefIconBut(block, UI_BTYPE_LABEL, 0, icon, x, y, width, height, NULL, 0.0f, 0.0f, 0.0f, 0.0f, NULL); + UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path)); if (drag) { - UI_but_drag_set_path(but, path); + /* path is no more static, cannot give it directly to but... */ + UI_but_drag_set_path(but, BLI_strdup(path), true); } } @@ -338,7 +321,9 @@ void file_calc_previews(const bContext *C, ARegion *ar) UI_view2d_totRect_set(v2d, sfile->layout->width, sfile->layout->height); } -static void file_draw_preview(uiBlock *block, struct direntry *file, int sx, int sy, ImBuf *imb, FileLayout *layout, bool is_icon, bool drag) +static void file_draw_preview( + uiBlock *block, const char *path, int sx, int sy, const float icon_aspect, + ImBuf *imb, const int icon, FileLayout *layout, const bool is_icon, const int typeflags, const bool drag) { uiBut *but; float fx, fy; @@ -348,7 +333,7 @@ static void file_draw_preview(uiBlock *block, struct direntry *file, int sx, int float scaledx, scaledy; float scale; int ex, ey; - bool use_dropshadow = !is_icon && (file->flags & FILE_TYPE_IMAGE); + bool use_dropshadow = !is_icon && (typeflags & FILE_TYPE_IMAGE); BLI_assert(imb != NULL); @@ -394,7 +379,7 @@ static void file_draw_preview(uiBlock *block, struct direntry *file, int sx, int glEnable(GL_BLEND); /* the image */ - if (!is_icon && file->flags & FILE_TYPE_FTFONT) { + if (!is_icon && typeflags & FILE_TYPE_FTFONT) { UI_ThemeColor(TH_TEXT); } else { @@ -402,16 +387,23 @@ static void file_draw_preview(uiBlock *block, struct direntry *file, int sx, int } glaDrawPixelsTexScaled((float)xco, (float)yco, imb->x, imb->y, GL_RGBA, GL_UNSIGNED_BYTE, GL_NEAREST, imb->rect, scale, scale); + if (icon) { + UI_icon_draw_aspect((float)xco, (float)yco, icon, icon_aspect, 1.0f); + } + /* border */ if (use_dropshadow) { glColor4f(0.0f, 0.0f, 0.0f, 0.4f); fdrawbox((float)xco, (float)yco, (float)(xco + ex), (float)(yco + ey)); } + but = uiDefBut(block, UI_BTYPE_LABEL, 0, "", xco, yco, ex, ey, NULL, 0.0, 0.0, 0, 0, NULL); + UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path)); + /* dragregion */ if (drag) { - but = uiDefBut(block, UI_BTYPE_LABEL, 0, "", xco, yco, ex, ey, NULL, 0.0, 0.0, 0, 0, ""); - UI_but_drag_set_image(but, file->path, get_file_icon(file), imb, scale); + /* path is no more static, cannot give it directly to but... */ + UI_but_drag_set_image(but, BLI_strdup(path), icon, imb, scale, true); } glDisable(GL_BLEND); @@ -424,6 +416,7 @@ static void renamebutton_cb(bContext *C, void *UNUSED(arg1), char *oldname) char filename[FILE_MAX + 12]; wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = (SpaceFile *)CTX_wm_space_data(C); + ScrArea *sa = CTX_wm_area(C); ARegion *ar = CTX_wm_region(C); BLI_make_file_string(G.main->name, orgname, sfile->params->dir, oldname); @@ -435,7 +428,7 @@ static void renamebutton_cb(bContext *C, void *UNUSED(arg1), char *oldname) if (!BLI_exists(newname)) { BLI_rename(orgname, newname); /* to make sure we show what is on disk */ - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); } ED_region_tag_redraw(ar); @@ -500,7 +493,8 @@ void file_draw_list(const bContext *C, ARegion *ar) FileLayout *layout = ED_fileselect_get_layout(sfile, ar); View2D *v2d = &ar->v2d; struct FileList *files = sfile->files; - struct direntry *file; + struct FileDirEntry *file; + const char *root = filelist_dir(files); ImBuf *imb; uiBlock *block = UI_block_begin(C, ar, __func__, UI_EMBOSS); int numfiles; @@ -513,8 +507,11 @@ void file_draw_list(const bContext *C, ARegion *ar) short align; bool do_drag; int column_space = 0.6f * UI_UNIT_X; + const bool small_size = SMALL_SIZE_CHECK(params->thumbnail_size); + const bool update_stat_strings = small_size != SMALL_SIZE_CHECK(layout->curr_size); + const float thumb_icon_aspect = sqrtf(64.0f / (float)(params->thumbnail_size)); - numfiles = filelist_numfiles(files); + numfiles = filelist_files_ensure(files); if (params->display != FILE_IMGDISPLAY) { @@ -536,27 +533,61 @@ void file_draw_list(const bContext *C, ARegion *ar) numfiles_layout += layout->columns; } + filelist_file_cache_slidingwindow_set(files, numfiles_layout); + textwidth = (FILE_IMGDISPLAY == params->display) ? layout->tile_w : (int)layout->column_widths[COLUMN_NAME]; textheight = (int)(layout->textheight * 3.0 / 2.0 + 0.5); align = (FILE_IMGDISPLAY == params->display) ? UI_STYLE_TEXT_CENTER : UI_STYLE_TEXT_LEFT; + if (numfiles > 0) { + const bool success = filelist_file_cache_block(files, min_ii(offset + (numfiles_layout / 2), numfiles - 1)); + BLI_assert(success); + UNUSED_VARS_NDEBUG(success); + + filelist_cache_previews_update(files); + + /* Handle preview timer here, since it's filelist_file_cache_block() and filelist_cache_previews_update() + * which controlls previews task. */ + { + const bool previews_running = filelist_cache_previews_running(files); +// printf("%s: preview task: %d\n", __func__, previews_running); + if (previews_running && !sfile->previews_timer) { + sfile->previews_timer = WM_event_add_timer_notifier(CTX_wm_manager(C), CTX_wm_window(C), + NC_SPACE | ND_SPACE_FILE_PREVIEW, 0.01); + } + if (!previews_running && sfile->previews_timer) { + /* Preview is not running, no need to keep generating update events! */ +// printf("%s: Inactive preview task, sleeping!\n", __func__); + WM_event_remove_timer_notifier(CTX_wm_manager(C), CTX_wm_window(C), sfile->previews_timer); + sfile->previews_timer = NULL; + } + } + } + for (i = offset; (i < numfiles) && (i < offset + numfiles_layout); i++) { + unsigned int file_selflag; + char path[FILE_MAX_LIBEXTRA]; ED_fileselect_layout_tilepos(layout, i, &sx, &sy); sx += (int)(v2d->tot.xmin + 0.1f * UI_UNIT_X); sy = (int)(v2d->tot.ymax - sy); file = filelist_file(files, i); + file_selflag = filelist_entry_select_get(sfile->files, file, CHECK_ALL); + + BLI_join_dirfile(path, sizeof(path), root, file->relpath); UI_ThemeColor4(TH_TEXT); - if (!(file->selflag & FILE_SEL_EDITING)) { - if ((params->highlight_file == i) || (file->selflag & FILE_SEL_HIGHLIGHTED) || (file->selflag & FILE_SEL_SELECTED)) { - int colorid = (file->selflag & FILE_SEL_SELECTED) ? TH_HILITE : TH_BACK; - int shade = (params->highlight_file == i) || (file->selflag & FILE_SEL_HIGHLIGHTED) ? 35 : 0; + if (!(file_selflag & FILE_SEL_EDITING)) { + if ((params->highlight_file == i) || (file_selflag & FILE_SEL_HIGHLIGHTED) || + (file_selflag & FILE_SEL_SELECTED)) + { + int colorid = (file_selflag & FILE_SEL_SELECTED) ? TH_HILITE : TH_BACK; + int shade = (params->highlight_file == i) || (file_selflag & FILE_SEL_HIGHLIGHTED) ? 35 : 0; - BLI_assert(i > 0 || FILENAME_IS_CURRPAR(file->relname)); + BLI_assert(i > 0 || FILENAME_IS_CURRPAR(file->relpath)); draw_tile(sx, sy - 1, layout->tile_w + 4, sfile->layout->tile_h + layout->tile_border_y, colorid, shade); } @@ -564,26 +595,29 @@ void file_draw_list(const bContext *C, ARegion *ar) UI_draw_roundbox_corner_set(UI_CNR_NONE); /* don't drag parent or refresh items */ - do_drag = !(FILENAME_IS_CURRPAR(file->relname)); + do_drag = !(FILENAME_IS_CURRPAR(file->relpath)); if (FILE_IMGDISPLAY == params->display) { + const int icon = filelist_geticon(files, i, false); is_icon = 0; imb = filelist_getimage(files, i); if (!imb) { - imb = filelist_geticon(files, i); + imb = filelist_geticon_image(files, i); is_icon = 1; } - file_draw_preview(block, file, sx, sy, imb, layout, is_icon, do_drag); + file_draw_preview(block, path, sx, sy, thumb_icon_aspect, + imb, icon, layout, is_icon, file->typeflag, do_drag); } else { - file_draw_icon(block, file->path, sx, sy - (UI_UNIT_Y / 6), get_file_icon(file), ICON_DEFAULT_WIDTH_SCALE, ICON_DEFAULT_HEIGHT_SCALE, do_drag); + file_draw_icon(block, path, sx, sy - (UI_UNIT_Y / 6), filelist_geticon(files, i, true), + ICON_DEFAULT_WIDTH_SCALE, ICON_DEFAULT_HEIGHT_SCALE, do_drag); sx += ICON_DEFAULT_WIDTH_SCALE + 0.2f * UI_UNIT_X; } UI_ThemeColor4(TH_TEXT); - if (file->selflag & FILE_SEL_EDITING) { + if (file_selflag & FILE_SEL_EDITING) { uiBut *but; short width; @@ -591,9 +625,7 @@ void file_draw_list(const bContext *C, ARegion *ar) width = layout->tile_w - (ICON_DEFAULT_WIDTH_SCALE + 0.2f * UI_UNIT_X); } else if (params->display == FILE_LONGDISPLAY) { - width = layout->column_widths[COLUMN_NAME] + layout->column_widths[COLUMN_MODE1] + - layout->column_widths[COLUMN_MODE2] + layout->column_widths[COLUMN_MODE3] + - (column_space * 3.5f); + width = layout->column_widths[COLUMN_NAME] + (column_space * 3.5f); } else { BLI_assert(params->display == FILE_IMGDISPLAY); @@ -601,59 +633,68 @@ void file_draw_list(const bContext *C, ARegion *ar) } but = uiDefBut(block, UI_BTYPE_TEXT, 1, "", sx, sy - layout->tile_h - 0.15f * UI_UNIT_X, - width, textheight, sfile->params->renameedit, 1.0f, (float)sizeof(sfile->params->renameedit), 0, 0, ""); + width, textheight, sfile->params->renameedit, 1.0f, + (float)sizeof(sfile->params->renameedit), 0, 0, ""); UI_but_func_rename_set(but, renamebutton_cb, file); UI_but_flag_enable(but, UI_BUT_NO_UTF8); /* allow non utf8 names */ UI_but_flag_disable(but, UI_BUT_UNDO); if (false == UI_but_active_only(C, ar, block, but)) { - file->selflag &= ~FILE_SEL_EDITING; + file_selflag = filelist_entry_select_set( + sfile->files, file, FILE_SEL_REMOVE, FILE_SEL_EDITING, CHECK_ALL); } } - if (!(file->selflag & FILE_SEL_EDITING)) { + if (!(file_selflag& FILE_SEL_EDITING)) { int tpos = (FILE_IMGDISPLAY == params->display) ? sy - layout->tile_h + layout->textheight : sy; - file_draw_string(sx + 1, tpos, file->relname, (float)textwidth, textheight, align); + file_draw_string(sx + 1, tpos, file->name, (float)textwidth, textheight, align); } + sx += (int)layout->column_widths[COLUMN_NAME] + column_space; if (params->display == FILE_SHORTDISPLAY) { - sx += (int)layout->column_widths[COLUMN_NAME] + column_space; - if (!(file->type & S_IFDIR)) { - file_draw_string(sx, sy, file->size, layout->column_widths[COLUMN_SIZE], layout->tile_h, align); - sx += (int)layout->column_widths[COLUMN_SIZE] + column_space; + if ((file->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) || + !(file->typeflag & (FILE_TYPE_DIR | FILE_TYPE_BLENDERLIB))) + { + if ((file->entry->size_str[0] == '\0') || update_stat_strings) { + BLI_filelist_entry_size_to_string(NULL, file->entry->size, small_size, file->entry->size_str); + } + file_draw_string( + sx, sy, file->entry->size_str, layout->column_widths[COLUMN_SIZE], layout->tile_h, align); } + sx += (int)layout->column_widths[COLUMN_SIZE] + column_space; } else if (params->display == FILE_LONGDISPLAY) { - sx += (int)layout->column_widths[COLUMN_NAME] + column_space; - -#ifndef WIN32 - /* rwx rwx rwx */ - file_draw_string(sx, sy, file->mode1, layout->column_widths[COLUMN_MODE1], layout->tile_h, align); - sx += layout->column_widths[COLUMN_MODE1] + column_space; - - file_draw_string(sx, sy, file->mode2, layout->column_widths[COLUMN_MODE2], layout->tile_h, align); - sx += layout->column_widths[COLUMN_MODE2] + column_space; - - file_draw_string(sx, sy, file->mode3, layout->column_widths[COLUMN_MODE3], layout->tile_h, align); - sx += layout->column_widths[COLUMN_MODE3] + column_space; - - file_draw_string(sx, sy, file->owner, layout->column_widths[COLUMN_OWNER], layout->tile_h, align); - sx += layout->column_widths[COLUMN_OWNER] + column_space; -#endif - - file_draw_string(sx, sy, file->date, layout->column_widths[COLUMN_DATE], layout->tile_h, align); - sx += (int)layout->column_widths[COLUMN_DATE] + column_space; - - file_draw_string(sx, sy, file->time, layout->column_widths[COLUMN_TIME], layout->tile_h, align); - sx += (int)layout->column_widths[COLUMN_TIME] + column_space; + if (!(file->typeflag & FILE_TYPE_BLENDERLIB) && !FILENAME_IS_CURRPAR(file->relpath)) { + if ((file->entry->date_str[0] == '\0') || update_stat_strings) { + BLI_filelist_entry_datetime_to_string( + NULL, file->entry->time, small_size, file->entry->time_str, file->entry->date_str); + } + file_draw_string( + sx, sy, file->entry->date_str, layout->column_widths[COLUMN_DATE], layout->tile_h, align); + sx += (int)layout->column_widths[COLUMN_DATE] + column_space; + file_draw_string( + sx, sy, file->entry->time_str, layout->column_widths[COLUMN_TIME], layout->tile_h, align); + sx += (int)layout->column_widths[COLUMN_TIME] + column_space; + } + else { + sx += (int)layout->column_widths[COLUMN_DATE] + column_space; + sx += (int)layout->column_widths[COLUMN_TIME] + column_space; + } - if (!(file->type & S_IFDIR)) { - file_draw_string(sx, sy, file->size, layout->column_widths[COLUMN_SIZE], layout->tile_h, align); - sx += (int)layout->column_widths[COLUMN_SIZE] + column_space; + if ((file->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) || + !(file->typeflag & (FILE_TYPE_DIR | FILE_TYPE_BLENDERLIB))) + { + if ((file->entry->size_str[0] == '\0') || update_stat_strings) { + BLI_filelist_entry_size_to_string(NULL, file->entry->size, small_size, file->entry->size_str); + } + file_draw_string( + sx, sy, file->entry->size_str, layout->column_widths[COLUMN_SIZE], layout->tile_h, align); } + sx += (int)layout->column_widths[COLUMN_SIZE] + column_space; } } UI_block_end(C, block); UI_block_draw(C, block); + layout->curr_size = params->thumbnail_size; } diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index b24780c6e05..baafefab1f6 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -48,6 +48,8 @@ struct ARegion *file_tools_region(struct ScrArea *sa); #define IMASEL_BUTTONS_HEIGHT (UI_UNIT_Y * 2) #define IMASEL_BUTTONS_MARGIN (UI_UNIT_Y / 6) +#define SMALL_SIZE_CHECK(_size) ((_size) < 64) /* Related to FileSelectParams.thumbnail_size. */ + void file_draw_buttons(const bContext *C, ARegion *ar); void file_calc_previews(const bContext *C, ARegion *ar); void file_draw_list(const bContext *C, ARegion *ar); diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index dddda43c3d3..36572d6469d 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -99,9 +99,9 @@ static void file_deselect_all(SpaceFile *sfile, unsigned int flag) { FileSelection sel; sel.first = 0; - sel.last = filelist_numfiles(sfile->files) - 1; + sel.last = filelist_files_ensure(sfile->files) - 1; - filelist_select(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL); + filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, flag, CHECK_ALL); } typedef enum FileSelect { @@ -139,7 +139,7 @@ static FileSelection file_selection_get(bContext *C, const rcti *rect, bool fill { ARegion *ar = CTX_wm_region(C); SpaceFile *sfile = CTX_wm_space_file(C); - int numfiles = filelist_numfiles(sfile->files); + int numfiles = filelist_files_ensure(sfile->files); FileSelection sel; sel = find_file_mouse_rect(sfile, ar, rect); @@ -152,7 +152,7 @@ static FileSelection file_selection_get(bContext *C, const rcti *rect, bool fill if (fill && (sel.last >= 0) && (sel.last < numfiles) ) { int f = sel.last; while (f >= 0) { - if (filelist_is_selected(sfile->files, f, CHECK_ALL) ) + if (filelist_entry_select_index_get(sfile->files, f, CHECK_ALL) ) break; f--; } @@ -168,8 +168,8 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen) FileSelect retval = FILE_SELECT_NOTHING; SpaceFile *sfile = CTX_wm_space_file(C); FileSelectParams *params = ED_fileselect_get_params(sfile); - int numfiles = filelist_numfiles(sfile->files); - struct direntry *file; + int numfiles = filelist_files_ensure(sfile->files); + const FileDirEntry *file; /* make the selected file active */ if ((selected_idx >= 0) && @@ -177,27 +177,33 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen) (file = filelist_file(sfile->files, selected_idx))) { params->highlight_file = selected_idx; - sfile->params->active_file = selected_idx; + params->active_file = selected_idx; - if (S_ISDIR(file->type)) { - const bool is_parent_dir = FILENAME_IS_PARENT(file->relname); + if (file->typeflag & FILE_TYPE_DIR) { + const bool is_parent_dir = FILENAME_IS_PARENT(file->relpath); if (do_diropen == false) { params->file[0] = '\0'; 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->relname) >= FILE_MAX) { + 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_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_cleanup_dir(G.main->name, params->dir); - strcat(params->dir, file->relname); + strcat(params->dir, file->relpath); BLI_add_slash(params->dir); } @@ -218,11 +224,12 @@ static FileSelect file_select_do(bContext *C, int selected_idx, bool do_diropen) */ static bool file_is_any_selected(struct FileList *files) { - const int numfiles = filelist_numfiles(files); + const int numfiles = filelist_files_ensure(files); int i; + /* Is any file selected ? */ for (i = 0; i < numfiles; ++i) { - if (filelist_is_selected(files, i, CHECK_ALL)) { + if (filelist_entry_select_index_get(files, i, CHECK_ALL)) { return true; } } @@ -230,6 +237,49 @@ static bool file_is_any_selected(struct FileList *files) return false; } +/** + * If \a file is outside viewbounds, this adjusts view to make sure it's inside + */ +static void file_ensure_inside_viewbounds(ARegion *ar, SpaceFile *sfile, const int file) +{ + FileLayout *layout = ED_fileselect_get_layout(sfile, ar); + rctf *cur = &ar->v2d.cur; + rcti rect; + bool changed = true; + + file_tile_boundbox(ar, 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 > ar->winy) { + cur->ymin = rect.ymin - (2 * layout->tile_border_y); + cur->ymax = cur->ymin + ar->winy; + } + /* up */ + else if (cur->ymax < rect.ymax) { + cur->ymax = rect.ymax + layout->tile_border_y; + cur->ymin = cur->ymax - ar->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 > ar->winx) { + cur->xmin = rect.xmin - layout->tile_border_x; + cur->xmax = cur->xmin + ar->winx; + } + /* right */ + else if (cur->xmax < rect.xmax) { + cur->xmax = rect.xmax + (2 * layout->tile_border_x); + cur->xmin = cur->xmax - ar->winx; + } + else { + BLI_assert(cur->xmin <= rect.xmin && cur->xmax >= rect.xmax && + cur->ymin <= rect.ymin && cur->ymax >= rect.ymax); + changed = false; + } + + if (changed) { + UI_view2d_curRect_validate(&ar->v2d); + } +} + static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select, bool fill, bool do_diropen) { @@ -239,7 +289,7 @@ static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select, const FileCheckType check_type = (sfile->params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_ALL; /* flag the files as selected in the filelist */ - filelist_select(sfile->files, &sel, select, FILE_SEL_SELECTED, check_type); + 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; @@ -247,7 +297,7 @@ static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select, /* 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_is_selected(sfile->files, sel.last, check_type)) { + if (filelist_entry_select_index_get(sfile->files, sel.last, check_type)) { retval = file_select_do(C, sel.last, do_diropen); } } @@ -255,6 +305,20 @@ static FileSelect file_select(bContext *C, const rcti *rect, FileSelType select, if (select != FILE_SEL_ADD && !file_is_any_selected(sfile->files)) { sfile->params->active_file = -1; } + else { + ARegion *ar = CTX_wm_region(C); + const FileLayout *layout = ED_fileselect_get_layout(sfile, ar); + + /* 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) && ar->winx > (1.2f * layout->tile_w)) || + ((layout->flag & FILE_LAYOUT_VER) && ar->winy > (2.0f * layout->tile_h))) + { + file_ensure_inside_viewbounds(ar, sfile, sel.last); + file_ensure_inside_viewbounds(ar, sfile, sel.first); + } + } /* update operator for name change event */ file_draw_check(C); @@ -269,6 +333,9 @@ static int file_border_select_find_last_selected( FileLayout *layout = ED_fileselect_get_layout(sfile, ar); rcti bounds_first, bounds_last; int dist_first, dist_last; + float mouseco_view[2]; + + UI_view2d_region_to_view(&ar->v2d, UNPACK2(mouse_xy), &mouseco_view[0], &mouseco_view[1]); file_tile_boundbox(ar, layout, sel->first, &bounds_first); file_tile_boundbox(ar, layout, sel->last, &bounds_last); @@ -278,18 +345,18 @@ static int file_border_select_find_last_selected( (layout->flag & FILE_LAYOUT_VER && bounds_first.ymin != bounds_last.ymin)) { /* use vertical distance */ - const int my_loc = mouse_xy[1] - ar->winrct.ymin; + 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 = mouse_xy[0] - ar->winrct.xmin; + 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; + return (dist_first < dist_last) ? sel->first : sel->last; } static int file_border_select_modal(bContext *C, wmOperator *op, const wmEvent *event) @@ -305,25 +372,24 @@ static int file_border_select_modal(bContext *C, wmOperator *op, const wmEvent * result = WM_border_select_modal(C, op, event); if (result == OPERATOR_RUNNING_MODAL) { - WM_operator_properties_border_to_rcti(op, &rect); BLI_rcti_isect(&(ar->v2d.mask), &rect, &rect); sel = file_selection_get(C, &rect, 0); - if ( (sel.first != params->sel_first) || (sel.last != params->sel_last) ) { + if ((sel.first != params->sel_first) || (sel.last != params->sel_last)) { int idx; file_deselect_all(sfile, FILE_SEL_HIGHLIGHTED); - filelist_select(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL); + 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--) { - struct direntry *file = filelist_file(sfile->files, idx); + const FileDirEntry *file = filelist_file(sfile->files, idx); /* dont highlight readonly file (".." or ".") on border select */ - if (FILENAME_IS_CURRPAR(file->relname)) { - file->selflag &= ~FILE_SEL_HIGHLIGHTED; + 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 */ @@ -333,7 +399,7 @@ static int file_border_select_modal(bContext *C, wmOperator *op, const wmEvent * } } params->sel_first = sel.first; params->sel_last = sel.last; - params->active_file = file_border_select_find_last_selected(sfile, ar, &sel, &event->x); + params->active_file = file_border_select_find_last_selected(sfile, ar, &sel, event->mval); } else { params->highlight_file = -1; @@ -366,7 +432,7 @@ static int file_border_select_exec(bContext *C, wmOperator *op) 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_select_file(sfile->files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); + filelist_entry_select_index_set(sfile->files, 0, 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); @@ -416,8 +482,9 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (sfile && sfile->params) { int idx = sfile->params->highlight_file; + int numfiles = filelist_files_ensure(sfile->files); - if (idx >= 0) { + if ((idx >= 0) && (idx < numfiles)) { /* single select, deselect all selected first */ if (!extend) { file_deselect_all(sfile, FILE_SEL_SELECTED); @@ -429,7 +496,7 @@ static int file_select_invoke(bContext *C, wmOperator *op, const wmEvent *event) if (extend) { /* unselect '..' parent entry - it's not supposed to be selected if more than one file is selected */ - filelist_select_file(sfile->files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); + filelist_entry_select_index_set(sfile->files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); } if (FILE_SELECT_DIR == ret) @@ -484,12 +551,13 @@ static bool file_walk_select_selection_set( if (has_selection) { if (extend && - filelist_is_selected(files, active_old, FILE_SEL_SELECTED) && - filelist_is_selected(files, active_new, FILE_SEL_SELECTED)) + 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_is_selected(files, other_site, FILE_SEL_SELECTED)); + 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 */ @@ -527,7 +595,7 @@ static bool file_walk_select_selection_set( params->highlight_file = params->active_file; /* unselect '..' parent entry - it's not supposed to be selected if more than one file is selected */ - filelist_select_file(files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); + filelist_entry_select_index_set(files, 0, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); } else { /* deselect all first */ @@ -548,21 +616,24 @@ static bool file_walk_select_selection_set( } /* fill selection between last and first selected file */ - filelist_select( + 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_select_file(files, active, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); + filelist_entry_select_index_set(files, active, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); } } else { - filelist_select_file( + 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(CTX_wm_region(C), sfile, params->active_file); + /* selection changed */ return true; } @@ -576,7 +647,7 @@ static bool file_walk_select_do( const bool extend, const bool fill) { struct FileList *files = sfile->files; - const int numfiles = filelist_numfiles(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; @@ -692,7 +763,7 @@ static int file_select_all_exec(bContext *C, wmOperator *UNUSED(op)) ScrArea *sa = CTX_wm_area(C); SpaceFile *sfile = CTX_wm_space_file(C); FileSelection sel; - const int numfiles = filelist_numfiles(sfile->files); + const int numfiles = filelist_files_ensure(sfile->files); const bool has_selection = file_is_any_selected(sfile->files); sel.first = 0; @@ -700,18 +771,18 @@ static int file_select_all_exec(bContext *C, wmOperator *UNUSED(op)) /* select all only if previously no file was selected */ if (has_selection) { - filelist_select(sfile->files, &sel, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); + filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_REMOVE, FILE_SEL_SELECTED, CHECK_ALL); sfile->params->active_file = -1; } else { const FileCheckType check_type = (sfile->params->flag & FILE_DIRSEL_ONLY) ? CHECK_DIRS : CHECK_FILES; int i; - filelist_select(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_SELECTED, check_type); + filelist_entries_select_index_range_set(sfile->files, &sel, FILE_SEL_ADD, FILE_SEL_SELECTED, check_type); /* set active_file to first selected */ for (i = 0; i < numfiles; i++) { - if (filelist_is_selected(sfile->files, i, check_type)) { + if (filelist_entry_select_index_get(sfile->files, i, check_type)) { sfile->params->active_file = i; break; } @@ -1029,7 +1100,7 @@ int file_highlight_set(SpaceFile *sfile, ARegion *ar, int mx, int my) if (sfile == NULL || sfile->files == NULL) return 0; - numfiles = filelist_numfiles(sfile->files); + numfiles = filelist_files_ensure(sfile->files); params = ED_fileselect_get_params(sfile); origfile = params->highlight_file; @@ -1143,17 +1214,17 @@ void file_sfile_to_operator(wmOperator *op, SpaceFile *sfile, char *filepath) /* this is called on operators check() so clear collections first since * they may be already set. */ { - int i, numfiles = filelist_numfiles(sfile->files); + 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_is_selected(sfile->files, i, CHECK_FILES)) { - struct direntry *file = filelist_file(sfile->files, i); + if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) { + FileDirEntry *file = filelist_file(sfile->files, i); RNA_property_collection_add(op->ptr, prop, &itemptr); - RNA_string_set(&itemptr, "name", file->relname); + RNA_string_set(&itemptr, "name", file->relpath); num_files++; } } @@ -1169,10 +1240,10 @@ void file_sfile_to_operator(wmOperator *op, SpaceFile *sfile, char *filepath) int num_dirs = 0; RNA_property_collection_clear(op->ptr, prop); for (i = 0; i < numfiles; i++) { - if (filelist_is_selected(sfile->files, i, CHECK_DIRS)) { - struct direntry *file = filelist_file(sfile->files, 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->relname); + RNA_string_set(&itemptr, "name", file->relpath); num_dirs++; } } @@ -1262,19 +1333,21 @@ int file_exec(bContext *C, wmOperator *exec_op) { wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); - const struct direntry *file = filelist_file(sfile->files, sfile->params->active_file); + const struct FileDirEntry *file = filelist_file(sfile->files, sfile->params->active_file); char filepath[FILE_MAX]; /* directory change */ - if (file && S_ISDIR(file->type)) { - BLI_assert(file->path == NULL || STRPREFIX(file->path, sfile->params->dir)); + if (file && (file->typeflag & FILE_TYPE_DIR)) { + if (!file->relpath) { + return OPERATOR_CANCELLED; + } - if (FILENAME_IS_PARENT(file->relname)) { + if (FILENAME_IS_PARENT(file->relpath)) { BLI_parent_dir(sfile->params->dir); } - else if (file->relname) { + else { BLI_cleanup_dir(G.main->name, sfile->params->dir); - strcat(sfile->params->dir, file->relname); + strcat(sfile->params->dir, file->relpath); BLI_add_slash(sfile->params->dir); } @@ -1287,10 +1360,11 @@ int file_exec(bContext *C, wmOperator *exec_op) /* when used as a macro, for doubleclick, * to prevent closing when doubleclicking 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 < filelist_numfiles(sfile->files); i++) { - if (filelist_is_selected(sfile->files, i, CHECK_ALL)) { + for (i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { active = 1; break; } @@ -1347,8 +1421,8 @@ int file_parent_exec(bContext *C, wmOperator *UNUSED(unused)) BLI_cleanup_dir(G.main->name, sfile->params->dir); /* if not browsing in .blend file, we still want to check whether the path is a directory */ if (sfile->params->type == FILE_LOADLIB) { - char tdir[FILE_MAX], tgroup[FILE_MAX]; - if (BLO_is_a_library(sfile->params->dir, tdir, tgroup)) { + char tdir[FILE_MAX]; + if (BLO_library_path_explode(sfile->params->dir, tdir, NULL, NULL)) { ED_file_change_dir(C, false); } else { @@ -1358,6 +1432,11 @@ int file_parent_exec(bContext *C, wmOperator *UNUSED(unused)) else { ED_file_change_dir(C, true); } + if (sfile->params->recursion_level > 1) { + /* Disable 'dirtree' recursion when going up in tree. */ + sfile->params->recursion_level = 0; + filelist_setrecursion(sfile->files, sfile->params->recursion_level); + } WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); } } @@ -1384,9 +1463,10 @@ static int file_refresh_exec(bContext *C, wmOperator *UNUSED(unused)) { wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); + ScrArea *sa = CTX_wm_area(C); struct FSMenu *fsmenu = ED_fsmenu_get(); - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); /* refresh system directory menu */ fsmenu_refresh_system_category(fsmenu); @@ -1476,11 +1556,11 @@ static int file_smoothscroll_invoke(bContext *C, wmOperator *UNUSED(op), const w if (sfile->smoothscroll_timer == NULL || sfile->smoothscroll_timer != event->customdata) return OPERATOR_PASS_THROUGH; - numfiles = filelist_numfiles(sfile->files); + numfiles = filelist_files_ensure(sfile->files); /* check if we are editing a name */ for (i = 0; i < numfiles; ++i) { - if (filelist_is_selected(sfile->files, i, CHECK_ALL) ) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL) ) { edit_idx = i; break; } @@ -1603,6 +1683,7 @@ int file_directory_new_exec(bContext *C, wmOperator *op) wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); + ScrArea *sa = CTX_wm_area(C); if (!sfile->params) { BKE_report(op->reports, RPT_WARNING, "No parent directory given"); @@ -1655,7 +1736,7 @@ int file_directory_new_exec(bContext *C, wmOperator *op) sfile->scroll_offset = 0; /* reload dir to make sure we're seeing what's in the directory */ - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); if (RNA_boolean_get(op->ptr, "open")) { BLI_strncpy(sfile->params->dir, path, sizeof(sfile->params->dir)); @@ -1813,12 +1894,13 @@ void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg matched_file[0] = '\0'; filepath[0] = '\0'; - BLI_filename_make_safe(sfile->params->file); - file_expand_directory(C); matches = file_select_match(sfile, sfile->params->file, matched_file); + /* *After* file_select_match! */ + BLI_filename_make_safe(sfile->params->file); + if (matches) { /* int i, numfiles = filelist_numfiles(sfile->files); */ /* XXX UNUSED */ sfile->params->file[0] = '\0'; @@ -1842,9 +1924,9 @@ void file_filename_enter_handle(bContext *C, void *UNUSED(arg_unused), void *arg WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_PARAMS, NULL); } else if (sfile->params->type == FILE_LOADLIB) { - char tdir[FILE_MAX], tgroup[FILE_MAX]; + char tdir[FILE_MAX]; BLI_add_slash(filepath); - if (BLO_is_a_library(filepath, tdir, tgroup)) { + if (BLO_library_path_explode(filepath, tdir, NULL, NULL)) { BLI_cleanup_dir(G.main->name, filepath); BLI_strncpy(sfile->params->dir, filepath, sizeof(sfile->params->dir)); sfile->params->file[0] = '\0'; @@ -1876,10 +1958,11 @@ static int file_hidedot_exec(bContext *C, wmOperator *UNUSED(unused)) { wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); + ScrArea *sa = CTX_wm_area(C); if (sfile->params) { sfile->params->flag ^= FILE_HIDE_DOT; - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); } @@ -1950,6 +2033,37 @@ void FILE_OT_bookmark_toggle(struct wmOperatorType *ot) } +/** + * Looks for a string of digits within name (using BLI_stringdec) 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; + unsigned short digits; + + pic = BLI_stringdec(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_stringenc(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); @@ -1957,7 +2071,7 @@ static int file_filenum_exec(bContext *C, wmOperator *op) int inc = RNA_int_get(op->ptr, "increment"); if (sfile->params && (inc != 0)) { - BLI_newname(sfile->params->file, inc); + filenum_newname(sfile->params->file, sizeof(sfile->params->file), inc); ED_area_tag_redraw(sa); file_draw_check(C); // WM_event_add_notifier(C, NC_WINDOW, NULL); @@ -1989,11 +2103,11 @@ static int file_rename_exec(bContext *C, wmOperator *UNUSED(op)) if (sfile->params) { int idx = sfile->params->highlight_file; - int numfiles = filelist_numfiles(sfile->files); + int numfiles = filelist_files_ensure(sfile->files); if ((0 <= idx) && (idx < numfiles)) { - struct direntry *file = filelist_file(sfile->files, idx); - filelist_select_file(sfile->files, idx, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL); - BLI_strncpy(sfile->params->renameedit, file->relname, FILE_MAXFILE); + FileDirEntry *file = filelist_file(sfile->files, idx); + filelist_entry_select_index_set(sfile->files, idx, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL); + BLI_strncpy(sfile->params->renameedit, file->relpath, FILE_MAXFILE); sfile->params->renamefile[0] = '\0'; } ED_area_tag_redraw(sa); @@ -2005,29 +2119,34 @@ static int file_rename_exec(bContext *C, wmOperator *UNUSED(op)) static int file_rename_poll(bContext *C) { - int poll = ED_operator_file_active(C); + bool poll = ED_operator_file_active(C); SpaceFile *sfile = CTX_wm_space_file(C); if (sfile && sfile->params) { int idx = sfile->params->highlight_file; + int numfiles = filelist_files_ensure(sfile->files); - if (idx >= 0) { - struct direntry *file = filelist_file(sfile->files, idx); - if (FILENAME_IS_CURRPAR(file->relname)) { - poll = 0; + if ((0 <= idx) && (idx < numfiles)) { + FileDirEntry *file = filelist_file(sfile->files, idx); + if (FILENAME_IS_CURRPAR(file->relpath)) { + poll = false; } } if (sfile->params->highlight_file < 0) { - poll = 0; + poll = false; } else { - char dir[FILE_MAX], group[FILE_MAX]; - if (filelist_islibrary(sfile->files, dir, group)) poll = 0; + char dir[FILE_MAX]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + poll = false; + } } } - else - poll = 0; + else { + poll = false; + } + return poll; } @@ -2050,14 +2169,14 @@ static int file_delete_poll(bContext *C) SpaceFile *sfile = CTX_wm_space_file(C); if (sfile && sfile->params) { - char dir[FILE_MAX], group[FILE_MAX]; - int numfiles = filelist_numfiles(sfile->files); + char dir[FILE_MAX]; + int numfiles = filelist_files_ensure(sfile->files); int i; int num_selected = 0; - if (filelist_islibrary(sfile->files, dir, group)) poll = 0; + if (filelist_islibrary(sfile->files, dir, NULL)) poll = 0; for (i = 0; i < numfiles; i++) { - if (filelist_is_selected(sfile->files, i, CHECK_FILES)) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) { num_selected++; } } @@ -2076,19 +2195,20 @@ int file_delete_exec(bContext *C, wmOperator *UNUSED(op)) char str[FILE_MAX]; wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); - struct direntry *file; - int numfiles = filelist_numfiles(sfile->files); + ScrArea *sa = CTX_wm_area(C); + FileDirEntry *file; + int numfiles = filelist_files_ensure(sfile->files); int i; for (i = 0; i < numfiles; i++) { - if (filelist_is_selected(sfile->files, i, CHECK_FILES)) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) { file = filelist_file(sfile->files, i); - BLI_make_file_string(G.main->name, str, sfile->params->dir, file->relname); + BLI_make_file_string(G.main->name, str, sfile->params->dir, file->relpath); BLI_delete(str, false, false); } } - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); WM_event_add_notifier(C, NC_SPACE | ND_SPACE_FILE_LIST, NULL); return OPERATOR_FINISHED; diff --git a/source/blender/editors/space_file/file_panels.c b/source/blender/editors/space_file/file_panels.c index 3da83aa6028..7acf2564fb2 100644 --- a/source/blender/editors/space_file/file_panels.c +++ b/source/blender/editors/space_file/file_panels.c @@ -34,7 +34,7 @@ #include "BKE_context.h" #include "BKE_screen.h" -#include "BLF_translation.h" +#include "BLT_translation.h" #include "DNA_screen_types.h" #include "DNA_space_types.h" @@ -100,7 +100,7 @@ void file_panels_register(ARegionType *art) pt = MEM_callocN(sizeof(PanelType), "spacetype file operator properties"); strcpy(pt->idname, "FILE_PT_operator"); strcpy(pt->label, N_("Operator")); - strcpy(pt->translation_context, BLF_I18NCONTEXT_DEFAULT_BPYRNA); + strcpy(pt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); pt->poll = file_panel_operator_poll; pt->draw_header = file_panel_operator_header; pt->draw = file_panel_operator; diff --git a/source/blender/editors/space_file/file_utils.c b/source/blender/editors/space_file/file_utils.c index 8a80e4a69ee..3c007f25da3 100644 --- a/source/blender/editors/space_file/file_utils.c +++ b/source/blender/editors/space_file/file_utils.c @@ -41,6 +41,7 @@ void file_tile_boundbox(const ARegion *ar, FileLayout *layout, const int file, r int xmin, ymax; ED_fileselect_layout_tilepos(layout, file, &xmin, &ymax); + ymax = (int)ar->v2d.tot.ymax - ymax; /* real, view space ymax */ BLI_rcti_init(r_bounds, xmin, xmin + layout->tile_w + layout->tile_border_x, - ar->winy - ymax - layout->tile_h - layout->tile_border_y, ar->winy - ymax); + ymax - layout->tile_h - layout->tile_border_y, ymax); } diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c index 3358812796e..56c48b7f650 100644 --- a/source/blender/editors/space_file/filelist.c +++ b/source/blender/editors/space_file/filelist.c @@ -35,6 +35,8 @@ #include <stdlib.h> #include <math.h> #include <string.h> +#include <sys/stat.h> +#include <time.h> #ifndef WIN32 # include <unistd.h> @@ -45,9 +47,16 @@ #include "MEM_guardedalloc.h" #include "BLI_blenlib.h" +#include "BLI_fileops.h" #include "BLI_fileops_types.h" #include "BLI_fnmatch.h" +#include "BLI_ghash.h" +#include "BLI_hash_md5.h" #include "BLI_linklist.h" +#include "BLI_math.h" +#include "BLI_stack.h" +#include "BLI_task.h" +#include "BLI_threads.h" #include "BLI_utildefines.h" #ifdef WIN32 @@ -60,13 +69,13 @@ #include "BKE_icons.h" #include "BKE_idcode.h" #include "BKE_main.h" -#include "BKE_report.h" #include "BLO_readfile.h" #include "DNA_space_types.h" #include "ED_datafiles.h" #include "ED_fileselect.h" +#include "ED_screen.h" #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" @@ -78,6 +87,9 @@ #include "WM_types.h" #include "UI_resources.h" +#include "UI_interface_icons.h" + +#include "atomic_ops.h" #include "filelist.h" @@ -194,49 +206,132 @@ ListBase *folderlist_duplicate(ListBase *folderlist) /* ------------------FILELIST------------------------ */ -struct FileList; +typedef struct FileListInternEntry { + struct FileListInternEntry *next, *prev; + + char uuid[16]; /* ASSET_UUID_LENGTH */ + + int typeflag; /* eFileSel_File_Types */ + int blentype; /* ID type, in case typeflag has FILE_TYPE_BLENDERLIB set. */ + + char *relpath; + char *name; /* not striclty needed, but used during sorting, avoids to have to recompute it there... */ + + BLI_stat_t st; +} FileListInternEntry; + +typedef struct FileListIntern { + ListBase entries; /* FileListInternEntry items. */ + FileListInternEntry **filtered; + + char curr_uuid[16]; /* Used to generate uuid during internal listing. */ +} FileListIntern; + +#define FILELIST_ENTRYCACHESIZE_DEFAULT 1024 /* Keep it a power of two! */ +typedef struct FileListEntryCache { + size_t size; /* The size of the cache... */ + + int flags; + + /* This one gathers all entries from both block and misc caches. Used for easy bulk-freing. */ + ListBase cached_entries; + + /* Block cache: all entries between start and end index. used for part of the list on diplay. */ + FileDirEntry **block_entries; + int block_start_index, block_end_index, block_center_index, block_cursor; + + /* Misc cache: random indices, FIFO behavior. + * Note: Not 100% sure we actually need that, time will say. */ + int misc_cursor; + int *misc_entries_indices; + GHash *misc_entries; -typedef struct FileImage { - struct FileImage *next, *prev; + /* Allows to quickly get a cached entry from its UUID. */ + GHash *uuids; + + /* Previews handling. */ + TaskScheduler *previews_scheduler; + TaskPool *previews_pool; + ThreadQueue *previews_todo; + ThreadQueue *previews_done; + double previews_timestamp; + int previews_pending; +} FileListEntryCache; + +/* FileListCache.flags */ +enum { + FLC_IS_INIT = 1 << 0, + FLC_PREVIEWS_ACTIVE = 1 << 1, +}; + +typedef struct FileListEntryPreview { char path[FILE_MAX]; unsigned int flags; int index; - short done; ImBuf *img; -} FileImage; +} FileListEntryPreview; typedef struct FileListFilter { - bool hide_dot; - bool hide_parent; unsigned int filter; + unsigned int filter_id; char filter_glob[64]; char filter_search[66]; /* + 2 for heading/trailing implicit '*' wildcards. */ + short flags; } FileListFilter; +/* FileListFilter.flags */ +enum { + FLF_HIDE_DOT = 1 << 0, + FLF_HIDE_PARENT = 1 << 1, + FLF_HIDE_LIB_DIR = 1 << 2, +}; + typedef struct FileList { - struct direntry *filelist; - int numfiles; - char dir[FILE_MAX]; + FileDirEntryArr filelist; + short prv_w; short prv_h; - bool changed; + short flags; short sort; - bool need_sorting; FileListFilter filter_data; - int *fidx; /* Also used to detect when we need to filter! */ - int numfiltered; - bool need_thumbnails; + struct FileListIntern filelist_intern; + + struct FileListEntryCache filelist_cache; + + /* We need to keep those info outside of actual filelist items, because those are no more persistent + * (only generated on demand, and freed as soon as possible). + * Persistent part (mere list of paths + stat info) is kept as small as possible, and filebrowser-agnostic. + */ + GHash *selection_state; + + short max_recursion; + short recursion_level; struct BlendHandle *libfiledata; - void (*readf)(struct FileList *); - bool (*filterf)(struct direntry *, const char *, FileListFilter *); + /* Set given path as root directory, may change given string in place to a valid value. */ + void (*checkdirf)(struct FileList *, char *); + + /* Fill filelist (to be called by read job). */ + void (*read_jobf)(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); + + /* Filter an entry of current filelist. */ + bool (*filterf)(struct FileListInternEntry *, const char *, FileListFilter *); } 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, +}; + #define SPECIAL_IMG_SIZE 48 #define SPECIAL_IMG_ROWS 4 #define SPECIAL_IMG_COLS 4 @@ -260,153 +355,188 @@ enum { static ImBuf *gSpecialFileImages[SPECIAL_IMG_MAX]; -static void filelist_from_main(struct FileList *filelist); -static void filelist_from_library(struct FileList *filelist); +static void filelist_readjob_main(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); +static void filelist_readjob_lib(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); +static void filelist_readjob_dir(struct FileList *, const char *, short *, short *, float *, ThreadMutex *); -static void filelist_read_main(struct FileList *filelist); -static void filelist_read_library(struct FileList *filelist); -static void filelist_read_dir(struct FileList *filelist); +/* helper, could probably go in BKE actually? */ +static int groupname_to_code(const char *group); +static unsigned int groupname_to_filter_id(const char *group); static void filelist_filter_clear(FileList *filelist); +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size); /* ********** Sort helpers ********** */ -static bool compare_is_directory(const struct direntry *entry) -{ - /* for library browse .blend files may be treated as directories, but - * for sorting purposes they should be considered regular files */ - if (S_ISDIR(entry->type)) - return !(entry->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)); - - return false; -} - -static int compare_direntry_generic(const struct direntry *entry1, const struct direntry *entry2) +static int compare_direntry_generic(const FileListInternEntry *entry1, const FileListInternEntry *entry2) { /* type is equal to stat.st_mode */ - if (compare_is_directory(entry1)) { - if (compare_is_directory(entry2) == 0) { - return -1; + if (entry1->typeflag & FILE_TYPE_DIR) { + if (entry2->typeflag & FILE_TYPE_DIR) { + /* If both entries are tagged as dirs, we make a 'sub filter' that shows first the real dirs, + * then libs (.blend files), then categories in libs. */ + if (entry1->typeflag & FILE_TYPE_BLENDERLIB) { + if (!(entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + return 1; + } + } + else if (entry2->typeflag & FILE_TYPE_BLENDERLIB) { + return -1; + } + else if (entry1->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + return 1; + } + } + else if (entry2->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + return -1; + } } - } - else if (compare_is_directory(entry2)) { - return 1; - } - - if (S_ISREG(entry1->type)) { - if (!S_ISREG(entry2->type)) { + else { return -1; } } - else if (S_ISREG(entry2->type)) { - return 1; + else if (entry2->typeflag & FILE_TYPE_DIR) { + return 1; } - if ((entry1->type & S_IFMT) < (entry2->type & S_IFMT)) return -1; - if ((entry1->type & S_IFMT) > (entry2->type & S_IFMT)) return 1; - + /* make sure "." and ".." are always first */ - if (FILENAME_IS_CURRENT(entry1->relname)) return -1; - if (FILENAME_IS_CURRENT(entry2->relname)) return 1; - if (FILENAME_IS_PARENT(entry1->relname)) return -1; - if (FILENAME_IS_PARENT(entry2->relname)) return 1; + if (FILENAME_IS_CURRENT(entry1->relpath)) return -1; + if (FILENAME_IS_CURRENT(entry2->relpath)) return 1; + if (FILENAME_IS_PARENT(entry1->relpath)) return -1; + if (FILENAME_IS_PARENT(entry2->relpath)) return 1; return 0; } -static int compare_name(const void *a1, const void *a2) +static int compare_name(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - return (BLI_natstrcmp(entry1->relname, entry2->relname)); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } -static int compare_date(const void *a1, const void *a2) +static int compare_date(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + int64_t time1, time2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (entry1->s.st_mtime < entry2->s.st_mtime) return 1; - if (entry1->s.st_mtime > entry2->s.st_mtime) return -1; + time1 = (int64_t)entry1->st.st_mtime; + time2 = (int64_t)entry2->st.st_mtime; + if (time1 < time2) return 1; + if (time1 > time2) return -1; - return BLI_natstrcmp(entry1->relname, entry2->relname); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } -static int compare_size(const void *a1, const void *a2) +static int compare_size(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; + uint64_t size1, size2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (entry1->s.st_size < entry2->s.st_size) return 1; - if (entry1->s.st_size > entry2->s.st_size) return -1; + size1 = entry1->st.st_size; + size2 = entry2->st.st_size; + if (size1 < size2) return 1; + if (size1 > size2) return -1; + + name1 = entry1->name; + name2 = entry2->name; - return BLI_natstrcmp(entry1->relname, entry2->relname); + return BLI_natstrcmp(name1, name2); } -static int compare_extension(const void *a1, const void *a2) +static int compare_extension(void *UNUSED(user_data), const void *a1, const void *a2) { - const struct direntry *entry1 = a1, *entry2 = a2; - const char *sufix1, *sufix2; - const char *nil = ""; + const FileListInternEntry *entry1 = a1; + const FileListInternEntry *entry2 = a2; + char *name1, *name2; int ret; if ((ret = compare_direntry_generic(entry1, entry2))) { return ret; } - if (!(sufix1 = strstr(entry1->relname, ".blend.gz"))) - sufix1 = strrchr(entry1->relname, '.'); - if (!(sufix2 = strstr(entry2->relname, ".blend.gz"))) - sufix2 = strrchr(entry2->relname, '.'); - if (!sufix1) sufix1 = nil; - if (!sufix2) sufix2 = nil; + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && !(entry2->typeflag & FILE_TYPE_BLENDERLIB)) return -1; + if (!(entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) return 1; + if ((entry1->typeflag & FILE_TYPE_BLENDERLIB) && (entry2->typeflag & FILE_TYPE_BLENDERLIB)) { + if ((entry1->typeflag & FILE_TYPE_DIR) && !(entry2->typeflag & FILE_TYPE_DIR)) return 1; + if (!(entry1->typeflag & FILE_TYPE_DIR) && (entry2->typeflag & FILE_TYPE_DIR)) return -1; + if (entry1->blentype < entry2->blentype) return -1; + if (entry1->blentype > entry2->blentype) return 1; + } + else { + const char *sufix1, *sufix2; + + if (!(sufix1 = strstr(entry1->relpath, ".blend.gz"))) + sufix1 = strrchr(entry1->relpath, '.'); + if (!(sufix2 = strstr(entry2->relpath, ".blend.gz"))) + sufix2 = strrchr(entry2->relpath, '.'); + if (!sufix1) sufix1 = ""; + if (!sufix2) sufix2 = ""; - return BLI_strcasecmp(sufix1, sufix2); -} + if ((ret = BLI_strcasecmp(sufix1, sufix2))) { + return ret; + } + } -bool filelist_need_sorting(struct FileList *filelist) -{ - return filelist->need_sorting && (filelist->sort != FILE_SORT_NONE); + name1 = entry1->name; + name2 = entry2->name; + + return BLI_natstrcmp(name1, name2); } void filelist_sort(struct FileList *filelist) { - if (filelist_need_sorting(filelist)) { - filelist->need_sorting = false; - + if ((filelist->flags & FL_NEED_SORTING) && (filelist->sort != FILE_SORT_NONE)) { switch (filelist->sort) { case FILE_SORT_ALPHA: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_name); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_name, NULL); break; case FILE_SORT_TIME: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_date); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_date, NULL); break; case FILE_SORT_SIZE: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_size); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_size, NULL); break; case FILE_SORT_EXTENSION: - qsort(filelist->filelist, filelist->numfiles, sizeof(struct direntry), compare_extension); + BLI_listbase_sort_r(&filelist->filelist_intern.entries, compare_extension, NULL); break; case FILE_SORT_NONE: /* Should never reach this point! */ default: BLI_assert(0); - return; + break; } filelist_filter_clear(filelist); + filelist->flags &= ~FL_NEED_SORTING; } } @@ -414,7 +544,7 @@ void filelist_setsorting(struct FileList *filelist, const short sort) { if (filelist->sort != sort) { filelist->sort = sort; - filelist->need_sorting = true; + filelist->flags |= FL_NEED_SORTING; } } @@ -422,9 +552,10 @@ void filelist_setsorting(struct FileList *filelist, const short sort) static bool is_hidden_file(const char *filename, FileListFilter *filter) { + char *sep = (char *)BLI_last_slash(filename); bool is_hidden = false; - if (filter->hide_dot) { + if (filter->flags & FLF_HIDE_DOT) { if (filename[0] == '.' && filename[1] != '.' && filename[1] != '\0') { is_hidden = true; /* ignore .file */ } @@ -435,7 +566,7 @@ static bool is_hidden_file(const char *filename, FileListFilter *filter) } } } - if (!is_hidden && filter->hide_parent) { + if (!is_hidden && (filter->flags & FLF_HIDE_PARENT)) { if (filename[0] == '.' && filename[1] == '.' && filename[2] == '\0') { is_hidden = true; /* ignore .. */ } @@ -443,22 +574,49 @@ static bool is_hidden_file(const char *filename, FileListFilter *filter) if (!is_hidden && ((filename[0] == '.') && (filename[1] == '\0'))) { is_hidden = true; /* ignore . */ } + /* filename might actually be a piece of path, in which case we have to check all its parts. */ + if (!is_hidden && sep) { + char tmp_filename[FILE_MAX_LIBEXTRA]; + + BLI_strncpy(tmp_filename, filename, sizeof(tmp_filename)); + sep = tmp_filename + (sep - filename); + while (sep) { + BLI_assert(sep[1] != '\0'); + if (is_hidden_file(sep + 1, filter)) { + is_hidden = true; + break; + } + *sep = '\0'; + sep = (char *)BLI_last_slash(tmp_filename); + } + } return is_hidden; } -static bool is_filtered_file(struct direntry *file, const char *UNUSED(root), FileListFilter *filter) +static bool is_filtered_file(FileListInternEntry *file, const char *UNUSED(root), FileListFilter *filter) { - bool is_filtered = !is_hidden_file(file->relname, filter); + bool is_filtered = !is_hidden_file(file->relpath, filter); - if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relname)) { - if ((file->type & S_IFDIR) && !(filter->filter & FILE_TYPE_FOLDER)) { - is_filtered = false; + if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + is_filtered = false; + } + } + else { + if (!(filter->filter & FILE_TYPE_FOLDER)) { + is_filtered = false; + } + } } - if (!(file->type & S_IFDIR) && !(file->flags & filter->filter)) { - is_filtered = false; + else { + if (!(file->typeflag & filter->filter)) { + is_filtered = false; + } } if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relname, FNM_CASEFOLD) != 0) { + if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { is_filtered = false; } } @@ -467,16 +625,41 @@ static bool is_filtered_file(struct direntry *file, const char *UNUSED(root), Fi return is_filtered; } -static bool is_filtered_lib(struct direntry *file, const char *root, FileListFilter *filter) +static bool is_filtered_lib(FileListInternEntry *file, const char *root, FileListFilter *filter) { - bool is_filtered = !is_hidden_file(file->relname, filter); - char dir[FILE_MAXDIR], group[BLO_GROUP_MAX]; + bool is_filtered; + char path[FILE_MAX_LIBEXTRA], dir[FILE_MAXDIR], *group, *name; - if (BLO_is_a_library(root, dir, group)) { - is_filtered = !is_hidden_file(file->relname, filter); - if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relname)) { + BLI_join_dirfile(path, sizeof(path), root, file->relpath); + + if (BLO_library_path_explode(path, dir, &group, &name)) { + is_filtered = !is_hidden_file(file->relpath, filter); + if (is_filtered && filter->filter && !FILENAME_IS_CURRPAR(file->relpath)) { + if (file->typeflag & FILE_TYPE_DIR) { + if (file->typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { + if (!(filter->filter & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP))) { + is_filtered = false; + } + } + else { + if (!(filter->filter & FILE_TYPE_FOLDER)) { + is_filtered = false; + } + } + } + if (is_filtered && group) { + if (!name && (filter->flags & FLF_HIDE_LIB_DIR)) { + is_filtered = false; + } + else { + unsigned int filter_id = groupname_to_filter_id(group); + if (!(filter_id & filter->filter_id)) { + is_filtered = false; + } + } + } if (is_filtered && (filter->filter_search[0] != '\0')) { - if (fnmatch(filter->filter_search, file->relname, FNM_CASEFOLD) != 0) { + if (fnmatch(filter->filter_search, file->relpath, FNM_CASEFOLD) != 0) { is_filtered = false; } } @@ -489,70 +672,97 @@ static bool is_filtered_lib(struct direntry *file, const char *root, FileListFil return is_filtered; } -static bool is_filtered_main(struct direntry *file, const char *UNUSED(dir), FileListFilter *filter) +static bool is_filtered_main(FileListInternEntry *file, const char *UNUSED(dir), FileListFilter *filter) { - return !is_hidden_file(file->relname, filter); + return !is_hidden_file(file->relpath, filter); } static void filelist_filter_clear(FileList *filelist) { - MEM_SAFE_FREE(filelist->fidx); - filelist->numfiltered = 0; + filelist->flags |= FL_NEED_FILTERING; } void filelist_filter(FileList *filelist) { int num_filtered = 0; - int *fidx_tmp; - int i; + const int num_files = filelist->filelist.nbr_entries; + FileListInternEntry **filtered_tmp, *file; - if (!filelist->filelist) { + if (filelist->filelist.nbr_entries == 0) { return; } - if (filelist->fidx) { + if (!(filelist->flags & FL_NEED_FILTERING)) { /* Assume it has already been filtered, nothing else to do! */ return; } - fidx_tmp = MEM_mallocN(sizeof(*fidx_tmp) * (size_t)filelist->numfiles, __func__); + filelist->filter_data.flags &= ~FLF_HIDE_LIB_DIR; + if (filelist->max_recursion) { + /* Never show lib ID 'categories' directories when we are in 'flat' mode, unless + * root path is a blend file. */ + char dir[FILE_MAXDIR]; + if (!filelist_islibrary(filelist, dir, NULL)) { + filelist->filter_data.flags |= FLF_HIDE_LIB_DIR; + } + } - /* Filter remap & count how many files are left after filter in a single loop. */ - for (i = 0; i < filelist->numfiles; ++i) { - struct direntry *file = &filelist->filelist[i]; + filtered_tmp = MEM_mallocN(sizeof(*filtered_tmp) * (size_t)num_files, __func__); - if (filelist->filterf(file, filelist->dir, &filelist->filter_data)) { - fidx_tmp[num_filtered++] = i; + /* Filter remap & count how many files are left after filter in a single loop. */ + for (file = filelist->filelist_intern.entries.first; file; file = file->next) { + if (filelist->filterf(file, filelist->filelist.root, &filelist->filter_data)) { + filtered_tmp[num_filtered++] = file; } } - /* Note: maybe we could even accept filelist->fidx to be filelist->numfiles -len allocated? */ - filelist->fidx = MEM_mallocN(sizeof(*filelist->fidx) * (size_t)num_filtered, __func__); - memcpy(filelist->fidx, fidx_tmp, sizeof(*filelist->fidx) * (size_t)num_filtered); - filelist->numfiltered = num_filtered; + if (filelist->filelist_intern.filtered) { + MEM_freeN(filelist->filelist_intern.filtered); + } + filelist->filelist_intern.filtered = MEM_mallocN(sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered, + __func__); + memcpy(filelist->filelist_intern.filtered, filtered_tmp, + sizeof(*filelist->filelist_intern.filtered) * (size_t)num_filtered); + filelist->filelist.nbr_entries_filtered = num_filtered; +// printf("Filetered: %d over %d entries\n", num_filtered, filelist->filelist.nbr_entries); + + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + filelist->flags &= ~FL_NEED_FILTERING; - MEM_freeN(fidx_tmp); + MEM_freeN(filtered_tmp); } void filelist_setfilter_options(FileList *filelist, const bool hide_dot, const bool hide_parent, - const unsigned int filter, + const unsigned int filter, const unsigned int filter_id, const char *filter_glob, const char *filter_search) { - if ((filelist->filter_data.hide_dot != hide_dot) || - (filelist->filter_data.hide_parent != hide_parent) || - (filelist->filter_data.filter != filter) || - !STREQ(filelist->filter_data.filter_glob, filter_glob) || - (BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) - { - filelist->filter_data.hide_dot = hide_dot; - filelist->filter_data.hide_parent = hide_parent; + bool update = false; + if (((filelist->filter_data.flags & FLF_HIDE_DOT) != 0) != (hide_dot != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_DOT; + update = true; + } + if (((filelist->filter_data.flags & FLF_HIDE_PARENT) != 0) != (hide_parent != 0)) { + filelist->filter_data.flags ^= FLF_HIDE_PARENT; + update = true; + } + if ((filelist->filter_data.filter != filter) || (filelist->filter_data.filter_id != filter_id)) { filelist->filter_data.filter = filter; + filelist->filter_data.filter_id = filter_id; + update = true; + } + if (!STREQ(filelist->filter_data.filter_glob, filter_glob)) { BLI_strncpy(filelist->filter_data.filter_glob, filter_glob, sizeof(filelist->filter_data.filter_glob)); + update = true; + } + if ((BLI_strcmp_ignore_pad(filelist->filter_data.filter_search, filter_search, '*') != 0)) { BLI_strncpy_ensure_pad(filelist->filter_data.filter_search, filter_search, '*', sizeof(filelist->filter_data.filter_search)); + update = true; + } - /* And now, free filtered data so that we now we have to filter again. */ + if (update) { + /* And now, free filtered data so that we know we have to filter again. */ filelist_filter_clear(filelist); } } @@ -607,101 +817,512 @@ void filelist_imgsize(struct FileList *filelist, short w, short h) filelist->prv_h = h; } -ImBuf *filelist_getimage(struct FileList *filelist, const int index) +static FileDirEntry *filelist_geticon_get_file(struct FileList *filelist, const int index) { - ImBuf *ibuf = NULL; - int fidx = 0; - BLI_assert(G.background == false); - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; - } - fidx = filelist->fidx[index]; - ibuf = filelist->filelist[fidx].image; + return filelist_file(filelist, index); +} - return ibuf; +ImBuf *filelist_getimage(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return file->image; } -ImBuf *filelist_geticon(struct FileList *filelist, const int index) +static ImBuf *filelist_geticon_image_ex(const unsigned int typeflag, const char *relpath) { ImBuf *ibuf = NULL; - struct direntry *file = NULL; - int fidx = 0; - BLI_assert(G.background == false); - - if ((index < 0) || (index >= filelist->numfiltered)) { - return NULL; - } - fidx = filelist->fidx[index]; - file = &filelist->filelist[fidx]; - if (file->type & S_IFDIR) { - if (FILENAME_IS_PARENT(filelist->filelist[fidx].relname)) { + if (typeflag & FILE_TYPE_DIR) { + if (FILENAME_IS_PARENT(relpath)) { ibuf = gSpecialFileImages[SPECIAL_IMG_PARENT]; } - else if (FILENAME_IS_CURRENT(filelist->filelist[fidx].relname)) { + else if (FILENAME_IS_CURRENT(relpath)) { ibuf = gSpecialFileImages[SPECIAL_IMG_REFRESH]; } else { ibuf = gSpecialFileImages[SPECIAL_IMG_FOLDER]; } } - else { - ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; - } - - if (file->flags & FILE_TYPE_BLENDER) { + else if (typeflag & FILE_TYPE_BLENDER) { ibuf = gSpecialFileImages[SPECIAL_IMG_BLENDFILE]; } - else if (file->flags & FILE_TYPE_MOVIE) { + else if (typeflag & FILE_TYPE_BLENDERLIB) { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } + else if (typeflag & (FILE_TYPE_MOVIE)) { ibuf = gSpecialFileImages[SPECIAL_IMG_MOVIEFILE]; } - else if (file->flags & FILE_TYPE_SOUND) { + else if (typeflag & FILE_TYPE_SOUND) { ibuf = gSpecialFileImages[SPECIAL_IMG_SOUNDFILE]; } - else if (file->flags & FILE_TYPE_PYSCRIPT) { + else if (typeflag & FILE_TYPE_PYSCRIPT) { ibuf = gSpecialFileImages[SPECIAL_IMG_PYTHONFILE]; } - else if (file->flags & FILE_TYPE_FTFONT) { + else if (typeflag & FILE_TYPE_FTFONT) { ibuf = gSpecialFileImages[SPECIAL_IMG_FONTFILE]; } - else if (file->flags & FILE_TYPE_TEXT) { + else if (typeflag & FILE_TYPE_TEXT) { ibuf = gSpecialFileImages[SPECIAL_IMG_TEXTFILE]; } - else if (file->flags & FILE_TYPE_IMAGE) { + else if (typeflag & FILE_TYPE_IMAGE) { ibuf = gSpecialFileImages[SPECIAL_IMG_LOADING]; } - else if (file->flags & FILE_TYPE_BLENDER_BACKUP) { + else if (typeflag & FILE_TYPE_BLENDER_BACKUP) { ibuf = gSpecialFileImages[SPECIAL_IMG_BACKUP]; } + else { + ibuf = gSpecialFileImages[SPECIAL_IMG_UNKNOWNFILE]; + } return ibuf; } +ImBuf *filelist_geticon_image(struct FileList *filelist, const int index) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return filelist_geticon_image_ex(file->typeflag, file->relpath); +} + +static int filelist_geticon_ex( + const int typeflag, const int blentype, const char *relpath, const bool is_main, const bool ignore_libdir) +{ + if ((typeflag & FILE_TYPE_DIR) && !(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER)))) { + if (FILENAME_IS_PARENT(relpath)) { + return is_main ? ICON_FILE_PARENT : ICON_NONE; + } + else if (typeflag & FILE_TYPE_APPLICATIONBUNDLE) { + return ICON_UGLYPACKAGE; + } + else if (typeflag & FILE_TYPE_BLENDER) { + return ICON_FILE_BLEND; + } + else if (is_main) { + /* Do not return icon for folders if icons are not 'main' draw type (e.g. when used over previews). */ + return ICON_FILE_FOLDER; + } + } + + if (typeflag & FILE_TYPE_BLENDER) + return ICON_FILE_BLEND; + else if (typeflag & FILE_TYPE_BLENDER_BACKUP) + return ICON_FILE_BACKUP; + else if (typeflag & FILE_TYPE_IMAGE) + return ICON_FILE_IMAGE; + else if (typeflag & FILE_TYPE_MOVIE) + return ICON_FILE_MOVIE; + else if (typeflag & FILE_TYPE_PYSCRIPT) + return ICON_FILE_SCRIPT; + else if (typeflag & FILE_TYPE_SOUND) + return ICON_FILE_SOUND; + else if (typeflag & FILE_TYPE_FTFONT) + return ICON_FILE_FONT; + else if (typeflag & FILE_TYPE_BTX) + return ICON_FILE_BLANK; + else if (typeflag & FILE_TYPE_COLLADA) + return ICON_FILE_BLANK; + else if (typeflag & FILE_TYPE_TEXT) + return ICON_FILE_TEXT; + else if (typeflag & FILE_TYPE_BLENDERLIB) { + const int ret = UI_idcode_icon_get(blentype); + if (ret != ICON_NONE) { + return ret; + } + } + return is_main ? ICON_FILE_BLANK : ICON_NONE; +} + +int filelist_geticon(struct FileList *filelist, const int index, const bool is_main) +{ + FileDirEntry *file = filelist_geticon_get_file(filelist, index); + + return filelist_geticon_ex(file->typeflag, file->blentype, file->relpath, is_main, false); +} + /* ********** Main ********** */ +static void filelist_checkdir_dir(struct FileList *UNUSED(filelist), char *r_dir) +{ + BLI_make_exist(r_dir); +} + +static void filelist_checkdir_lib(struct FileList *UNUSED(filelist), char *r_dir) +{ + char dir[FILE_MAXDIR]; + if (!BLO_library_path_explode(r_dir, dir, NULL, NULL)) { + /* if not a valid library, we need it to be a valid directory! */ + BLI_make_exist(r_dir); + } +} + +static void filelist_checkdir_main(struct FileList *filelist, char *r_dir) +{ + /* TODO */ + filelist_checkdir_lib(filelist, r_dir); +} + +static void filelist_entry_clear(FileDirEntry *entry) +{ + if (entry->name) { + MEM_freeN(entry->name); + } + if (entry->description) { + MEM_freeN(entry->description); + } + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->image) { + IMB_freeImBuf(entry->image); + } + /* For now, consider FileDirEntryRevision::poin as not owned here, so no need to do anything about it */ + + if (!BLI_listbase_is_empty(&entry->variants)) { + FileDirEntryVariant *var; + + for (var = entry->variants.first; var; var = var->next) { + if (var->name) { + MEM_freeN(var->name); + } + if (var->description) { + MEM_freeN(var->description); + } + + if (!BLI_listbase_is_empty(&var->revisions)) { + FileDirEntryRevision *rev; + + for (rev = var->revisions.first; rev; rev = rev->next) { + if (rev->comment) { + MEM_freeN(rev->comment); + } + } + + BLI_freelistN(&var->revisions); + } + } + + /* TODO: tags! */ + + BLI_freelistN(&entry->variants); + } + else if (entry->entry) { + MEM_freeN(entry->entry); + } +} + +static void filelist_entry_free(FileDirEntry *entry) +{ + filelist_entry_clear(entry); + MEM_freeN(entry); +} + +static void filelist_direntryarr_free(FileDirEntryArr *array) +{ +#if 0 + FileDirEntry *entry, *entry_next; + + for (entry = array->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&array->entries); +#else + BLI_assert(BLI_listbase_is_empty(&array->entries)); +#endif + array->nbr_entries = 0; + array->nbr_entries_filtered = -1; + array->entry_idx_start = -1; + array->entry_idx_end = -1; +} + +static void filelist_intern_entry_free(FileListInternEntry *entry) +{ + if (entry->relpath) { + MEM_freeN(entry->relpath); + } + if (entry->name) { + MEM_freeN(entry->name); + } + MEM_freeN(entry); +} + +static void filelist_intern_free(FileListIntern *filelist_intern) +{ + FileListInternEntry *entry, *entry_next; + + for (entry = filelist_intern->entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_intern_entry_free(entry); + } + BLI_listbase_clear(&filelist_intern->entries); + + MEM_SAFE_FREE(filelist_intern->filtered); +} + +static void filelist_cache_previewf(TaskPool *pool, void *taskdata, int UNUSED(threadid)) +{ + FileListEntryCache *cache = taskdata; + FileListEntryPreview *preview; + +// printf("%s: Start (%d)...\n", __func__, threadid); + + /* Note we wait on queue here. */ + while (!BLI_task_pool_canceled(pool) && (preview = BLI_thread_queue_pop(cache->previews_todo))) { + ThumbSource source = 0; + +// printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + BLI_assert(preview->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)); + if (preview->flags & FILE_TYPE_IMAGE) { + source = THB_SOURCE_IMAGE; + } + else if (preview->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB)) { + source = THB_SOURCE_BLEND; + } + else if (preview->flags & FILE_TYPE_MOVIE) { + source = THB_SOURCE_MOVIE; + } + else if (preview->flags & FILE_TYPE_FTFONT) { + source = THB_SOURCE_FONT; + } + + IMB_thumb_path_lock(preview->path); + preview->img = IMB_thumb_manage(preview->path, THB_LARGE, source); + IMB_thumb_path_unlock(preview->path); + + BLI_thread_queue_push(cache->previews_done, preview); + } + +// printf("%s: End (%d)...\n", __func__, threadid); +} + +static void filelist_cache_preview_ensure_running(FileListEntryCache *cache) +{ + if (!cache->previews_pool) { + TaskScheduler *scheduler; + TaskPool *pool; + int num_tasks = max_ii(1, (BLI_system_thread_count() / 2) + 1); + + scheduler = cache->previews_scheduler = BLI_task_scheduler_create(num_tasks + 1); + pool = cache->previews_pool = BLI_task_pool_create(scheduler, NULL); + cache->previews_todo = BLI_thread_queue_init(); + cache->previews_done = BLI_thread_queue_init(); + + while (num_tasks--) { + BLI_task_pool_push(pool, filelist_cache_previewf, cache, false, TASK_PRIORITY_HIGH); + } + IMB_thumb_locks_acquire(); + } + cache->previews_timestamp = 0.0; +} + +static void filelist_cache_previews_clear(FileListEntryCache *cache) +{ + FileListEntryPreview *preview; + + if (cache->previews_pool) { + while ((preview = BLI_thread_queue_pop_timeout(cache->previews_todo, 0))) { +// printf("%s: TODO %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + MEM_freeN(preview); + cache->previews_pending--; + } + while ((preview = BLI_thread_queue_pop_timeout(cache->previews_done, 0))) { +// printf("%s: DONE %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + if (preview->img) { + IMB_freeImBuf(preview->img); + } + MEM_freeN(preview); + cache->previews_pending--; + } + } +// printf("%s: remaining pending previews: %d\n", __func__, cache->previews_pending); +} + +static void filelist_cache_previews_free(FileListEntryCache *cache, const bool set_inactive) +{ + if (cache->previews_pool) { + BLI_thread_queue_nowait(cache->previews_todo); + BLI_thread_queue_nowait(cache->previews_done); + BLI_task_pool_cancel(cache->previews_pool); + + filelist_cache_previews_clear(cache); + + BLI_thread_queue_free(cache->previews_done); + BLI_thread_queue_free(cache->previews_todo); + BLI_task_pool_free(cache->previews_pool); + BLI_task_scheduler_free(cache->previews_scheduler); + cache->previews_scheduler = NULL; + cache->previews_pool = NULL; + cache->previews_todo = NULL; + cache->previews_done = NULL; + + IMB_thumb_locks_release(); + } + if (set_inactive) { + cache->flags &= ~FLC_PREVIEWS_ACTIVE; + } + BLI_assert(cache->previews_pending == 0); + cache->previews_timestamp = 0.0; +} + +static void filelist_cache_previews_push(FileList *filelist, FileDirEntry *entry, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + BLI_assert(cache->flags & FLC_PREVIEWS_ACTIVE); + + if (!entry->image && + !(entry->flags & FILE_ENTRY_INVALID_PREVIEW) && + (entry->typeflag & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | + FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP | FILE_TYPE_BLENDERLIB))) + { + FileListEntryPreview *preview = MEM_mallocN(sizeof(*preview), __func__); + BLI_join_dirfile(preview->path, sizeof(preview->path), filelist->filelist.root, entry->relpath); + preview->index = index; + preview->flags = entry->typeflag; + preview->img = NULL; +// printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + filelist_cache_preview_ensure_running(cache); + BLI_thread_queue_push(cache->previews_todo, preview); + cache->previews_pending++; + } +} + +static void filelist_cache_init(FileListEntryCache *cache, size_t cache_size) +{ + BLI_listbase_clear(&cache->cached_entries); + + cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0; + cache->block_entries = MEM_mallocN(sizeof(*cache->block_entries) * cache_size, __func__); + + cache->misc_entries = BLI_ghash_ptr_new_ex(__func__, cache_size); + cache->misc_entries_indices = MEM_mallocN(sizeof(*cache->misc_entries_indices) * cache_size, __func__); + copy_vn_i(cache->misc_entries_indices, cache_size, -1); + cache->misc_cursor = 0; + + /* XXX This assumes uint is 32 bits and uuid is 128 bits (char[16]), be careful! */ + cache->uuids = BLI_ghash_new_ex( + BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__, cache_size * 2); + + cache->size = cache_size; + cache->flags = FLC_IS_INIT; +} + +static void filelist_cache_free(FileListEntryCache *cache) +{ + FileDirEntry *entry, *entry_next; + + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_free(cache, true); + + MEM_freeN(cache->block_entries); + + BLI_ghash_free(cache->misc_entries, NULL, NULL); + MEM_freeN(cache->misc_entries_indices); + + BLI_ghash_free(cache->uuids, NULL, NULL); + + for (entry = cache->cached_entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} + +static void filelist_cache_clear(FileListEntryCache *cache, size_t new_size) +{ + FileDirEntry *entry, *entry_next; + + if (!(cache->flags & FLC_IS_INIT)) { + return; + } + + filelist_cache_previews_clear(cache); + + cache->block_cursor = cache->block_start_index = cache->block_center_index = cache->block_end_index = 0; + if (new_size != cache->size) { + cache->block_entries = MEM_reallocN(cache->block_entries, sizeof(*cache->block_entries) * new_size); + } + + BLI_ghash_clear_ex(cache->misc_entries, NULL, NULL, new_size); + if (new_size != cache->size) { + cache->misc_entries_indices = MEM_reallocN(cache->misc_entries_indices, + sizeof(*cache->misc_entries_indices) * new_size); + } + copy_vn_i(cache->misc_entries_indices, new_size, -1); + + BLI_ghash_clear_ex(cache->uuids, NULL, NULL, new_size * 2); + + cache->size = new_size; + + for (entry = cache->cached_entries.first; entry; entry = entry_next) { + entry_next = entry->next; + filelist_entry_free(entry); + } + BLI_listbase_clear(&cache->cached_entries); +} + FileList *filelist_new(short type) { FileList *p = MEM_callocN(sizeof(*p), __func__); + filelist_cache_init(&p->filelist_cache, FILELIST_ENTRYCACHESIZE_DEFAULT); + + p->selection_state = BLI_ghash_new(BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__); + switch (type) { case FILE_MAIN: - p->readf = filelist_read_main; + p->checkdirf = filelist_checkdir_main; + p->read_jobf = filelist_readjob_main; p->filterf = is_filtered_main; break; case FILE_LOADLIB: - p->readf = filelist_read_library; + p->checkdirf = filelist_checkdir_lib; + p->read_jobf = filelist_readjob_lib; p->filterf = is_filtered_lib; break; default: - p->readf = filelist_read_dir; + p->checkdirf = filelist_checkdir_dir; + p->read_jobf = filelist_readjob_dir; p->filterf = is_filtered_file; break; } return p; } +void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection) +{ + if (!filelist) { + return; + } + + filelist_filter_clear(filelist); + + if (do_cache) { + filelist_cache_clear(&filelist->filelist_cache, filelist->filelist_cache.size); + } + + filelist_intern_free(&filelist->filelist_intern); + + filelist_direntryarr_free(&filelist->filelist); + + if (do_selection && filelist->selection_state) { + BLI_ghash_clear(filelist->selection_state, MEM_freeN, NULL); + } +} + +void filelist_clear(struct FileList *filelist) +{ + filelist_clear_ex(filelist, true, true); +} + void filelist_free(struct FileList *filelist) { if (!filelist) { @@ -709,16 +1330,18 @@ void filelist_free(struct FileList *filelist) return; } - MEM_SAFE_FREE(filelist->fidx); - filelist->numfiltered = 0; + filelist_clear_ex(filelist, false, false); /* No need to clear cache & selection_state, we free them anyway. */ + filelist_cache_free(&filelist->filelist_cache); + + if (filelist->selection_state) { + BLI_ghash_free(filelist->selection_state, MEM_freeN, NULL); + filelist->selection_state = NULL; + } + memset(&filelist->filter_data, 0, sizeof(filelist->filter_data)); - filelist->need_sorting = false; + filelist->flags &= ~(FL_NEED_SORTING | FL_NEED_FILTERING); filelist->sort = FILE_SORT_NONE; - - BLI_filelist_free(filelist->filelist, filelist->numfiles, NULL); - filelist->numfiles = 0; - filelist->filelist = NULL; } void filelist_freelib(struct FileList *filelist) @@ -733,49 +1356,191 @@ BlendHandle *filelist_lib(struct FileList *filelist) return filelist->libfiledata; } -int filelist_numfiles(struct FileList *filelist) +static const char *fileentry_uiname(const char *root, const char *relpath, const int typeflag, char *buff) { - return filelist->numfiltered; + char *name = NULL; + + if (typeflag & FILE_TYPE_BLENDERLIB) { + char abspath[FILE_MAX_LIBEXTRA]; + char *group; + + BLI_join_dirfile(abspath, sizeof(abspath), root, relpath); + BLO_library_path_explode(abspath, buff, &group, &name); + if (!name) { + name = group; + } + } + /* Depending on platforms, 'my_file.blend/..' might be viewed as dir or not... */ + if (!name) { + if (typeflag & FILE_TYPE_DIR) { + name = (char *)relpath; + } + else { + name = (char *)BLI_path_basename(relpath); + } + } + BLI_assert(name); + + return name; } const char *filelist_dir(struct FileList *filelist) { - return filelist->dir; + return filelist->filelist.root; } -void filelist_setdir(struct FileList *filelist, const char *dir) +/** + * May modify in place given r_dir, which is expected to be FILE_MAX_LIBEXTRA length. + */ +void filelist_setdir(struct FileList *filelist, char *r_dir) { - BLI_strncpy(filelist->dir, dir, sizeof(filelist->dir)); + BLI_assert(strlen(r_dir) < FILE_MAX_LIBEXTRA); + + BLI_cleanup_dir(G.main->name, r_dir); + filelist->checkdirf(filelist, r_dir); + + if (!STREQ(filelist->filelist.root, r_dir)) { + BLI_strncpy(filelist->filelist.root, r_dir, sizeof(filelist->filelist.root)); + filelist->flags |= FL_FORCE_RESET; + } } -short filelist_changed(struct FileList *filelist) +void filelist_setrecursion(struct FileList *filelist, const int recursion_level) { - return filelist->changed; + if (filelist->max_recursion != recursion_level) { + filelist->max_recursion = recursion_level; + filelist->flags |= FL_FORCE_RESET; + } } -struct direntry *filelist_file(struct FileList *filelist, int index) +bool filelist_force_reset(struct FileList *filelist) { - int fidx = 0; - - if ((index < 0) || (index >= filelist->numfiltered)) { + return (filelist->flags & FL_FORCE_RESET) != 0; +} + +bool filelist_is_ready(struct FileList *filelist) +{ + return (filelist->flags & FL_IS_READY) != 0; +} + +bool filelist_pending(struct FileList *filelist) +{ + return (filelist->flags & FL_IS_PENDING) != 0; +} + +/** + * Limited version of full update done by space_file's file_refresh(), to be used by operators and such. + * Ensures given filelist is ready to be used (i.e. it is filtered and sorted), unless it is tagged for a full refresh. + */ +int filelist_files_ensure(FileList *filelist) +{ + if (!filelist_force_reset(filelist) || !filelist_empty(filelist)) { + filelist_sort(filelist); + filelist_filter(filelist); + } + + return filelist->filelist.nbr_entries_filtered;; +} + +static FileDirEntry *filelist_file_create_entry(FileList *filelist, const int index) +{ + FileListInternEntry *entry = filelist->filelist_intern.filtered[index]; + FileListEntryCache *cache = &filelist->filelist_cache; + FileDirEntry *ret; + FileDirEntryRevision *rev; + + ret = MEM_callocN(sizeof(*ret), __func__); + rev = MEM_callocN(sizeof(*rev), __func__); + + rev->size = (uint64_t)entry->st.st_size; + + rev->time = (int64_t)entry->st.st_mtime; + + ret->entry = rev; + ret->relpath = BLI_strdup(entry->relpath); + ret->name = BLI_strdup(entry->name); + ret->description = BLI_strdupcat(filelist->filelist.root, entry->relpath); + memcpy(ret->uuid, entry->uuid, sizeof(ret->uuid)); + ret->blentype = entry->blentype; + ret->typeflag = entry->typeflag; + + BLI_addtail(&cache->cached_entries, ret); + return ret; +} + +static void filelist_file_release_entry(FileList *filelist, FileDirEntry *entry) +{ + BLI_remlink(&filelist->filelist_cache.cached_entries, entry); + filelist_entry_free(entry); +} + +static FileDirEntry *filelist_file_ex(struct FileList *filelist, const int index, const bool use_request) +{ + FileDirEntry *ret = NULL, *old; + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + int old_index; + + if ((index < 0) || (index >= filelist->filelist.nbr_entries_filtered)) { + return ret; + } + + if (index >= cache->block_start_index && index < cache->block_end_index) { + const int idx = (index - cache->block_start_index + cache->block_cursor) % cache_size; + return cache->block_entries[idx]; + } + + if ((ret = BLI_ghash_lookup(cache->misc_entries, SET_INT_IN_POINTER(index)))) { + return ret; + } + + if (!use_request) { return NULL; } - fidx = filelist->fidx[index]; - return &filelist->filelist[fidx]; +// printf("requesting file %d (not yet cached)\n", index); + + /* Else, we have to add new entry to 'misc' cache - and possibly make room for it first! */ + ret = filelist_file_create_entry(filelist, index); + old_index = cache->misc_entries_indices[cache->misc_cursor]; + if ((old = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(old_index), NULL))) { + BLI_ghash_remove(cache->uuids, old->uuid, NULL, NULL); + filelist_file_release_entry(filelist, old); + } + BLI_ghash_insert(cache->misc_entries, SET_INT_IN_POINTER(index), ret); + BLI_ghash_insert(cache->uuids, ret->uuid, ret); + + cache->misc_entries_indices[cache->misc_cursor] = index; + cache->misc_cursor = (cache->misc_cursor + 1) % cache_size; + +#if 0 /* Actually no, only block cached entries should have preview imho. */ + if (cache->previews_pool) { + filelist_cache_previews_push(filelist, ret, index); + } +#endif + + return ret; +} + +FileDirEntry *filelist_file(struct FileList *filelist, int index) +{ + return filelist_file_ex(filelist, index, true); } -int filelist_find(struct FileList *filelist, const char *filename) +int filelist_file_findpath(struct FileList *filelist, const char *filename) { int fidx = -1; - if (!filelist->fidx) + if (filelist->filelist.nbr_entries_filtered < 0) { return fidx; + } - for (fidx = 0; fidx < filelist->numfiltered; fidx++) { - int index = filelist->fidx[fidx]; + /* XXX TODO Cache could probably use a ghash on paths too? Not really urgent though. + * This is only used to find again renamed entry, annoying but looks hairy to get rid of it currently. */ - if (STREQ(filelist->filelist[index].relname, filename)) { + for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (STREQ(entry->relpath, filename)) { return fidx; } } @@ -783,6 +1548,385 @@ int filelist_find(struct FileList *filelist, const char *filename) return -1; } +FileDirEntry *filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]) +{ + if (filelist->filelist.nbr_entries_filtered < 0) { + return NULL; + } + + if (filelist->filelist_cache.uuids) { + FileDirEntry *entry = BLI_ghash_lookup(filelist->filelist_cache.uuids, uuid); + if (entry) { + return entry; + } + } + + { + int fidx; + + for (fidx = 0; fidx < filelist->filelist.nbr_entries_filtered; fidx++) { + FileListInternEntry *entry = filelist->filelist_intern.filtered[fidx]; + if (memcmp(entry->uuid, uuid, sizeof(entry->uuid)) == 0) { + return filelist_file(filelist, fidx); + } + } + } + + return NULL; +} + +void filelist_file_cache_slidingwindow_set(FileList *filelist, size_t window_size) +{ + /* Always keep it power of 2, in [256, 8192] range for now, cache being app. twice bigger than requested window. */ + size_t size = 256; + window_size *= 2; + + while (size < window_size && size < 8192) { + size *= 2; + } + + if (size != filelist->filelist_cache.size) { + filelist_cache_clear(&filelist->filelist_cache, size); + } +} + +/* Helpers, low-level, they assume cursor + size <= cache_size */ +static bool filelist_file_cache_block_create(FileList *filelist, const int start_index, const int size, int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i, idx; + + for (i = 0, idx = start_index; i < size; i++, idx++, cursor++) { + FileDirEntry *entry; + + /* That entry might have already been requested and stored in misc cache... */ + if ((entry = BLI_ghash_popkey(cache->misc_entries, SET_INT_IN_POINTER(idx), NULL)) == NULL) { + entry = filelist_file_create_entry(filelist, idx); + BLI_ghash_insert(cache->uuids, entry->uuid, entry); + } + cache->block_entries[cursor] = entry; + } + return true; + } + + return false; +} + +static void filelist_file_cache_block_release(struct FileList *filelist, const int size, int cursor) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + { + int i; + + for (i = 0; i < size; i++, cursor++) { + FileDirEntry *entry = cache->block_entries[cursor]; +// printf("%s: release cacheidx %d (%%p %%s)\n", __func__, cursor/*, cache->block_entries[cursor], cache->block_entries[cursor]->relpath*/); + BLI_ghash_remove(cache->uuids, entry->uuid, NULL, NULL); + filelist_file_release_entry(filelist, entry); +#ifndef NDEBUG + cache->block_entries[cursor] = NULL; +#endif + } + } +} + +/* Load in cache all entries "around" given index (as much as block cache may hold). */ +bool filelist_file_cache_block(struct FileList *filelist, const int index) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + const size_t cache_size = cache->size; + + const int nbr_entries = filelist->filelist.nbr_entries_filtered; + int start_index = max_ii(0, index - (cache_size / 2)); + int end_index = min_ii(nbr_entries, index + (cache_size / 2)); + int i; + + if ((index < 0) || (index >= nbr_entries)) { +// printf("Wrong index %d ([%d:%d])", index, 0, nbr_entries); + return false; + } + + /* Maximize cached range! */ + if ((end_index - start_index) < cache_size) { + if (start_index == 0) { + end_index = min_ii(nbr_entries, start_index + cache_size); + } + else if (end_index == nbr_entries) { + start_index = max_ii(0, end_index - cache_size); + } + } + + BLI_assert((end_index - start_index) <= cache_size) ; + +// printf("%s: [%d:%d] around index %d (current cache: [%d:%d])\n", __func__, +// start_index, end_index, index, cache->block_start_index, cache->block_end_index); + + /* If we have something to (re)cache... */ + if ((start_index != cache->block_start_index) || (end_index != cache->block_end_index)) { + if ((start_index >= cache->block_end_index) || (end_index <= cache->block_start_index)) { + int size1 = cache->block_end_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + +// printf("Full Recaching!\n"); + + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_clear(cache); + } + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_start_index = cache->block_end_index = cache->block_cursor = 0; + + /* New cached block does not overlap existing one, simple. */ + if (!filelist_file_cache_block_create(filelist, start_index, end_index - start_index, 0)) { + return false; + } + + cache->block_start_index = start_index; + cache->block_end_index = end_index; + } + else { +// printf("Partial Recaching!\n"); + + /* At this point, we know we keep part of currently cached entries, so update previews if needed, + * and remove everything from working queue - we'll add all newly needed entries at the end. */ + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + +// printf("\tpreview cleaned up...\n"); + + if (start_index > cache->block_start_index) { + int size1 = start_index - cache->block_start_index; + int size2 = 0; + int idx1 = cache->block_cursor, idx2 = 0; + +// printf("\tcache releasing: [%d:%d] (%d, %d)\n", cache->block_start_index, cache->block_start_index + size1, cache->block_cursor, size1); + + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_cursor = (idx1 + size1 + size2) % cache_size; + cache->block_start_index = start_index; + } + if (end_index < cache->block_end_index) { + int size1 = cache->block_end_index - end_index; + int size2 = 0; + int idx1, idx2 = 0; + +// printf("\tcache releasing: [%d:%d] (%d)\n", cache->block_end_index - size1, cache->block_end_index, cache->block_cursor); + + idx1 = (cache->block_cursor + end_index - cache->block_start_index) % cache_size; + if (idx1 + size1 > cache_size) { + size2 = idx1 + size1 - cache_size; + size1 -= size2; + filelist_file_cache_block_release(filelist, size2, idx2); + } + filelist_file_cache_block_release(filelist, size1, idx1); + + cache->block_end_index = end_index; + } + +// printf("\tcache cleaned up...\n"); + + if (start_index < cache->block_start_index) { + /* Add (request) needed entries before already cached ones. */ + /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */ + int size1 = cache->block_start_index - start_index; + int size2 = 0; + int idx1, idx2; + + if (size1 > cache->block_cursor) { + size2 = size1; + size1 -= cache->block_cursor; + size2 -= size1; + idx2 = 0; + idx1 = cache_size - size1; + } + else { + idx1 = cache->block_cursor - size1; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, start_index + size1, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, start_index, size1, idx1)) { + return false; + } + + cache->block_cursor = idx1; + cache->block_start_index = start_index; + } +// printf("\tstart-extended...\n"); + if (end_index > cache->block_end_index) { + /* Add (request) needed entries after already cached ones. */ + /* Note: We need some index black magic to wrap around (cycle) inside our cache_size array... */ + int size1 = end_index - cache->block_end_index; + int size2 = 0; + int idx1, idx2; + + idx1 = (cache->block_cursor + end_index - cache->block_start_index - size1) % cache_size; + if ((idx1 + size1) > cache_size) { + size2 = size1; + size1 = cache_size - idx1; + size2 -= size1; + idx2 = 0; + } + + if (size2) { + if (!filelist_file_cache_block_create(filelist, end_index - size2, size2, idx2)) { + return false; + } + } + if (!filelist_file_cache_block_create(filelist, end_index - size1 - size2, size1, idx1)) { + return false; + } + + cache->block_end_index = end_index; + } + +// printf("\tend-extended...\n"); + } + } + else if ((cache->block_center_index != index) && (cache->flags & FLC_PREVIEWS_ACTIVE)) { + /* We try to always preview visible entries first, so 'restart' preview background task. */ + filelist_cache_previews_update(filelist); + filelist_cache_previews_clear(cache); + } + +// printf("Re-queueing previews...\n"); + + /* Note we try to preview first images around given index - i.e. assumed visible ones. */ + if (cache->flags & FLC_PREVIEWS_ACTIVE) { + for (i = 0; ((index + i) < end_index) || ((index - i) >= start_index); i++) { + if ((index - i) >= start_index) { + const int idx = (cache->block_cursor + (index - start_index) - i) % cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[idx], index - i); + } + if ((index + i) < end_index) { + const int idx = (cache->block_cursor + (index - start_index) + i) % cache_size; + filelist_cache_previews_push(filelist, cache->block_entries[idx], index + i); + } + } + } + + cache->block_center_index = index; + +// printf("%s Finished!\n", __func__); + + return true; +} + +void filelist_cache_previews_set(FileList *filelist, const bool use_previews) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + if (use_previews == ((cache->flags & FLC_PREVIEWS_ACTIVE) != 0)) { + return; + } + /* Do not start preview work while listing, gives nasty flickering! */ + else if (use_previews && (filelist->flags & FL_IS_READY)) { + cache->flags |= FLC_PREVIEWS_ACTIVE; + + BLI_assert((cache->previews_pool == NULL) && (cache->previews_todo == NULL) && (cache->previews_done == NULL)); + +// printf("%s: Init Previews...\n", __func__); + + /* No need to populate preview queue here, filelist_file_cache_block() handles this. */ + } + else { +// printf("%s: Clear Previews...\n", __func__); + + filelist_cache_previews_free(cache, true); + } +} + +bool filelist_cache_previews_update(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + TaskPool *pool = cache->previews_pool; + bool changed = false; + + if (!pool) { + return changed; + } + +// printf("%s: Update Previews...\n", __func__); + + while (!BLI_thread_queue_is_empty(cache->previews_done)) { + FileListEntryPreview *preview = BLI_thread_queue_pop(cache->previews_done); + FileDirEntry *entry; + + /* Paranoid (should never happen currently since we consume this queue from a single thread), but... */ + if (!preview) { + continue; + } + /* entry might have been removed from cache in the mean while, we do not want to cache it again here. */ + entry = filelist_file_ex(filelist, preview->index, false); + +// printf("%s: %d - %s - %p\n", __func__, preview->index, preview->path, preview->img); + + if (preview->img) { + /* Due to asynchronous process, a preview for a given image may be generated several times, i.e. + * entry->image may already be set at this point. */ + if (entry && !entry->image) { + entry->image = preview->img; + changed = true; + } + else { + IMB_freeImBuf(preview->img); + } + } + else if (entry) { + /* We want to avoid re-processing this entry continuously! + * Note that, since entries only live in cache, preview will be retried quite often anyway. */ + entry->flags |= FILE_ENTRY_INVALID_PREVIEW; + } + + MEM_freeN(preview); + cache->previews_pending--; + BLI_assert(cache->previews_pending >= 0); + } + +// printf("%s: Previews pending: %d\n", __func__, cache->previews_pending); + if (cache->previews_pending == 0) { + if (cache->previews_timestamp == 0.0) { + cache->previews_timestamp = PIL_check_seconds_timer(); + } + else if (PIL_check_seconds_timer() - cache->previews_timestamp > 1.0) { + /* Preview task is IDLE since more than (approximatively) 1000ms, + * kill workers & task for now - they will be automatically restarted when needed. */ +// printf("%s: Inactive preview task, sleeping (%f vs %f)!\n", __func__, PIL_check_seconds_timer(), cache->previews_timestamp); + filelist_cache_previews_free(cache, false); + } + } + + return changed; +} + +bool filelist_cache_previews_running(FileList *filelist) +{ + FileListEntryCache *cache = &filelist->filelist_cache; + + return (cache->previews_pool != NULL); +} + /* would recognize .blend as well */ static bool file_is_blend_backup(const char *str) { @@ -857,10 +2001,10 @@ static int path_extension_type(const char *path) return 0; } -static int file_extension_type(const char *dir, const char *relname) +static int file_extension_type(const char *dir, const char *relpath) { char path[FILE_MAX]; - BLI_join_dirfile(path, sizeof(path), dir, relname); + BLI_join_dirfile(path, sizeof(path), dir, relpath); return path_extension_type(path); } @@ -868,185 +2012,137 @@ int ED_file_extension_icon(const char *path) { int type = path_extension_type(path); - if (type == FILE_TYPE_BLENDER) - return ICON_FILE_BLEND; - else if (type == FILE_TYPE_BLENDER_BACKUP) - return ICON_FILE_BACKUP; - else if (type == FILE_TYPE_IMAGE) - return ICON_FILE_IMAGE; - else if (type == FILE_TYPE_MOVIE) - return ICON_FILE_MOVIE; - else if (type == FILE_TYPE_PYSCRIPT) - return ICON_FILE_SCRIPT; - else if (type == FILE_TYPE_SOUND) - return ICON_FILE_SOUND; - else if (type == FILE_TYPE_FTFONT) - return ICON_FILE_FONT; - else if (type == FILE_TYPE_BTX) - return ICON_FILE_BLANK; - else if (type == FILE_TYPE_COLLADA) - return ICON_FILE_BLANK; - else if (type == FILE_TYPE_TEXT) - return ICON_FILE_TEXT; - - return ICON_FILE_BLANK; -} - -static void filelist_setfiletypes(struct FileList *filelist) -{ - struct direntry *file; - int num; - - file = filelist->filelist; - - for (num = 0; num < filelist->numfiles; num++, file++) { -#ifndef __APPLE__ - /* Don't check extensions for directories, allow in OSX cause bundles have extensions*/ - if (file->type & S_IFDIR) { - continue; - } -#endif - if (filelist->filter_data.filter_glob[0] && - BLI_testextensie_glob(file->relname, filelist->filter_data.filter_glob)) - { - file->flags = FILE_TYPE_OPERATOR; - } - else { - file->flags = file_extension_type(filelist->dir, file->relname); - } + switch (type) { + case FILE_TYPE_BLENDER: + return ICON_FILE_BLEND; + case FILE_TYPE_BLENDER_BACKUP: + return ICON_FILE_BACKUP; + case FILE_TYPE_IMAGE: + return ICON_FILE_IMAGE; + case FILE_TYPE_MOVIE: + return ICON_FILE_MOVIE; + case FILE_TYPE_PYSCRIPT: + return ICON_FILE_SCRIPT; + case FILE_TYPE_SOUND: + return ICON_FILE_SOUND; + case FILE_TYPE_FTFONT: + return ICON_FILE_FONT; + case FILE_TYPE_BTX: + return ICON_FILE_BLANK; + case FILE_TYPE_COLLADA: + return ICON_FILE_BLANK; + case FILE_TYPE_TEXT: + return ICON_FILE_TEXT; + default: + return ICON_FILE_BLANK; } } -static void filelist_read_dir(struct FileList *filelist) -{ - if (!filelist) return; - - filelist->fidx = NULL; - filelist->filelist = NULL; - - BLI_make_exist(filelist->dir); - BLI_cleanup_dir(G.main->name, filelist->dir); - filelist->numfiles = BLI_filelist_dir_contents(filelist->dir, &(filelist->filelist)); - - /* We shall *never* get an empty list here, since we now the dir exists and is readable - * (ensured by BLI_make_exist()). So we expect at the very least the parent '..' entry. */ - BLI_assert(filelist->numfiles != 0); - - filelist_setfiletypes(filelist); -} - -static void filelist_read_main(struct FileList *filelist) +int filelist_empty(struct FileList *filelist) { - if (!filelist) return; - filelist_from_main(filelist); + return (filelist->filelist.nbr_entries == 0); } -static void filelist_read_library(struct FileList *filelist) +unsigned int filelist_entry_select_set( + const FileList *filelist, const FileDirEntry *entry, FileSelType select, unsigned int flag, FileCheckType check) { - if (!filelist) return; - BLI_cleanup_dir(G.main->name, filelist->dir); - filelist_from_library(filelist); - if (!filelist->libfiledata) { - int num; - struct direntry *file; + /* Default NULL pointer if not found is fine here! */ + void **es_p = BLI_ghash_lookup_p(filelist->selection_state, entry->uuid); + unsigned int entry_flag = es_p ? GET_UINT_FROM_POINTER(*es_p) : 0; + const unsigned int org_entry_flag = entry_flag; - filelist_read_dir(filelist); - file = filelist->filelist; - for (num = 0; num < filelist->numfiles; num++, file++) { - if (BLO_has_bfile_extension(file->relname)) { - char name[FILE_MAX]; + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); - BLI_join_dirfile(name, sizeof(name), filelist->dir, file->relname); + if (((check == CHECK_ALL)) || + ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) + { + switch (select) { + case FILE_SEL_REMOVE: + entry_flag &= ~flag; + break; + case FILE_SEL_ADD: + entry_flag |= flag; + break; + case FILE_SEL_TOGGLE: + entry_flag ^= flag; + break; + } + } - /* prevent current file being used as acceptable dir */ - if (BLI_path_cmp(G.main->name, name) != 0) { - file->type &= ~S_IFMT; - file->type |= S_IFDIR; - } + if (entry_flag != org_entry_flag) { + if (es_p) { + if (entry_flag) { + *es_p = SET_UINT_IN_POINTER(entry_flag); + } + else { + BLI_ghash_remove(filelist->selection_state, entry->uuid, MEM_freeN, NULL); } } + else if (entry_flag) { + void *key = MEM_mallocN(sizeof(entry->uuid), __func__); + memcpy(key, entry->uuid, sizeof(entry->uuid)); + BLI_ghash_insert(filelist->selection_state, key, SET_UINT_IN_POINTER(entry_flag)); + } } -} - -void filelist_readdir(struct FileList *filelist) -{ - filelist->readf(filelist); - filelist->need_sorting = true; - filelist->need_thumbnails = true; - filelist_filter_clear(filelist); + return entry_flag; } -int filelist_empty(struct FileList *filelist) +void filelist_entry_select_index_set(FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check) { - return filelist->filelist == NULL; -} + FileDirEntry *entry = filelist_file(filelist, index); -void filelist_select_file(struct FileList *filelist, int index, FileSelType select, unsigned int flag, FileCheckType check) -{ - struct direntry *file = filelist_file(filelist, index); - if (file != NULL) { - int check_ok = 0; - switch (check) { - case CHECK_DIRS: - check_ok = S_ISDIR(file->type); - break; - case CHECK_ALL: - check_ok = 1; - break; - case CHECK_FILES: - default: - check_ok = !S_ISDIR(file->type); - break; - } - if (check_ok) { - switch (select) { - case FILE_SEL_REMOVE: - file->selflag &= ~flag; - break; - case FILE_SEL_ADD: - file->selflag |= flag; - break; - case FILE_SEL_TOGGLE: - file->selflag ^= flag; - break; - } - } + if (entry) { + filelist_entry_select_set(filelist, entry, select, flag, check); } } -void filelist_select(struct FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check) +void filelist_entries_select_index_range_set( + FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check) { /* select all valid files between first and last indicated */ - if ((sel->first >= 0) && (sel->first < filelist->numfiltered) && (sel->last >= 0) && (sel->last < filelist->numfiltered)) { + if ((sel->first >= 0) && (sel->first < filelist->filelist.nbr_entries_filtered) && + (sel->last >= 0) && (sel->last < filelist->filelist.nbr_entries_filtered)) + { int current_file; for (current_file = sel->first; current_file <= sel->last; current_file++) { - filelist_select_file(filelist, current_file, select, flag, check); + filelist_entry_select_index_set(filelist, current_file, select, flag, check); } } } -bool filelist_is_selected(struct FileList *filelist, int index, FileCheckType check) +unsigned int filelist_entry_select_get(FileList *filelist, FileDirEntry *entry, FileCheckType check) { - struct direntry *file = filelist_file(filelist, index); - if (!file) { - return 0; - } - switch (check) { - case CHECK_DIRS: - return S_ISDIR(file->type) && (file->selflag & FILE_SEL_SELECTED); - case CHECK_FILES: - return S_ISREG(file->type) && (file->selflag & FILE_SEL_SELECTED); - case CHECK_ALL: - default: - return (file->selflag & FILE_SEL_SELECTED) != 0; + BLI_assert(entry); + BLI_assert(ELEM(check, CHECK_DIRS, CHECK_FILES, CHECK_ALL)); + + if (((check == CHECK_ALL)) || + ((check == CHECK_DIRS) && (entry->typeflag & FILE_TYPE_DIR)) || + ((check == CHECK_FILES) && !(entry->typeflag & FILE_TYPE_DIR))) + { + /* Default NULL pointer if not found is fine here! */ + return GET_UINT_FROM_POINTER(BLI_ghash_lookup(filelist->selection_state, entry->uuid)); } + + return 0; } +unsigned int filelist_entry_select_index_get(FileList *filelist, const int index, FileCheckType check) +{ + FileDirEntry *entry = filelist_file(filelist, index); + + if (entry) { + return filelist_entry_select_get(filelist, entry, check); + } + + return 0; +} -bool filelist_islibrary(struct FileList *filelist, char *dir, char *group) +bool filelist_islibrary(struct FileList *filelist, char *dir, char **group) { - return BLO_is_a_library(filelist->dir, dir, group); + return BLO_library_path_explode(filelist->filelist.root, dir, group, NULL); } static int groupname_to_code(const char *group) @@ -1054,6 +2150,8 @@ static int groupname_to_code(const char *group) char buf[BLO_GROUP_MAX]; char *lslash; + BLI_assert(group); + BLI_strncpy(buf, group, sizeof(buf)); lslash = (char *)BLI_last_slash(buf); if (lslash) @@ -1062,182 +2160,228 @@ static int groupname_to_code(const char *group) return buf[0] ? BKE_idcode_from_name(buf) : 0; } -static void filelist_from_library(struct FileList *filelist) +static unsigned int groupname_to_filter_id(const char *group) { - LinkNode *l, *names, *previews; - struct ImBuf *ima; - int ok, i, nprevs, nnames, idcode; - char filename[FILE_MAX]; - char dir[FILE_MAX], group[BLO_GROUP_MAX]; + int id_code = groupname_to_code(group); + + return BKE_idcode_to_idfilter(id_code); +} + +/** + * From here, we are in 'Job Context', i.e. have to be careful about sharing stuff between background working thread + * and main one (used by UI among other things). + */ +typedef struct TodoDir { + int level; + char *dir; +} TodoDir; + +static int filelist_readjob_list_dir( + const char *root, ListBase *entries, const char *filter_glob, + const bool do_lib, const char *main_name, const bool skip_currpar) +{ + struct direntry *files; + int nbr_files, nbr_entries = 0; + + nbr_files = BLI_filelist_dir_contents(root, &files); + if (files) { + int i = nbr_files; + while (i--) { + FileListInternEntry *entry; + + if (skip_currpar && FILENAME_IS_CURRPAR(files[i].relname)) { + continue; + } + + entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = MEM_dupallocN(files[i].relname); + entry->st = files[i].s; + + /* Set file type. */ + if (S_ISDIR(files[i].s.st_mode)) { + entry->typeflag = FILE_TYPE_DIR; + } + else if (do_lib && BLO_has_bfile_extension(entry->relpath)) { + /* If we are considering .blend files as libs, promote them to directory status. */ + char name[FILE_MAX]; + + entry->typeflag = FILE_TYPE_BLENDER; + + BLI_join_dirfile(name, sizeof(name), root, entry->relpath); + + /* prevent current file being used as acceptable dir */ + if (BLI_path_cmp(main_name, name) != 0) { + entry->typeflag |= FILE_TYPE_DIR; + } + } + /* Otherwise, do not check extensions for directories! */ + else if (!(entry->typeflag & FILE_TYPE_DIR)) { + entry->typeflag = file_extension_type(root, entry->relpath); + if (filter_glob[0] && BLI_testextensie_glob(entry->relpath, filter_glob)) { + entry->typeflag |= FILE_TYPE_OPERATOR; + } + } + + BLI_addtail(entries, entry); + nbr_entries++; + } + BLI_filelist_free(files, nbr_files); + } + return nbr_entries; +} + +static int filelist_readjob_list_lib(const char *root, ListBase *entries, const bool skip_currpar) +{ + FileListInternEntry *entry; + LinkNode *ln, *names; + int i, nnames, idcode = 0, nbr_entries = 0; + char dir[FILE_MAX], *group; + bool ok; + + struct BlendHandle *libfiledata = NULL; /* name test */ - ok = filelist_islibrary(filelist, dir, group); + ok = BLO_library_path_explode(root, dir, &group, NULL); if (!ok) { - /* free */ - if (filelist->libfiledata) BLO_blendhandle_close(filelist->libfiledata); - filelist->libfiledata = NULL; - return; + return nbr_entries; } - - BLI_strncpy(filename, G.main->name, sizeof(filename)); /* there we go */ - /* for the time being only read filedata when libfiledata==0 */ - if (filelist->libfiledata == NULL) { - filelist->libfiledata = BLO_blendhandle_from_file(dir, NULL); - if (filelist->libfiledata == NULL) return; + libfiledata = BLO_blendhandle_from_file(dir, NULL); + if (libfiledata == NULL) { + return nbr_entries; } - - idcode = groupname_to_code(group); - - /* memory for strings is passed into filelist[i].relname - * and freed in freefilelist */ - if (idcode) { - previews = BLO_blendhandle_get_previews(filelist->libfiledata, idcode, &nprevs); - names = BLO_blendhandle_get_datablock_names(filelist->libfiledata, idcode, &nnames); - /* ugh, no rewind, need to reopen */ - BLO_blendhandle_close(filelist->libfiledata); - filelist->libfiledata = BLO_blendhandle_from_file(dir, NULL); - + + /* memory for strings is passed into filelist[i].entry->relpath and freed in filelist_entry_free. */ + if (group) { + idcode = groupname_to_code(group); + names = BLO_blendhandle_get_datablock_names(libfiledata, idcode, &nnames); } else { - previews = NULL; - nprevs = 0; - names = BLO_blendhandle_get_linkable_groups(filelist->libfiledata); + names = BLO_blendhandle_get_linkable_groups(libfiledata); nnames = BLI_linklist_count(names); } - filelist->numfiles = nnames + 1; - filelist->filelist = MEM_mallocN(filelist->numfiles * sizeof(*filelist->filelist), __func__); - memset(filelist->filelist, 0, filelist->numfiles * sizeof(*filelist->filelist)); + BLO_blendhandle_close(libfiledata); - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[0].type |= S_IFDIR; - - for (i = 0, l = names; i < nnames; i++, l = l->next) { - const char *blockname = l->link; - - filelist->filelist[i + 1].relname = BLI_strdup(blockname); - if (idcode) { - filelist->filelist[i + 1].type |= S_IFREG; - } - else { - filelist->filelist[i + 1].type |= S_IFDIR; - } + if (!skip_currpar) { + entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(FILENAME_PARENT); + entry->typeflag |= (FILE_TYPE_BLENDERLIB | FILE_TYPE_DIR); + BLI_addtail(entries, entry); + nbr_entries++; } - if (previews && (nnames != nprevs)) { - printf("filelist_from_library: error, found %d items, %d previews\n", nnames, nprevs); - } - else if (previews) { - for (i = 0, l = previews; i < nnames; i++, l = l->next) { - PreviewImage *img = l->link; - - if (img) { - unsigned int w = img->w[ICON_SIZE_PREVIEW]; - unsigned int h = img->h[ICON_SIZE_PREVIEW]; - unsigned int *rect = img->rect[ICON_SIZE_PREVIEW]; + for (i = 0, ln = names; i < nnames; i++, ln = ln->next) { + const char *blockname = ln->link; - /* first allocate imbuf for copying preview into it */ - if (w > 0 && h > 0 && rect) { - ima = IMB_allocImBuf(w, h, 32, IB_rect); - memcpy(ima->rect, rect, w * h * sizeof(unsigned int)); - filelist->filelist[i + 1].image = ima; - filelist->filelist[i + 1].flags = FILE_TYPE_IMAGE; - } - } + entry = MEM_callocN(sizeof(*entry), __func__); + entry->relpath = BLI_strdup(blockname); + entry->typeflag |= FILE_TYPE_BLENDERLIB; + if (!(group && idcode)) { + entry->typeflag |= FILE_TYPE_DIR; + entry->blentype = groupname_to_code(blockname); + } + else { + entry->blentype = idcode; } + BLI_addtail(entries, entry); + nbr_entries++; } BLI_linklist_free(names, free); - if (previews) { - BLI_linklist_free(previews, BKE_previewimg_freefunc); - } - BLI_strncpy(G.main->name, filename, sizeof(filename)); /* prevent G.main->name to change */ + return nbr_entries; } -static void filelist_from_main(struct FileList *filelist) +#if 0 +/* Kept for reference here, in case we want to add back that feature later. We do not need it currently. */ +/* Code ***NOT*** updated for job stuff! */ +static void filelist_readjob_main_rec(struct FileList *filelist) { ID *id; - struct direntry *files, *firstlib = NULL; + FileDirEntry *files, *firstlib = NULL; ListBase *lb; int a, fake, idcode, ok, totlib, totbl; + + // filelist->type = FILE_MAIN; // XXX TODO: add modes to filebrowser - // filelist->type = FILE_MAIN; // XXXXX TODO: add modes to filebrowser + BLI_assert(filelist->filelist.entries == NULL); - if (filelist->dir[0] == '/') filelist->dir[0] = 0; + if (filelist->filelist.root[0] == '/') filelist->filelist.root[0] = '\0'; - if (filelist->dir[0]) { - idcode = groupname_to_code(filelist->dir); - if (idcode == 0) filelist->dir[0] = 0; + if (filelist->filelist.root[0]) { + idcode = groupname_to_code(filelist->filelist.root); + if (idcode == 0) filelist->filelist.root[0] = '\0'; } if (filelist->dir[0] == 0) { /* make directories */ #ifdef WITH_FREESTYLE - filelist->numfiles = 24; + filelist->filelist.nbr_entries = 24; #else - filelist->numfiles = 23; + filelist->filelist.nbr_entries = 23; #endif - filelist->filelist = MEM_mallocN(sizeof(*filelist->filelist) * filelist->numfiles, __func__); - - for (a = 0; a < filelist->numfiles; a++) { - memset(&(filelist->filelist[a]), 0, sizeof(struct direntry)); - filelist->filelist[a].type |= S_IFDIR; - } - - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[1].relname = BLI_strdup("Scene"); - filelist->filelist[2].relname = BLI_strdup("Object"); - filelist->filelist[3].relname = BLI_strdup("Mesh"); - filelist->filelist[4].relname = BLI_strdup("Curve"); - filelist->filelist[5].relname = BLI_strdup("Metaball"); - filelist->filelist[6].relname = BLI_strdup("Material"); - filelist->filelist[7].relname = BLI_strdup("Texture"); - filelist->filelist[8].relname = BLI_strdup("Image"); - filelist->filelist[9].relname = BLI_strdup("Ika"); - filelist->filelist[10].relname = BLI_strdup("Wave"); - filelist->filelist[11].relname = BLI_strdup("Lattice"); - filelist->filelist[12].relname = BLI_strdup("Lamp"); - filelist->filelist[13].relname = BLI_strdup("Camera"); - filelist->filelist[14].relname = BLI_strdup("Ipo"); - filelist->filelist[15].relname = BLI_strdup("World"); - filelist->filelist[16].relname = BLI_strdup("Screen"); - filelist->filelist[17].relname = BLI_strdup("VFont"); - filelist->filelist[18].relname = BLI_strdup("Text"); - filelist->filelist[19].relname = BLI_strdup("Armature"); - filelist->filelist[20].relname = BLI_strdup("Action"); - filelist->filelist[21].relname = BLI_strdup("NodeTree"); - filelist->filelist[22].relname = BLI_strdup("Speaker"); + filelist_resize(filelist, filelist->filelist.nbr_entries); + + for (a = 0; a < filelist->filelist.nbr_entries; a++) { + filelist->filelist.entries[a].typeflag |= FILE_TYPE_DIR; + } + + filelist->filelist.entries[0].entry->relpath = BLI_strdup(FILENAME_PARENT); + filelist->filelist.entries[1].entry->relpath = BLI_strdup("Scene"); + filelist->filelist.entries[2].entry->relpath = BLI_strdup("Object"); + filelist->filelist.entries[3].entry->relpath = BLI_strdup("Mesh"); + filelist->filelist.entries[4].entry->relpath = BLI_strdup("Curve"); + filelist->filelist.entries[5].entry->relpath = BLI_strdup("Metaball"); + filelist->filelist.entries[6].entry->relpath = BLI_strdup("Material"); + filelist->filelist.entries[7].entry->relpath = BLI_strdup("Texture"); + filelist->filelist.entries[8].entry->relpath = BLI_strdup("Image"); + filelist->filelist.entries[9].entry->relpath = BLI_strdup("Ika"); + filelist->filelist.entries[10].entry->relpath = BLI_strdup("Wave"); + filelist->filelist.entries[11].entry->relpath = BLI_strdup("Lattice"); + filelist->filelist.entries[12].entry->relpath = BLI_strdup("Lamp"); + filelist->filelist.entries[13].entry->relpath = BLI_strdup("Camera"); + filelist->filelist.entries[14].entry->relpath = BLI_strdup("Ipo"); + filelist->filelist.entries[15].entry->relpath = BLI_strdup("World"); + filelist->filelist.entries[16].entry->relpath = BLI_strdup("Screen"); + filelist->filelist.entries[17].entry->relpath = BLI_strdup("VFont"); + filelist->filelist.entries[18].entry->relpath = BLI_strdup("Text"); + filelist->filelist.entries[19].entry->relpath = BLI_strdup("Armature"); + filelist->filelist.entries[20].entry->relpath = BLI_strdup("Action"); + filelist->filelist.entries[21].entry->relpath = BLI_strdup("NodeTree"); + filelist->filelist.entries[22].entry->relpath = BLI_strdup("Speaker"); #ifdef WITH_FREESTYLE - filelist->filelist[23].relname = BLI_strdup("FreestyleLineStyle"); + filelist->filelist.entries[23].entry->relpath = BLI_strdup("FreestyleLineStyle"); #endif } else { /* make files */ - idcode = groupname_to_code(filelist->dir); + idcode = groupname_to_code(filelist->filelist.root); lb = which_libbase(G.main, idcode); if (lb == NULL) return; - filelist->numfiles = 0; + filelist->filelist.nbr_entries = 0; for (id = lb->first; id; id = id->next) { - if (!filelist->filter_data.hide_dot || id->name[2] != '.') { - filelist->numfiles++; + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { + filelist->filelist.nbr_entries++; } } - /* XXXXX TODO: if databrowse F4 or append/link filelist->hide_parent has to be set */ - if (!filelist->filter_data.hide_parent) filelist->numfiles += 1; - filelist->filelist = filelist->numfiles > 0 ? MEM_mallocN(sizeof(*filelist->filelist) * filelist->numfiles, __func__) : NULL; + /* XXX TODO: if databrowse F4 or append/link filelist->flags & FLF_HIDE_PARENT has to be set */ + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) + filelist->filelist.nbr_entries++; - files = filelist->filelist; + if (filelist->filelist.nbr_entries > 0) { + filelist_resize(filelist, filelist->filelist.nbr_entries); + } - if (files && !filelist->filter_data.hide_parent) { - memset(&(filelist->filelist[0]), 0, sizeof(struct direntry)); - filelist->filelist[0].relname = BLI_strdup(FILENAME_PARENT); - filelist->filelist[0].type |= S_IFDIR; + files = filelist->filelist.entries; + + if (!(filelist->filter_data.flags & FLF_HIDE_PARENT)) { + files->entry->relpath = BLI_strdup(FILENAME_PARENT); + files->typeflag |= FILE_TYPE_DIR; files++; } @@ -1246,37 +2390,36 @@ static void filelist_from_main(struct FileList *filelist) for (id = lb->first; id; id = id->next) { ok = 1; if (ok) { - if (files && (!filelist->filter_data.hide_dot || id->name[2] != '.')) { - memset(files, 0, sizeof(struct direntry)); + if (!(filelist->filter_data.flags & FLF_HIDE_DOT) || id->name[2] != '.') { if (id->lib == NULL) { - files->relname = BLI_strdup(id->name + 2); + files->entry->relpath = BLI_strdup(id->name + 2); } else { char relname[FILE_MAX + (MAX_ID_NAME - 2) + 3]; BLI_snprintf(relname, sizeof(relname), "%s | %s", id->lib->name, id->name + 2); - files->relname = BLI_strdup(relname); + files->entry->relpath = BLI_strdup(relname); } - files->type |= S_IFREG; -#if 0 /* XXXXX TODO show the selection status of the objects */ +// files->type |= S_IFREG; +#if 0 /* XXX TODO show the selection status of the objects */ if (!filelist->has_func) { /* F4 DATA BROWSE */ if (idcode == ID_OB) { - if ( ((Object *)id)->flag & SELECT) files->selflag |= FILE_SEL_SELECTED; + if ( ((Object *)id)->flag & SELECT) files->entry->selflag |= FILE_SEL_SELECTED; } else if (idcode == ID_SCE) { - if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->selflag |= FILE_SEL_SELECTED; + if ( ((Scene *)id)->r.scemode & R_BG_RENDER) files->entry->selflag |= FILE_SEL_SELECTED; } } #endif - files->nr = totbl + 1; - files->poin = id; +// files->entry->nr = totbl + 1; + files->entry->poin = id; fake = id->flag & LIB_FAKEUSER; if (idcode == ID_MA || idcode == ID_TE || idcode == ID_LA || idcode == ID_WO || idcode == ID_IM) { - files->flags |= FILE_TYPE_IMAGE; + files->typeflag |= FILE_TYPE_IMAGE; } - if (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->extra), "LF %d", id->us); - else if (id->lib) BLI_snprintf(files->extra, sizeof(files->extra), "L %d", id->us); - else if (fake) BLI_snprintf(files->extra, sizeof(files->extra), "F %d", id->us); - else BLI_snprintf(files->extra, sizeof(files->extra), " %d", id->us); +// if (id->lib && fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "LF %d", id->us); +// else if (id->lib) BLI_snprintf(files->extra, sizeof(files->entry->extra), "L %d", id->us); +// else if (fake) BLI_snprintf(files->extra, sizeof(files->entry->extra), "F %d", id->us); +// else BLI_snprintf(files->extra, sizeof(files->entry->extra), " %d", id->us); if (id->lib) { if (totlib == 0) firstlib = files; @@ -1291,152 +2434,279 @@ static void filelist_from_main(struct FileList *filelist) /* only qsort of library blocks */ if (totlib > 1) { - qsort(firstlib, totlib, sizeof(struct direntry), compare_name); + qsort(firstlib, totlib, sizeof(*files), compare_name); } } } +#endif -/* ********** Thumbnails job ********** */ +static void filelist_readjob_do( + const bool do_lib, + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + ListBase entries = {0}; + BLI_Stack *todo_dirs; + TodoDir *td_dir; + char dir[FILE_MAX_LIBEXTRA]; + char filter_glob[64]; /* TODO should be define! */ + const char *root = filelist->filelist.root; + const int max_recursion = filelist->max_recursion; + int nbr_done_dirs = 0, nbr_todo_dirs = 1; -typedef struct ThumbnailJob { - ListBase loadimages; - ImBuf *static_icons_buffers[BIFICONID_LAST]; - const short *stop; - const short *do_update; - struct FileList *filelist; - ReportList reports; -} ThumbnailJob; +// BLI_assert(filelist->filtered == NULL); + BLI_assert(BLI_listbase_is_empty(&filelist->filelist.entries) && (filelist->filelist.nbr_entries == 0)); -bool filelist_need_thumbnails(FileList *filelist) -{ - return filelist->need_thumbnails; -} + todo_dirs = BLI_stack_new(sizeof(*td_dir), __func__); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = 1; -static void thumbnail_joblist_free(ThumbnailJob *tj) -{ - FileImage *limg = tj->loadimages.first; - - /* free the images not yet copied to the filelist -> these will get freed with the filelist */ - for (; limg; limg = limg->next) { - if ((limg->img) && (!limg->done)) { - IMB_freeImBuf(limg->img); - } - } - BLI_freelistN(&tj->loadimages); -} + BLI_strncpy(dir, filelist->filelist.root, sizeof(dir)); + BLI_strncpy(filter_glob, filelist->filter_data.filter_glob, sizeof(filter_glob)); -static void thumbnails_startjob(void *tjv, short *stop, short *do_update, float *UNUSED(progress)) -{ - ThumbnailJob *tj = tjv; - FileImage *limg = tj->loadimages.first; + BLI_cleanup_dir(main_name, dir); + td_dir->dir = BLI_strdup(dir); - tj->stop = stop; - tj->do_update = do_update; + while (!BLI_stack_is_empty(todo_dirs) && !(*stop)) { + FileListInternEntry *entry; + int nbr_entries = 0; + bool is_lib = do_lib; - while ((*stop == 0) && (limg)) { - ThumbSource source = 0; + char *subdir; + int recursion_level; + bool skip_currpar; - BLI_assert(limg->flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)); - if (limg->flags & FILE_TYPE_IMAGE) { - source = THB_SOURCE_IMAGE; + td_dir = BLI_stack_peek(todo_dirs); + subdir = td_dir->dir; + recursion_level = td_dir->level; + skip_currpar = (recursion_level > 1); + + BLI_stack_discard(todo_dirs); + + if (do_lib) { + nbr_entries = filelist_readjob_list_lib(subdir, &entries, skip_currpar); } - else if (limg->flags & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) { - source = THB_SOURCE_BLEND; + if (!nbr_entries) { + is_lib = false; + nbr_entries = filelist_readjob_list_dir(subdir, &entries, filter_glob, do_lib, main_name, skip_currpar); } - else if (limg->flags & FILE_TYPE_MOVIE) { - source = THB_SOURCE_MOVIE; + + for (entry = entries.first; entry; entry = entry->next) { + BLI_join_dirfile(dir, sizeof(dir), subdir, entry->relpath); + BLI_cleanup_file(root, dir); + + /* Generate our entry uuid. Abusing uuid as an uint32, shall be more than enough here, + * things would crash way before we overflow that counter! + * Using an atomic operation to avoid having to lock thread... + * Note that we do not really need this here currently, since there is a single listing thread, but better + * remain consistent about threading! */ + *((uint32_t *)entry->uuid) = atomic_add_uint32((uint32_t *)filelist->filelist_intern.curr_uuid, 1); + + BLI_path_rel(dir, root); + /* Only thing we change in direntry here, so we need to free it first. */ + MEM_freeN(entry->relpath); + entry->relpath = BLI_strdup(dir + 2); /* + 2 to remove '//' added by BLI_path_rel */ + entry->name = BLI_strdup(fileentry_uiname(root, entry->relpath, entry->typeflag, dir)); + + /* Here we decide whether current filedirentry is to be listed too, or not. */ + if (max_recursion && (is_lib || (recursion_level <= max_recursion))) { + if (((entry->typeflag & FILE_TYPE_DIR) == 0) || FILENAME_IS_CURRPAR(entry->relpath)) { + /* Skip... */ + } + else if (!is_lib && (recursion_level >= max_recursion) && + ((entry->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) == 0)) + { + /* Do not recurse in real directories in this case, only in .blend libs. */ + } + else { + /* We have a directory we want to list, add it to todo list! */ + BLI_join_dirfile(dir, sizeof(dir), root, entry->relpath); + BLI_cleanup_dir(main_name, dir); + td_dir = BLI_stack_push_r(todo_dirs); + td_dir->level = recursion_level + 1; + td_dir->dir = BLI_strdup(dir); + nbr_todo_dirs++; + } + } } - else if (limg->flags & FILE_TYPE_FTFONT) { - source = THB_SOURCE_FONT; + + if (nbr_entries) { + BLI_mutex_lock(lock); + + *do_update = true; + + BLI_movelisttolist(&filelist->filelist.entries, &entries); + filelist->filelist.nbr_entries += nbr_entries; + + BLI_mutex_unlock(lock); } - limg->img = IMB_thumb_manage(limg->path, THB_LARGE, source); - *do_update = true; - PIL_sleep_ms(10); - limg = limg->next; + + nbr_done_dirs++; + *progress = (float)nbr_done_dirs / (float)nbr_todo_dirs; + MEM_freeN(subdir); + } + + /* If we were interrupted by stop, stack may not be empty and we need to free pending dir paths. */ + while (!BLI_stack_is_empty(todo_dirs)) { + td_dir = BLI_stack_peek(todo_dirs); + MEM_freeN(td_dir->dir); + BLI_stack_discard(todo_dirs); } + BLI_stack_free(todo_dirs); } -static void thumbnails_update(void *tjv) +static void filelist_readjob_dir( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) { - ThumbnailJob *tj = tjv; + filelist_readjob_do(false, filelist, main_name, stop, do_update, progress, lock); +} - if (tj->filelist && tj->filelist->filelist) { - FileImage *limg = tj->loadimages.first; - while (limg) { - if (!limg->done && limg->img) { - tj->filelist->filelist[limg->index].image = IMB_dupImBuf(limg->img); - limg->done = true; - IMB_freeImBuf(limg->img); - limg->img = NULL; - } - limg = limg->next; - } - } +static void filelist_readjob_lib( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + filelist_readjob_do(true, filelist, main_name, stop, do_update, progress, lock); +} + +static void filelist_readjob_main( + FileList *filelist, const char *main_name, short *stop, short *do_update, float *progress, ThreadMutex *lock) +{ + /* TODO! */ + filelist_readjob_dir(filelist, main_name, stop, do_update, progress, lock); +} + + +typedef struct FileListReadJob { + ThreadMutex lock; + char main_name[FILE_MAX]; + struct FileList *filelist; + struct FileList *tmp_filelist; /* XXX We may use a simpler struct here... just a linked list and root path? */ +} FileListReadJob; + +static void filelist_readjob_startjob(void *flrjv, short *stop, short *do_update, float *progress) +{ + FileListReadJob *flrj = flrjv; + +// printf("START filelist reading (%d files, main thread: %d)\n", +// flrj->filelist->filelist.nbr_entries, BLI_thread_is_main()); + + BLI_mutex_lock(&flrj->lock); + + BLI_assert((flrj->tmp_filelist == NULL) && flrj->filelist); + + flrj->tmp_filelist = MEM_dupallocN(flrj->filelist); + + BLI_listbase_clear(&flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.nbr_entries = 0; + + flrj->tmp_filelist->filelist_intern.filtered = NULL; + BLI_listbase_clear(&flrj->tmp_filelist->filelist_intern.entries); + memset(flrj->tmp_filelist->filelist_intern.curr_uuid, 0, sizeof(flrj->tmp_filelist->filelist_intern.curr_uuid)); + + flrj->tmp_filelist->libfiledata = NULL; + memset(&flrj->tmp_filelist->filelist_cache, 0, sizeof(flrj->tmp_filelist->filelist_cache)); + flrj->tmp_filelist->selection_state = NULL; + + BLI_mutex_unlock(&flrj->lock); + + flrj->tmp_filelist->read_jobf(flrj->tmp_filelist, flrj->main_name, stop, do_update, progress, &flrj->lock); } -static void thumbnails_endjob(void *tjv) +static void filelist_readjob_update(void *flrjv) { - ThumbnailJob *tj = tjv; + FileListReadJob *flrj = flrjv; + FileListIntern *fl_intern = &flrj->filelist->filelist_intern; + ListBase new_entries = {NULL}; + int nbr_entries, new_nbr_entries = 0; + + BLI_movelisttolist(&new_entries, &fl_intern->entries); + nbr_entries = flrj->filelist->filelist.nbr_entries; - if (!*tj->stop) { - tj->filelist->need_thumbnails = false; + BLI_mutex_lock(&flrj->lock); + + if (flrj->tmp_filelist->filelist.nbr_entries) { + /* We just move everything out of 'thread context' into final list. */ + new_nbr_entries = flrj->tmp_filelist->filelist.nbr_entries; + BLI_movelisttolist(&new_entries, &flrj->tmp_filelist->filelist.entries); + flrj->tmp_filelist->filelist.nbr_entries = 0; + } + + BLI_mutex_unlock(&flrj->lock); + + if (new_nbr_entries) { + /* Do not clear selection cache, we can assume already 'selected' uuids are still valid! */ + filelist_clear_ex(flrj->filelist, true, false); + + flrj->filelist->flags |= (FL_NEED_SORTING | FL_NEED_FILTERING); } + + /* if no new_nbr_entries, this is NOP */ + BLI_movelisttolist(&fl_intern->entries, &new_entries); + flrj->filelist->filelist.nbr_entries = nbr_entries + new_nbr_entries; } -static void thumbnails_free(void *tjv) +static void filelist_readjob_endjob(void *flrjv) { - ThumbnailJob *tj = tjv; - thumbnail_joblist_free(tj); - MEM_freeN(tj); + FileListReadJob *flrj = flrjv; + + /* In case there would be some dangling update... */ + filelist_readjob_update(flrjv); + + flrj->filelist->flags &= ~FL_IS_PENDING; + flrj->filelist->flags |= FL_IS_READY; } +static void filelist_readjob_free(void *flrjv) +{ + FileListReadJob *flrj = flrjv; + +// printf("END filelist reading (%d files)\n", flrj->filelist->filelist.nbr_entries); + + if (flrj->tmp_filelist) { + /* tmp_filelist shall never ever be filtered! */ + BLI_assert(flrj->tmp_filelist->filelist.nbr_entries == 0); + BLI_assert(BLI_listbase_is_empty(&flrj->tmp_filelist->filelist.entries)); -void thumbnails_start(FileList *filelist, const bContext *C) + filelist_freelib(flrj->tmp_filelist); + filelist_free(flrj->tmp_filelist); + MEM_freeN(flrj->tmp_filelist); + } + + BLI_mutex_end(&flrj->lock); + + MEM_freeN(flrj); +} + +void filelist_readjob_start(FileList *filelist, const bContext *C) { wmJob *wm_job; - ThumbnailJob *tj; - int idx; + FileListReadJob *flrj; /* prepare job data */ - tj = MEM_callocN(sizeof(*tj), __func__); - tj->filelist = filelist; - for (idx = 0; idx < filelist->numfiles; idx++) { - if (!filelist->filelist[idx].path) { - continue; - } - if (!filelist->filelist[idx].image) { - if (filelist->filelist[idx].flags & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT | - FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) - { - FileImage *limg = MEM_callocN(sizeof(*limg), __func__); - BLI_strncpy(limg->path, filelist->filelist[idx].path, sizeof(limg->path)); - limg->index = idx; - limg->flags = filelist->filelist[idx].flags; - BLI_addtail(&tj->loadimages, limg); - } - } - } + flrj = MEM_callocN(sizeof(*flrj), __func__); + flrj->filelist = filelist; + BLI_strncpy(flrj->main_name, G.main->name, sizeof(flrj->main_name)); + + filelist->flags &= ~(FL_FORCE_RESET | FL_IS_READY); + filelist->flags |= FL_IS_PENDING; - BKE_reports_init(&tj->reports, RPT_PRINT); + BLI_mutex_init(&flrj->lock); /* setup job */ - wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), filelist, "Thumbnails", - 0, WM_JOB_TYPE_FILESEL_THUMBNAIL); - WM_jobs_customdata_set(wm_job, tj, thumbnails_free); - WM_jobs_timer(wm_job, 0.5, NC_WINDOW, NC_WINDOW); - WM_jobs_callbacks(wm_job, thumbnails_startjob, NULL, thumbnails_update, thumbnails_endjob); + wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_wm_area(C), "Listing Dirs...", + WM_JOB_PROGRESS, WM_JOB_TYPE_FILESEL_READDIR); + WM_jobs_customdata_set(wm_job, flrj, filelist_readjob_free); + WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST); + WM_jobs_callbacks(wm_job, filelist_readjob_startjob, NULL, filelist_readjob_update, filelist_readjob_endjob); /* start the job */ WM_jobs_start(CTX_wm_manager(C), wm_job); } -void thumbnails_stop(wmWindowManager *wm, FileList *filelist) +void filelist_readjob_stop(wmWindowManager *wm, ScrArea *sa) { - WM_jobs_kill_type(wm, filelist, WM_JOB_TYPE_FILESEL_THUMBNAIL); + WM_jobs_kill_type(wm, sa, WM_JOB_TYPE_FILESEL_READDIR); } -int thumbnails_running(wmWindowManager *wm, FileList *filelist) +int filelist_readjob_running(wmWindowManager *wm, ScrArea *sa) { - return WM_jobs_test(wm, filelist, WM_JOB_TYPE_FILESEL_THUMBNAIL); + return WM_jobs_test(wm, sa, WM_JOB_TYPE_FILESEL_READDIR); } diff --git a/source/blender/editors/space_file/filelist.h b/source/blender/editors/space_file/filelist.h index 44e0a5169fa..d70faab1d6a 100644 --- a/source/blender/editors/space_file/filelist.h +++ b/source/blender/editors/space_file/filelist.h @@ -40,9 +40,10 @@ extern "C" { struct BlendHandle; struct FileList; struct FileSelection; -struct direntry; struct wmWindowManager; +struct FileDirEntry; + typedef enum FileSelType { FILE_SEL_REMOVE = 0, FILE_SEL_ADD = 1, @@ -65,11 +66,10 @@ int folderlist_clear_next(struct SpaceFile *sfile); void filelist_setsorting(struct FileList *filelist, const short sort); -bool filelist_need_sorting(struct FileList *filelist); void filelist_sort(struct FileList *filelist); void filelist_setfilter_options(struct FileList *filelist, const bool hide_dot, const bool hide_parent, - const unsigned int filter, + const unsigned int filter, const unsigned int filter_id, const char *filter_glob, const char *filter_search); void filelist_filter(struct FileList *filelist); @@ -77,34 +77,48 @@ void filelist_init_icons(void); void filelist_free_icons(void); void filelist_imgsize(struct FileList *filelist, short w, short h); struct ImBuf * filelist_getimage(struct FileList *filelist, const int index); -struct ImBuf * filelist_geticon(struct FileList *filelist, const int index); +struct ImBuf * filelist_geticon_image(struct FileList *filelist, const int index); +int filelist_geticon(struct FileList *filelist, const int index, const bool is_main); struct FileList * filelist_new(short type); +void filelist_clear(struct FileList *filelist); +void filelist_clear_ex(struct FileList *filelist, const bool do_cache, const bool do_selection); void filelist_free(struct FileList *filelist); const char * filelist_dir(struct FileList *filelist); -void filelist_readdir(struct FileList *filelist); -void filelist_setdir(struct FileList *filelist, const char *dir); +void filelist_setdir(struct FileList *filelist, char *r_dir); +int filelist_files_ensure(struct FileList *filelist); int filelist_empty(struct FileList *filelist); -int filelist_numfiles(struct FileList *filelist); -struct direntry * filelist_file(struct FileList *filelist, int index); -int filelist_find(struct FileList *filelist, const char *file); +FileDirEntry * filelist_file(struct FileList *filelist, int index); +int filelist_file_findpath(struct FileList *filelist, const char *file); +FileDirEntry * filelist_entry_find_uuid(struct FileList *filelist, const int uuid[4]); +void filelist_file_cache_slidingwindow_set(struct FileList *filelist, size_t window_size); +bool filelist_file_cache_block(struct FileList *filelist, const int index); + +bool filelist_force_reset(struct FileList *filelist); +bool filelist_pending(struct FileList *filelist); +bool filelist_is_ready(struct FileList *filelist); -short filelist_changed(struct FileList *filelist); +unsigned int filelist_entry_select_set(const struct FileList *filelist, const struct FileDirEntry *entry, FileSelType select, unsigned int flag, FileCheckType check); +void filelist_entry_select_index_set(struct FileList *filelist, const int index, FileSelType select, unsigned int flag, FileCheckType check); +void filelist_entries_select_index_range_set(struct FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check); +unsigned int filelist_entry_select_get(struct FileList *filelist, struct FileDirEntry *entry, FileCheckType check); +unsigned int filelist_entry_select_index_get(struct FileList *filelist, const int index, FileCheckType check); -void filelist_select(struct FileList *filelist, FileSelection *sel, FileSelType select, unsigned int flag, FileCheckType check); -void filelist_select_file(struct FileList *filelist, int index, FileSelType select, unsigned int flag, FileCheckType check); -bool filelist_is_selected(struct FileList *filelist, int index, FileCheckType check); +void filelist_setrecursion(struct FileList *filelist, const int recursion_level); struct BlendHandle *filelist_lib(struct FileList *filelist); -bool filelist_islibrary(struct FileList *filelist, char *dir, char *group); +bool filelist_islibrary(struct FileList *filelist, char *dir, char **group); void filelist_freelib(struct FileList *filelist); -bool filelist_need_thumbnails(struct FileList *filelist); -void thumbnails_start(struct FileList *filelist, const struct bContext *C); -void thumbnails_stop(struct wmWindowManager *wm, struct FileList *filelist); -int thumbnails_running(struct wmWindowManager *wm, struct FileList *filelist); +void filelist_readjob_start(struct FileList *filelist, const struct bContext *C); +void filelist_readjob_stop(struct wmWindowManager *wm, struct ScrArea *sa); +int filelist_readjob_running(struct wmWindowManager *wm, struct ScrArea *sa); + +bool filelist_cache_previews_update(struct FileList *filelist); +void filelist_cache_previews_set(struct FileList *filelist, const bool use_previews); +bool filelist_cache_previews_running(struct FileList *filelist); #ifdef __cplusplus } diff --git a/source/blender/editors/space_file/filesel.c b/source/blender/editors/space_file/filesel.c index 21db41b27a5..a83cae6eb17 100644 --- a/source/blender/editors/space_file/filesel.c +++ b/source/blender/editors/space_file/filesel.c @@ -163,6 +163,8 @@ short ED_fileselect_set_params(SpaceFile *sfile) params->filter = 0; if ((prop = RNA_struct_find_property(op->ptr, "filter_blender"))) params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER : 0; + if ((prop = RNA_struct_find_property(op->ptr, "filter_blenlib"))) + params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDERLIB : 0; if ((prop = RNA_struct_find_property(op->ptr, "filter_backup"))) params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER_BACKUP : 0; if ((prop = RNA_struct_find_property(op->ptr, "filter_image"))) @@ -200,6 +202,13 @@ short ED_fileselect_set_params(SpaceFile *sfile) } } + /* For now, always init filterid to 'all true' */ + params->filter_id = FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU | FILTER_ID_GD | + FILTER_ID_GR | FILTER_ID_IM | FILTER_ID_LA | FILTER_ID_LS | FILTER_ID_LT | FILTER_ID_MA | + FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | FILTER_ID_OB | + FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | FILTER_ID_SO | + FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO; + if (U.uiflag & USER_HIDE_DOT) { params->flag |= FILE_HIDE_DOT; } @@ -218,6 +227,13 @@ short ED_fileselect_set_params(SpaceFile *sfile) params->display = RNA_property_enum_get(op->ptr, prop); } + if ((prop = RNA_struct_find_property(op->ptr, "sort_method"))) { + params->sort = RNA_property_enum_get(op->ptr, prop); + } + else { + params->sort = FILE_SORT_ALPHA; + } + if (params->display == FILE_DEFAULTDISPLAY) { if (U.uiflag & USER_SHOW_THUMBNAILS) { if (params->filter & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE)) @@ -244,12 +260,12 @@ short ED_fileselect_set_params(SpaceFile *sfile) params->flag |= FILE_HIDE_DOT; params->flag &= ~FILE_DIRSEL_ONLY; params->display = FILE_SHORTDISPLAY; + params->sort = FILE_SORT_ALPHA; params->filter = 0; params->filter_glob[0] = '\0'; } /* operator has no setting for this */ - params->sort = FILE_SORT_ALPHA; params->active_file = -1; @@ -292,9 +308,9 @@ void ED_fileselect_reset_params(SpaceFile *sfile) */ void fileselect_file_set(SpaceFile *sfile, const int index) { - const struct direntry *file = filelist_file(sfile->files, index); - if (file && file->relname[0] && file->path && !BLI_is_dir(file->path)) { - BLI_strncpy(sfile->params->file, file->relname, FILE_MAXFILE); + const struct FileDirEntry *file = filelist_file(sfile->files, index); + if (file && file->relpath && file->relpath[0] && !(file->typeflag & FILE_TYPE_FOLDER)) { + BLI_strncpy(sfile->params->file, file->relpath, FILE_MAXFILE); } } @@ -446,37 +462,20 @@ float file_font_pointsize(void) #endif } -static void column_widths(struct FileList *files, struct FileLayout *layout) +static void column_widths(FileSelectParams *params, struct FileLayout *layout) { int i; - int numfiles = filelist_numfiles(files); + const bool small_size = SMALL_SIZE_CHECK(params->thumbnail_size); for (i = 0; i < MAX_FILE_COLUMN; ++i) { layout->column_widths[i] = 0; } - for (i = 0; (i < numfiles); ++i) { - struct direntry *file = filelist_file(files, i); - if (file) { - float len; - len = file_string_width(file->relname); - if (len > layout->column_widths[COLUMN_NAME]) layout->column_widths[COLUMN_NAME] = len; - len = file_string_width(file->date); - if (len > layout->column_widths[COLUMN_DATE]) layout->column_widths[COLUMN_DATE] = len; - len = file_string_width(file->time); - if (len > layout->column_widths[COLUMN_TIME]) layout->column_widths[COLUMN_TIME] = len; - len = file_string_width(file->size); - if (len > layout->column_widths[COLUMN_SIZE]) layout->column_widths[COLUMN_SIZE] = len; - len = file_string_width(file->mode1); - if (len > layout->column_widths[COLUMN_MODE1]) layout->column_widths[COLUMN_MODE1] = len; - len = file_string_width(file->mode2); - if (len > layout->column_widths[COLUMN_MODE2]) layout->column_widths[COLUMN_MODE2] = len; - len = file_string_width(file->mode3); - if (len > layout->column_widths[COLUMN_MODE3]) layout->column_widths[COLUMN_MODE3] = len; - len = file_string_width(file->owner); - if (len > layout->column_widths[COLUMN_OWNER]) layout->column_widths[COLUMN_OWNER] = len; - } - } + layout->column_widths[COLUMN_NAME] = ((float)params->thumbnail_size / 8.0f) * UI_UNIT_X;; + /* Biggest possible reasonable values... */ + layout->column_widths[COLUMN_DATE] = file_string_width(small_size ? "23/08/89" : "23-Dec-89"); + layout->column_widths[COLUMN_TIME] = file_string_width("23:59"); + layout->column_widths[COLUMN_SIZE] = file_string_width(small_size ? "98.7 M" : "98.7 MiB"); } void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *ar) @@ -496,7 +495,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *ar) return; } - numfiles = filelist_numfiles(sfile->files); + numfiles = filelist_files_ensure(sfile->files); textheight = (int)file_font_pointsize(); layout = sfile->layout; layout->textheight = textheight; @@ -535,7 +534,7 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *ar) layout->height = (int)(BLI_rctf_size_y(&v2d->cur) - 2 * layout->tile_border_y); layout->rows = layout->height / (layout->tile_h + 2 * layout->tile_border_y); - column_widths(sfile->files, layout); + column_widths(params, layout); if (params->display == FILE_SHORTDISPLAY) { maxlen = ICON_DEFAULT_WIDTH_SCALE + column_icon_space + @@ -545,12 +544,6 @@ void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *ar) else { maxlen = ICON_DEFAULT_WIDTH_SCALE + column_icon_space + (int)layout->column_widths[COLUMN_NAME] + column_space + -#ifndef WIN32 - (int)layout->column_widths[COLUMN_MODE1] + column_space + - (int)layout->column_widths[COLUMN_MODE2] + column_space + - (int)layout->column_widths[COLUMN_MODE3] + column_space + - (int)layout->column_widths[COLUMN_OWNER] + column_space + -#endif (int)layout->column_widths[COLUMN_DATE] + column_space + (int)layout->column_widths[COLUMN_TIME] + column_space + (int)layout->column_widths[COLUMN_SIZE] + column_space; @@ -581,9 +574,10 @@ void ED_file_change_dir(bContext *C, const bool checkdir) { wmWindowManager *wm = CTX_wm_manager(C); SpaceFile *sfile = CTX_wm_space_file(C); + ScrArea *sa = CTX_wm_area(C); if (sfile->params) { - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); /* Clear search string, it is very rare to want to keep that filter while changing dir, * and usually very annoying to keep it actually! */ @@ -610,8 +604,8 @@ int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matche int match = 0; int i; - struct direntry *file; - int n = filelist_numfiles(sfile->files); + FileDirEntry *file; + int n = filelist_files_ensure(sfile->files); /* select any file that matches the pattern, this includes exact match * if the user selects a single file by entering the filename @@ -619,10 +613,10 @@ int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matche for (i = 0; i < n; i++) { file = filelist_file(sfile->files, i); /* Do not check wether file is a file or dir here! Causes T44243 (we do accept dirs at this stage). */ - if (fnmatch(pattern, file->relname, 0) == 0) { - file->selflag |= FILE_SEL_SELECTED; + if (fnmatch(pattern, file->relpath, 0) == 0) { + filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL); if (!match) { - BLI_strncpy(matched_file, file->relname, FILE_MAX); + BLI_strncpy(matched_file, file->relpath, FILE_MAX); } match++; } @@ -687,14 +681,12 @@ int autocomplete_file(struct bContext *C, char *str, void *UNUSED(arg_v)) /* search if str matches the beginning of name */ if (str[0] && sfile->files) { AutoComplete *autocpl = UI_autocomplete_begin(str, FILE_MAX); - int nentries = filelist_numfiles(sfile->files); + int nentries = filelist_files_ensure(sfile->files); int i; for (i = 0; i < nentries; ++i) { - struct direntry *file = filelist_file(sfile->files, i); - if (file && (S_ISREG(file->type) || S_ISDIR(file->type))) { - UI_autocomplete_update_name(autocpl, file->relname); - } + FileDirEntry *file = filelist_file(sfile->files, i); + UI_autocomplete_update_name(autocpl, file->relpath); } match = UI_autocomplete_end(autocpl, str); } @@ -702,20 +694,20 @@ int autocomplete_file(struct bContext *C, char *str, void *UNUSED(arg_v)) return match; } -void ED_fileselect_clear(struct wmWindowManager *wm, struct SpaceFile *sfile) +void ED_fileselect_clear(wmWindowManager *wm, ScrArea *sa, SpaceFile *sfile) { /* only NULL in rare cases - [#29734] */ if (sfile->files) { - thumbnails_stop(wm, sfile->files); + filelist_readjob_stop(wm, sa); filelist_freelib(sfile->files); - filelist_free(sfile->files); + filelist_clear(sfile->files); } sfile->params->highlight_file = -1; WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_LIST, NULL); } -void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile) +void ED_fileselect_exit(wmWindowManager *wm, ScrArea *sa, SpaceFile *sfile) { if (!sfile) return; if (sfile->op) { @@ -727,7 +719,8 @@ void ED_fileselect_exit(struct wmWindowManager *wm, struct SpaceFile *sfile) folderlist_free(sfile->folders_next); if (sfile->files) { - ED_fileselect_clear(wm, sfile); + ED_fileselect_clear(wm, sa, sfile); + filelist_free(sfile->files); MEM_freeN(sfile->files); sfile->files = NULL; } diff --git a/source/blender/editors/space_file/fsmenu.c b/source/blender/editors/space_file/fsmenu.c index fdf7b458865..33e6990b179 100644 --- a/source/blender/editors/space_file/fsmenu.c +++ b/source/blender/editors/space_file/fsmenu.c @@ -292,6 +292,30 @@ void fsmenu_insert_entry(struct FSMenu *fsmenu, FSMenuCategory category, const c fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme"); fsm_iter->path = BLI_strdup(path); fsm_iter->save = (flag & FS_INSERT_SAVE) != 0; + + if ((category == FS_CATEGORY_RECENT) && (!name || !name[0])) { + /* Special handling when adding new recent entry - check if dir exists in some other categories, + * and try to use name from there if so. */ + FSMenuCategory cats[] = {FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS}; + int i = ARRAY_SIZE(cats); + + while (i--) { + FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]); + + for (; tfsm; tfsm = tfsm->next) { + if (STREQ(tfsm->path, fsm_iter->path)) { + if (tfsm->name[0]) { + name = tfsm->name; + } + break; + } + } + if (tfsm) { + break; + } + } + } + if (name && name[0]) { BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name)); } @@ -439,19 +463,36 @@ void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks) { wchar_t wline[FILE_MAXDIR]; __int64 tmp; - char tmps[4]; + char tmps[4], *name; int i; - + tmp = GetLogicalDrives(); - + for (i = 0; i < 26; i++) { if ((tmp >> i) & 1) { tmps[0] = 'A' + i; tmps[1] = ':'; tmps[2] = '\\'; - tmps[3] = 0; - - fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, NULL, FS_INSERT_SORTED); + tmps[3] = '\0'; + name = NULL; + + /* Flee from horrible win querying hover floppy drives! */ + if (i > 1) { + /* Try to get volume label as well... */ + BLI_strncpy_wchar_from_utf8(wline, tmps, 4); + if (GetVolumeInformationW(wline, wline + 4, FILE_MAXDIR - 4, NULL, NULL, NULL, NULL, 0)) { + size_t label_len; + + BLI_strncpy_wchar_as_utf8(line, wline + 4, FILE_MAXDIR - 4); + + label_len = MIN2(strlen(line), FILE_MAXDIR - 6); + BLI_snprintf(line + label_len, 6, " (%.2s)", tmps); + + name = line; + } + } + + fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, name, FS_INSERT_SORTED); } } diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index a8b71238e14..97c2d75e469 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -117,6 +117,8 @@ static void file_free(SpaceLink *sl) { SpaceFile *sfile = (SpaceFile *) sl; + BLI_assert(sfile->previews_timer == NULL); + if (sfile->files) { // XXXXX would need to do thumbnails_stop here, but no context available filelist_freelib(sfile->files); @@ -170,7 +172,12 @@ static void file_exit(wmWindowManager *wm, ScrArea *sa) { SpaceFile *sfile = (SpaceFile *)sa->spacedata.first; - ED_fileselect_exit(wm, sfile); + if (sfile->previews_timer) { + WM_event_remove_timer_notifier(wm, NULL, sfile->previews_timer); + sfile->previews_timer = NULL; + } + + ED_fileselect_exit(wm, sa, sfile); } static SpaceLink *file_duplicate(SpaceLink *sl) @@ -211,13 +218,15 @@ static void file_refresh(const bContext *C, ScrArea *sa) } if (!sfile->files) { sfile->files = filelist_new(params->type); - filelist_setdir(sfile->files, params->dir); params->highlight_file = -1; /* added this so it opens nicer (ton) */ } + filelist_setdir(sfile->files, params->dir); + filelist_setrecursion(sfile->files, params->recursion_level); filelist_setsorting(sfile->files, params->sort); - filelist_setfilter_options(sfile->files, params->flag & FILE_HIDE_DOT, + filelist_setfilter_options(sfile->files, (params->flag & FILE_HIDE_DOT) != 0, false, /* TODO hide_parent, should be controllable? */ params->flag & FILE_FILTER ? params->filter : 0, + params->filter_id, params->filter_glob, params->filter_search); @@ -227,39 +236,45 @@ static void file_refresh(const bContext *C, ScrArea *sa) sfile->bookmarknr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_BOOKMARKS, params->dir); sfile->recentnr = fsmenu_get_active_indices(fsmenu, FS_CATEGORY_RECENT, params->dir); - if (filelist_empty(sfile->files)) { - thumbnails_stop(wm, sfile->files); - filelist_readdir(sfile->files); - filelist_sort(sfile->files); - BLI_strncpy(params->dir, filelist_dir(sfile->files), FILE_MAX); - } - else if (filelist_need_sorting(sfile->files)) { - thumbnails_stop(wm, sfile->files); - filelist_sort(sfile->files); + if (filelist_force_reset(sfile->files)) { + filelist_readjob_stop(wm, sa); + filelist_clear(sfile->files); } - if ((params->display == FILE_IMGDISPLAY) && filelist_need_thumbnails(sfile->files)) { - if (!thumbnails_running(wm, sfile->files)) { - thumbnails_start(sfile->files, C); + if (filelist_empty(sfile->files)) { + if (!filelist_pending(sfile->files)) { + filelist_readjob_start(sfile->files, C); } } - else { - /* stop any running thumbnail jobs if we're not displaying them - speedup for NFS */ - thumbnails_stop(wm, sfile->files); - } + filelist_sort(sfile->files); filelist_filter(sfile->files); + if (params->display == FILE_IMGDISPLAY) { + filelist_cache_previews_set(sfile->files, true); + } + else { + filelist_cache_previews_set(sfile->files, false); + if (sfile->previews_timer) { + WM_event_remove_timer_notifier(wm, CTX_wm_window(C), sfile->previews_timer); + sfile->previews_timer = NULL; + } + } + if (params->renamefile[0] != '\0') { - int idx = filelist_find(sfile->files, params->renamefile); + int idx = filelist_file_findpath(sfile->files, params->renamefile); if (idx >= 0) { - struct direntry *file = filelist_file(sfile->files, idx); + FileDirEntry *file = filelist_file(sfile->files, idx); if (file) { - file->selflag |= FILE_SEL_EDITING; + filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL); } } BLI_strncpy(sfile->params->renameedit, sfile->params->renamefile, sizeof(sfile->params->renameedit)); - params->renamefile[0] = '\0'; + /* File listing is now async, do not clear renamefile if matching entry not found + * and dirlist is not finished! */ + if (idx >= 0 || filelist_is_ready(sfile->files)) { + params->renamefile[0] = '\0'; + } } if (sfile->layout) { @@ -278,7 +293,7 @@ static void file_refresh(const bContext *C, ScrArea *sa) static void file_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *wmn) { - /* SpaceFile *sfile = (SpaceFile *)sa->spacedata.first; */ + SpaceFile *sfile = (SpaceFile *)sa->spacedata.first; /* context changes */ switch (wmn->category) { @@ -292,6 +307,12 @@ static void file_listener(bScreen *UNUSED(sc), ScrArea *sa, wmNotifier *wmn) ED_area_tag_refresh(sa); ED_area_tag_redraw(sa); break; + case ND_SPACE_FILE_PREVIEW: + if (sfile->files && filelist_cache_previews_update(sfile->files)) { + ED_area_tag_refresh(sa); + ED_area_tag_redraw(sa); + } + break; } break; } |