UI: Dynamic File Browser Tooltips #104547

Merged
Harley Acheson merged 27 commits from Harley/blender:FileTooltips into main 2024-01-03 03:56:23 +01:00
5 changed files with 288 additions and 7 deletions

View File

@ -534,6 +534,14 @@ void BLO_sanitize_experimental_features_userpref_blend(struct UserDef *userdef);
*/
struct BlendThumbnail *BLO_thumbnail_from_file(const char *filepath);
/**
* Does a very light reading of given .blend file to extract its version.
*
* \param filepath: The path of the blend file to extract version from.
* \return The file version
*/
short BLO_version_from_file(const char *filepath);
/** Default theme, see: `release/datafiles/userdef/userdef_default_theme.c`. */
extern const struct bTheme U_theme_default;
/** Default preferences, defined by: `release/datafiles/userdef/userdef_default.c`. */

View File

@ -1412,6 +1412,17 @@ BlendThumbnail *BLO_thumbnail_from_file(const char *filepath)
return data;
}
short BLO_version_from_file(const char *filepath)
{
short version = 0;
FileData *fd = blo_filedata_from_file_minimal(filepath);
if (fd) {
version = fd->fileversion;
blo_filedata_free(fd);
}
return version;
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -9,6 +9,7 @@
#include <cerrno>
#include <cmath>
#include <cstring>
#include <string>
#include "MEM_guardedalloc.h"
@ -29,11 +30,16 @@
#include "BKE_context.hh"
#include "BKE_report.h"
#include "BLO_readfile.h"
#include "BLT_translation.h"
#include "BLF_api.h"
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "IMB_metadata.h"
#include "IMB_thumbs.h"
#include "DNA_userdef_types.h"
#include "DNA_windowmanager_types.h"
@ -108,11 +114,227 @@ void ED_file_path_button(bScreen *screen,
UI_block_func_set(block, nullptr, nullptr, nullptr);
}
/* Dummy helper - we need dynamic tooltips here. */
static char *file_draw_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
struct file_tooltip_data {
Harley marked this conversation as resolved Outdated
struct FileTooltipData {
  const SpaceFile *sfile;
  const FileDirEntry *file;
};
```c++ struct FileTooltipData { const SpaceFile *sfile; const FileDirEntry *file; }; ```
const SpaceFile *sfile;
const FileDirEntry *file;
};
static file_tooltip_data *file_tooltip_data_create(const SpaceFile *sfile,
const FileDirEntry *file)
{
char *dyn_tooltip = static_cast<char *>(argN);
return BLI_strdup(dyn_tooltip);
file_tooltip_data *data = (file_tooltip_data *)MEM_mallocN(sizeof(file_tooltip_data),
"tooltip_data");
data->sfile = sfile;
data->file = file;
return data;
}
static void file_draw_tooltip_custom_func(bContext * /*C*/, struct uiTooltipData *tip, void *argN)
{
file_tooltip_data *file_data = static_cast<file_tooltip_data *>(argN);
const SpaceFile *sfile = file_data->sfile;
const FileList *files = sfile->files;
const FileSelectParams *params = ED_fileselect_get_active_params(sfile);
const FileDirEntry *file = file_data->file;
BLI_assert_msg(!file->asset, "Asset tooltip should never be overridden here.");
Harley marked this conversation as resolved Outdated

I'd rather assert here, since when this is called, it would mean any asset tooltip callback was overridden by a file one.

I'd rather assert here, since when this is called, it would mean any asset tooltip callback was overridden by a file one.
/* Check the FileDirEntry first to see if the preview is already loaded. */
ImBuf *thumb = filelist_file_getimage(file);
/* Only free if it is loaded later. */
bool free_imbuf = (thumb == nullptr);
UI_tooltip_text_field_add(
tip, BLI_strdup(file->name), nullptr, UI_TIP_STYLE_HEADER, UI_TIP_LC_MAIN);
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
if (!(file->typeflag & FILE_TYPE_BLENDERLIB)) {
char full_path[FILE_MAX_LIBEXTRA];
filelist_file_get_full_path(files, file, full_path);
if (params->recursion_level > 0) {
char root[FILE_MAX];
BLI_path_split_dir_part(full_path, root, FILE_MAX);
UI_tooltip_text_field_add(
tip, BLI_strdup(root), nullptr, UI_TIP_STYLE_NORMAL, UI_TIP_LC_NORMAL);
}
if (file->redirection_path) {
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s: %s", N_("Link target"), file->redirection_path),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
}
if (file->attributes & FILE_ATTR_OFFLINE) {
UI_tooltip_text_field_add(tip,
BLI_strdup(N_("This file is offline")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_ALERT);
}
if (file->attributes & FILE_ATTR_READONLY) {
UI_tooltip_text_field_add(tip,
BLI_strdup(N_("This file is read-only")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_ALERT);
}
if (file->attributes & (FILE_ATTR_SYSTEM | FILE_ATTR_RESTRICTED)) {
UI_tooltip_text_field_add(tip,
BLI_strdup(N_("This is a restricted system file")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_ALERT);
}
if (file->typeflag & (FILE_TYPE_BLENDER | FILE_TYPE_BLENDER_BACKUP)) {

Why is this only using _read(), instead of _manage()?

Why is this only using `_read()`, instead of `_manage()`?

In this case I don't want to create a new preview if one doesn't exist. Getting the version from the file is faster.

In this case I don't want to create a new preview if one doesn't exist. Getting the version from the file is faster.
char version_st[128] = {0};
if (!thumb) {
/* Load the thumbnail from cache if existing, but don't create if not. */
thumb = IMB_thumb_read(full_path, THB_LARGE);
}
if (thumb) {
/* Look for version in existing thumbnail if available. */
Harley marked this conversation as resolved Outdated

It's sightly suboptimal to open a file as part of tooltip drawing. On a slow network driver this could lead to a noticeable delay in display the tooltip when quickly scanning across multiple files quickly.

It's sightly suboptimal to open a file as part of tooltip drawing. On a slow network driver this could lead to a noticeable delay in display the tooltip when quickly scanning across multiple files quickly.
IMB_metadata_get_field(
thumb->metadata, "Thumb::Blender::Version", version_st, sizeof(version_st));
}
if (!version_st[0] && !(file->attributes & FILE_ATTR_OFFLINE)) {
/* Load Blender version directly from the file. */
short version = BLO_version_from_file(full_path);
if (version != 0) {
SNPRINTF(version_st, "%d.%01d", version / 100, version % 100);
}
}
if (version_st[0]) {
UI_tooltip_text_field_add(tip,
BLI_sprintfN("Blender %s", version_st),
nullptr,
UI_TIP_STYLE_NORMAL,
Harley marked this conversation as resolved Outdated

IMB_thumb_manage() handles the case of offline files now, it won't attempt to generate those.

`IMB_thumb_manage()` handles the case of offline files now, it won't attempt to generate those.
UI_TIP_LC_NORMAL);
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
}
}
else if (file->typeflag & FILE_TYPE_IMAGE) {
if (!thumb) {
/* Load the thumbnail from cache if existing, create if not. */
thumb = IMB_thumb_manage(full_path, THB_LARGE, THB_SOURCE_IMAGE);
}
if (thumb) {
char value1[128];
char value2[128];
if (IMB_metadata_get_field(
thumb->metadata, "Thumb::Image::Width", value1, sizeof(value1)) &&
IMB_metadata_get_field(
thumb->metadata, "Thumb::Image::Height", value2, sizeof(value2)))
{
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s \u00D7 %s", value1, value2),
nullptr,
UI_TIP_STYLE_NORMAL,
Harley marked this conversation as resolved Outdated

Same here re offline file handling in IMB_thumb_manage().

Same here re offline file handling in `IMB_thumb_manage()`.
UI_TIP_LC_NORMAL);
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
}
}
}
else if (file->typeflag & FILE_TYPE_MOVIE) {
if (!thumb) {
/* This could possibly take a while. */
thumb = IMB_thumb_manage(full_path, THB_LARGE, THB_SOURCE_MOVIE);
}
if (thumb) {
char value1[128];
char value2[128];
char value3[128];
if (IMB_metadata_get_field(
thumb->metadata, "Thumb::Video::Width", value1, sizeof(value1)) &&
IMB_metadata_get_field(
thumb->metadata, "Thumb::Video::Height", value2, sizeof(value2)))
{
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s \u00D7 %s", value1, value2),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
}
if (IMB_metadata_get_field(
thumb->metadata, "Thumb::Video::Frames", value1, sizeof(value1)) &&
IMB_metadata_get_field(thumb->metadata, "Thumb::Video::FPS", value2, sizeof(value2)) &&
IMB_metadata_get_field(
thumb->metadata, "Thumb::Video::Duration", value3, sizeof(value3)))
{
UI_tooltip_text_field_add(
tip,
BLI_sprintfN("%s %s @ %s %s", value1, N_("Frames"), value2, N_("FPS")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s %s", value3, N_("seconds")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
}
}
}
char date_st[FILELIST_DIRENTRY_DATE_LEN], time_st[FILELIST_DIRENTRY_TIME_LEN];
bool is_today, is_yesterday;
std::string day_string = ("");
BLI_filelist_entry_datetime_to_string(
NULL, file->time, false, time_st, date_st, &is_today, &is_yesterday);
if (is_today || is_yesterday) {
day_string = (is_today ? N_("Today") : N_("Yesterday")) + std::string(" ");
}
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s: %s%s%s",
N_("Modified"),
day_string.c_str(),
(is_today || is_yesterday) ? "" : date_st,
(is_today || is_yesterday) ? time_st : ""),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
if (!(file->typeflag & FILE_TYPE_DIR) && file->size > 0) {
char size[16];
BLI_filelist_entry_size_to_string(NULL, file->size, false, size);
if (file->size < 10000) {
char size_full[16];
BLI_str_format_uint64_grouped(size_full, file->size);
UI_tooltip_text_field_add(
tip,
BLI_sprintfN("%s: %s (%s %s)", N_("Size"), size, size_full, N_("bytes")),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
}
else {
UI_tooltip_text_field_add(tip,
BLI_sprintfN("%s: %s", N_("Size"), size),
nullptr,
UI_TIP_STYLE_NORMAL,
UI_TIP_LC_NORMAL);
}
}
}
if (thumb && params->display != FILE_IMGDISPLAY) {
float scale = (96.0f * UI_SCALE_FAC) / float(MAX2(thumb->x, thumb->y));
short size[2] = {short(float(thumb->x) * scale), short(float(thumb->y) * scale)};
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
UI_tooltip_text_field_add(tip, nullptr, nullptr, UI_TIP_STYLE_SPACER, UI_TIP_LC_NORMAL);
UI_tooltip_image_field_add(tip, thumb, size);
}
if (thumb && free_imbuf) {
IMB_freeImBuf(thumb);
}
}
static char *file_draw_asset_tooltip_func(bContext * /*C*/, void *argN, const char * /*tip*/)
@ -172,7 +394,7 @@ static void file_but_enable_drag(uiBut *but,
static uiBut *file_add_icon_but(const SpaceFile *sfile,
uiBlock *block,
const char *path,
const char * /*path*/,
const FileDirEntry *file,
const rcti *tile_draw_rect,
int icon,
@ -195,7 +417,8 @@ static uiBut *file_add_icon_but(const SpaceFile *sfile,
UI_but_func_tooltip_set(but, file_draw_asset_tooltip_func, file->asset, nullptr);
}
else {
UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path), MEM_freeN);
UI_but_func_tooltip_custom_set(
but, file_draw_tooltip_custom_func, file_tooltip_data_create(sfile, file), MEM_freeN);
}
return but;
@ -335,7 +558,8 @@ static void file_add_preview_drag_but(const SpaceFile *sfile,
UI_but_func_tooltip_set(but, file_draw_asset_tooltip_func, file->asset, nullptr);
}
else {
UI_but_func_tooltip_set(but, file_draw_tooltip_func, BLI_strdup(path), MEM_freeN);
UI_but_func_tooltip_custom_set(
but, file_draw_tooltip_custom_func, file_tooltip_data_create(sfile, file), MEM_freeN);
}
}

View File

@ -1549,6 +1549,30 @@ ImBuf *IMB_anim_previewframe(anim *anim)
IMB_freeImBuf(ibuf);
position = anim->duration_in_frames / 2;
ibuf = IMB_anim_absolute(anim, position, IMB_TC_NONE, IMB_PROXY_NONE);
char value[128];
IMB_metadata_ensure(&ibuf->metadata);
SNPRINTF(value, "%i", anim->x);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Width", value);
Harley marked this conversation as resolved Outdated

If this metadata is specifically for thumbnails, it could use Thumb::Image for all the fields?

If this metadata is specifically for thumbnails, it could use `Thumb::Image` for all the fields?
SNPRINTF(value, "%i", anim->y);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Height", value);
SNPRINTF(value, "%i", anim->duration_in_frames);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Frames", value);
#ifdef WITH_FFMPEG
if (anim->pFormatCtx && anim->curtype == ANIM_FFMPEG) {
AVStream *v_st = anim->pFormatCtx->streams[anim->videoStream];
AVRational frame_rate = av_guess_frame_rate(anim->pFormatCtx, v_st, NULL);
if (frame_rate.num != 0) {
double duration = anim->duration_in_frames / av_q2d(frame_rate);
SNPRINTF(value, "%g", av_q2d(frame_rate));
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::FPS", value);
SNPRINTF(value, "%g", duration);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Duration", value);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Video::Codec", anim->pCodec->long_name);
}
}
#endif
}
return ibuf;
}

View File

@ -92,6 +92,7 @@
#include "IMB_imbuf.h"
#include "IMB_imbuf_types.h"
#include "IMB_metadata.h"
#include "IMB_thumbs.h"
#include "ED_asset.hh"
@ -1722,6 +1723,12 @@ static ImBuf *blend_file_thumb_from_screenshot(bContext *C, BlendThumbnail **r_t
/* File-system thumbnail image can be 256x256. */
IMB_scaleImBuf(ibuf, ex * 2, ey * 2);
/* Save metadata for quick access. */
char version_st[10] = {0};
SNPRINTF(version_st, "%d.%01d", BLENDER_VERSION / 100, BLENDER_VERSION % 100);
IMB_metadata_ensure(&ibuf->metadata);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Blender::Version", version_st);
/* Thumbnail inside blend should be 128x128. */
ImBuf *thumb_ibuf = IMB_dupImBuf(ibuf);
IMB_scaleImBuf(thumb_ibuf, ex, ey);
@ -1831,6 +1838,13 @@ static ImBuf *blend_file_thumb_from_camera(const bContext *C,
/* dirty oversampling */
ImBuf *thumb_ibuf;
thumb_ibuf = IMB_dupImBuf(ibuf);
/* Save metadata for quick access. */
char version_st[10] = {0};
SNPRINTF(version_st, "%d.%01d", BLENDER_VERSION / 100, BLENDER_VERSION % 100);
IMB_metadata_ensure(&ibuf->metadata);
IMB_metadata_set_field(ibuf->metadata, "Thumb::Blender::Version", version_st);
/* BLEN_THUMB_SIZE is size of thumbnail inside blend file: 128x128. */
IMB_scaleImBuf(thumb_ibuf, BLEN_THUMB_SIZE, BLEN_THUMB_SIZE);
thumb = BKE_main_thumbnail_from_imbuf(nullptr, thumb_ibuf);