UI: show recently selected items at the top of searches #110828

Merged
Jacques Lucke merged 24 commits from JacquesLucke/blender:recent-searches into main 2023-09-25 10:56:20 +02:00
24 changed files with 227 additions and 33 deletions

View File

@ -216,6 +216,11 @@ class USERPREF_PT_interface_display(InterfacePanel, CenterAlignMixIn, Panel):
sub.active = view.show_tooltips
sub.prop(view, "show_tooltips_python")
col.separator()
col = layout.column(heading="Search", align=True)
col.prop(prefs, "use_recent_searches", text="Sort by Most Recent")
class USERPREF_PT_interface_text(InterfacePanel, CenterAlignMixIn, Panel):
bl_label = "Text Rendering"

View File

@ -188,6 +188,7 @@ enum {
#define BLENDER_QUIT_FILE "quit.blend"
#define BLENDER_BOOKMARK_FILE "bookmarks.txt"
#define BLENDER_HISTORY_FILE "recent-files.txt"
#define BLENDER_RECENT_SEARCHES_FILE "recent-searches.txt"
#define BLENDER_PLATFORM_SUPPORT_FILE "platform_support.txt"
#ifdef __cplusplus

View File

@ -5,6 +5,7 @@
#pragma once
#include "BLI_linear_allocator.hh"
#include "BLI_map.hh"
#include "BLI_span.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
@ -12,10 +13,23 @@
namespace blender::string_search {
struct SearchItem {
void *user_data;
Span<blender::StringRef> normalized_words;
int length;
void *user_data;
int weight;
/**
JacquesLucke marked this conversation as resolved Outdated

Worth noting what units this is in & if it can ever be negative.

Worth noting what units this is in & if it can ever be negative.
* This is a logical time stamp, i.e. the greater it is, the more recent the item was used. The
* number is not based on an actual clock.
*/
int recent_time;
};
JacquesLucke marked this conversation as resolved Outdated

the more recent -> the more recently

`the more recent` -> `the more recently`
struct RecentCache {
/**
* Stores a logical time stamp for each previously choosen search item. The higher the time
* stamp, the more recently the item has been selected.
*/
Map<std::string, int> logical_time_by_str;
};
/**
@ -25,6 +39,7 @@ class StringSearchBase {
protected:
LinearAllocator<> allocator_;
Vector<SearchItem> items_;
const RecentCache *recent_cache_ = nullptr;
protected:
void add_impl(StringRef str, void *user_data, int weight);
@ -43,6 +58,11 @@ class StringSearchBase {
*/
template<typename T> class StringSearch : private StringSearchBase {
public:
StringSearch(const RecentCache *recent_cache = nullptr)
{
this->recent_cache_ = recent_cache;
}
/**
* Add a new possible result to the search.
*

View File

@ -398,8 +398,14 @@ void StringSearchBase::add_impl(const StringRef str, void *user_data, const int
{
Vector<StringRef, 64> words;
string_search::extract_normalized_words(str, allocator_, words);
items_.append(
{allocator_.construct_array_copy(words.as_span()), int(str.size()), user_data, weight});
const int recent_time = recent_cache_ ?
recent_cache_->logical_time_by_str.lookup_default(str, -1) :
-1;
items_.append({user_data,
allocator_.construct_array_copy(words.as_span()),
int(str.size()),
weight,
recent_time});
}
Vector<void *> StringSearchBase::query_impl(const StringRef query) const
@ -429,17 +435,23 @@ Vector<void *> StringSearchBase::query_impl(const StringRef query) const
Vector<int> sorted_result_indices;
for (const int score : found_scores) {
MutableSpan<int> indices = result_indices_by_score.lookup(score);
if (score == found_scores[0] && !query.is_empty()) {
/* Sort items with best score by length. Shorter items are more likely the ones you are
* looking for. This also ensures that exact matches will be at the top, even if the query is
* a sub-string of another item. */
std::sort(indices.begin(), indices.end(), [&](int a, int b) {
return items_[a].length < items_[b].length;
});
/* Prefer items with larger weights. Use `stable_sort` so that if the weights are the same,
* the order won't be changed. */
if (score == found_scores[0]) {
if (!query.is_empty()) {
/* Sort items with best score by length. Shorter items are more likely the ones you are
* looking for. This also ensures that exact matches will be at the top, even if the query
* is a sub-string of another item. */
std::sort(indices.begin(), indices.end(), [&](int a, int b) {
return items_[a].length < items_[b].length;
});
/* Prefer items with larger weights. Use `stable_sort` so that if the weights are the same,
* the order won't be changed. */
std::stable_sort(indices.begin(), indices.end(), [&](int a, int b) {
return items_[a].weight > items_[b].weight;
});
}
/* Prefer items that have been selected recently. */
std::stable_sort(indices.begin(), indices.end(), [&](int a, int b) {
return items_[a].weight > items_[b].weight;
return items_[a].recent_time > items_[b].recent_time;
});
}
sorted_result_indices.extend(indices);

View File

@ -461,7 +461,7 @@ void blo_do_versions_userdef(UserDef *userdef)
if (!USER_VERSION_ATLEAST(278, 6)) {
/* Clear preference flags for re-use. */
userdef->flag &= ~(USER_FLAG_NUMINPUT_ADVANCED | USER_FLAG_UNUSED_2 | USER_FLAG_UNUSED_3 |
userdef->flag &= ~(USER_FLAG_NUMINPUT_ADVANCED | (1 << 2) | USER_FLAG_UNUSED_3 |
USER_FLAG_UNUSED_6 | USER_FLAG_UNUSED_7 | USER_FLAG_UNUSED_9 |
USER_DEVELOPER_UI);
userdef->uiflag &= ~(USER_HEADER_BOTTOM);

View File

@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_string_search.hh"
namespace blender::ui::string_search {
/**
* Remember the string that the user chose. This allows us to put it higher up in the search items
* later on.
*/
void add_recent_search(StringRef chosen_str);
/**
* Depending on the user preferences, either outputs the recent cache or null.
*/
const blender::string_search::RecentCache *get_recent_cache_or_null();
void write_recent_searches_file();
void read_recent_searches_file();
/**
* Wrapper for the lower level #StringSearch in blenlib that takes recent searches into account
* automatically.
*/
template<typename T> class StringSearch : public blender::string_search::StringSearch<T> {
public:
StringSearch() : blender::string_search::StringSearch<T>(get_recent_cache_or_null()) {}
};
} // namespace blender::ui::string_search

View File

@ -64,6 +64,7 @@ set(SRC
interface_region_search.cc
interface_region_tooltip.cc
interface_regions.cc
interface_string_search.cc
interface_style.cc
interface_template_asset_view.cc
interface_template_attribute_search.cc

View File

@ -25,7 +25,6 @@
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_string_utf8.h"
#include "BLI_vector.hh"
@ -52,6 +51,7 @@
#include "UI_interface.hh"
#include "UI_interface_icons.hh"
#include "UI_string_search.hh"
#include "UI_view2d.hh"
#include "IMB_imbuf.h"
@ -3444,6 +3444,7 @@ static void ui_but_free_type_specific(uiBut *but)
switch (but->type) {
case UI_BTYPE_SEARCH_MENU: {
uiButSearch *search_but = (uiButSearch *)but;
MEM_SAFE_FREE(search_but->item_active_str);
if (search_but->arg_free_fn) {
search_but->arg_free_fn(search_but->arg);
@ -6400,7 +6401,7 @@ static void operator_enum_search_update_fn(
const EnumPropertyItem *all_items;
RNA_property_enum_items_gettexted((bContext *)C, ptr, prop, &all_items, nullptr, &do_free);
blender::string_search::StringSearch<const EnumPropertyItem> search;
blender::ui::string_search::StringSearch<const EnumPropertyItem> search;
for (const EnumPropertyItem *item = all_items; item->identifier; item++) {
search.add(item->name, item);

View File

@ -56,6 +56,7 @@
#include "ED_undo.hh"
#include "UI_interface.hh"
#include "UI_string_search.hh"
#include "UI_view2d.hh"
#include "BLF_api.h"
@ -1252,6 +1253,8 @@ static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
if ((but->func_arg2 == nullptr) && (but->type == UI_BTYPE_SEARCH_MENU)) {
uiButSearch *search_but = (uiButSearch *)but;
but->func_arg2 = search_but->item_active;
blender::ui::string_search::add_recent_search(search_but->item_active_str);
}
ui_apply_but_func(C, but);

View File

@ -329,6 +329,7 @@ struct uiButSearch : public uiBut {
uiButSearchListenFn listen_fn = nullptr;
void *item_active = nullptr;
char *item_active_str;
void *arg = nullptr;
uiFreeArgFunc arg_free_fn = nullptr;

View File

@ -301,6 +301,8 @@ bool ui_searchbox_apply(uiBut *but, ARegion *region)
}
search_but->item_active = data->items.pointers[data->active];
MEM_SAFE_FREE(search_but->item_active_str);
search_but->item_active_str = BLI_strdup(data->items.names[data->active]);
return true;
}

View File

@ -0,0 +1,103 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include <sstream>
#include "BKE_appdir.h"
#include "DNA_userdef_types.h"
#include "UI_string_search.hh"
#include "BLI_fileops.hh"
#include "BLI_map.hh"
#include "BLI_path_util.h"
namespace blender::ui::string_search {
using blender::string_search::RecentCache;
struct RecentCacheStorage {
/**
* Is incremented every time a search item has been selected. This is used to keep track of the
* order of recent searches.
*/
int logical_clock = 0;
RecentCache cache;
};
static RecentCacheStorage &get_recent_cache_storage()
{
static RecentCacheStorage storage;
return storage;
}
JacquesLucke marked this conversation as resolved Outdated

Spelling: choosen -> chosen. Same in the header.

Spelling: `choosen` -> `chosen`. Same in the header.
void add_recent_search(const StringRef chosen_str)
{
RecentCacheStorage &storage = get_recent_cache_storage();
storage.cache.logical_time_by_str.add_overwrite(chosen_str, storage.logical_clock);
storage.logical_clock++;
}
const RecentCache *get_recent_cache_or_null()
{
if (U.flag & USER_FLAG_RECENT_SEARCHES_DISABLE) {
return nullptr;
}
RecentCacheStorage &storage = get_recent_cache_storage();
return &storage.cache;
}
static std::optional<std::string> get_recent_searches_file_path()
{
const char *user_config_dir = BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, nullptr);
if (!user_config_dir) {
return std::nullopt;
}
char filepath[FILE_MAX];
BLI_path_join(filepath, sizeof(filepath), user_config_dir, BLENDER_RECENT_SEARCHES_FILE);
return std::string(filepath);
}
void write_recent_searches_file()
{
const std::optional<std::string> path = get_recent_searches_file_path();
if (!path) {
return;
}
const RecentCacheStorage &storage = get_recent_cache_storage();
Vector<std::pair<int, std::string>> values;
for (const auto item : storage.cache.logical_time_by_str.items()) {
values.append({item.value, item.key});
}
std::sort(values.begin(), values.end());
fstream file(*path, std::ios::out);
for (const auto &item : values) {
file.write(item.second.data(), item.second.size());
file.write("\n", 1);
}
}
void read_recent_searches_file()
{
const std::optional<std::string> path = get_recent_searches_file_path();
if (!path) {
return;
}
RecentCacheStorage &storage = get_recent_cache_storage();
storage.logical_clock = 0;
storage.cache.logical_time_by_str.clear();
fstream file(*path);
std::string line;
while (std::getline(file, line)) {
storage.cache.logical_time_by_str.add_overwrite(line, storage.logical_clock);
storage.logical_clock++;
}
}
} // namespace blender::ui::string_search

View File

@ -7,7 +7,6 @@
*/
#include "BLI_string_ref.hh"
#include "BLI_string_search.hh"
#include "DNA_customdata_types.h"
@ -22,6 +21,7 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_string_search.hh"
using blender::nodes::geo_eval_log::GeometryAttributeInfo;
@ -91,7 +91,7 @@ void attribute_search_add_items(StringRefNull str,
* so the items are in the same order they will appear in while searching. */
const char *string = is_first ? "" : str.c_str();
string_search::StringSearch<const GeometryAttributeInfo> search;
ui::string_search::StringSearch<const GeometryAttributeInfo> search;
for (const GeometryAttributeInfo *item : infos) {
if (!bke::allow_procedural_attribute_access(item->name)) {
continue;

View File

@ -31,7 +31,6 @@
#include "BLI_set.hh"
#include "BLI_stack.hh"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_string_utils.h"
#include "BLI_utildefines.h"
@ -50,6 +49,7 @@
#include "WM_types.hh"
#include "UI_interface.hh"
#include "UI_string_search.hh"
#include "interface_intern.hh"
/* For key-map item access. */
@ -1002,7 +1002,7 @@ static void menu_search_update_fn(const bContext * /*C*/,
{
MenuSearch_Data *data = (MenuSearch_Data *)arg;
blender::string_search::StringSearch<MenuSearch_Item> search;
blender::ui::string_search::StringSearch<MenuSearch_Item> search;
LISTBASE_FOREACH (MenuSearch_Item *, item, &data->items) {
search.add(item->drawwstr_full, item);

View File

@ -34,7 +34,6 @@
#include "BLI_path_util.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_string_utils.h"
#include "BLI_timecode.h"
#include "BLI_utildefines.h"
@ -90,6 +89,7 @@
#include "UI_interface.hh"
#include "UI_interface_icons.hh"
#include "UI_string_search.hh"
#include "UI_view2d.hh"
#include "interface_intern.hh"
@ -423,7 +423,7 @@ static void id_search_cb(const bContext *C,
ListBase *lb = template_ui->idlb;
const int flag = RNA_property_flag(template_ui->prop);
blender::string_search::StringSearch<ID> search;
blender::ui::string_search::StringSearch<ID> search;
/* ID listbase */
LISTBASE_FOREACH (ID *, id, lb) {

View File

@ -18,7 +18,6 @@
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
@ -37,6 +36,7 @@
#include "UI_interface.hh"
#include "UI_interface_icons.hh"
#include "UI_resources.hh"
#include "UI_string_search.hh"
#include "UI_view2d.hh"
#include "WM_api.hh"
@ -519,7 +519,7 @@ void ui_rna_collection_search_update_fn(
char *name;
bool has_id_icon = false;
blender::string_search::StringSearch<CollItemSearch> search;
blender::ui::string_search::StringSearch<CollItemSearch> search;
if (data->search_prop != nullptr) {
/* build a temporary list of relevant items first */

View File

@ -5,7 +5,6 @@
#include "AS_asset_representation.hh"
#include "BLI_listbase.h"
#include "BLI_string_search.hh"
#include "DNA_space_types.h"
@ -17,6 +16,8 @@
#include "BKE_node_tree_update.h"
#include "BKE_screen.h"
#include "UI_string_search.hh"
#include "NOD_socket.hh"
#include "NOD_socket_search_link.hh"
@ -359,7 +360,7 @@ static void link_drag_search_update_fn(
storage.update_items_tag = false;
}
string_search::StringSearch<SocketLinkOperation> search;
ui::string_search::StringSearch<SocketLinkOperation> search;
for (SocketLinkOperation &op : storage.search_link_ops) {
search.add(op.name, &op, op.weight);

View File

@ -9,7 +9,6 @@
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_string_search.hh"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"

View File

@ -16,7 +16,6 @@
#include "BLI_listbase.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
@ -41,6 +40,7 @@
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_string_search.hh"
#include "UI_view2d.hh"
#include "DEG_depsgraph.hh"
@ -1346,7 +1346,7 @@ static void node_find_update_fn(const bContext *C,
{
SpaceNode *snode = CTX_wm_space_node(C);
string_search::StringSearch<bNode> search;
ui::string_search::StringSearch<bNode> search;
for (bNode *node : snode->edittree->all_nodes()) {
char name[256];

View File

@ -104,6 +104,7 @@ set(SRC
../include/UI_interface_c.hh
../include/UI_interface_icons.hh
../include/UI_resources.hh
../include/UI_string_search.hh
../include/UI_tree_view.hh
../include/UI_view2d.hh
)

View File

@ -1084,9 +1084,9 @@ typedef enum eUserPref_SpaceData_Flag {
typedef enum eUserPref_Flag {
USER_AUTOSAVE = (1 << 0),
USER_FLAG_NUMINPUT_ADVANCED = (1 << 1),
USER_FLAG_UNUSED_2 = (1 << 2), /* cleared */
USER_FLAG_UNUSED_3 = (1 << 3), /* cleared */
USER_FLAG_UNUSED_4 = (1 << 4), /* cleared */
USER_FLAG_RECENT_SEARCHES_DISABLE = (1 << 2), /* cleared */
USER_FLAG_UNUSED_3 = (1 << 3), /* cleared */
USER_FLAG_UNUSED_4 = (1 << 4), /* cleared */
USER_TRACKBALL = (1 << 5),
USER_FLAG_UNUSED_6 = (1 << 6), /* cleared */
USER_FLAG_UNUSED_7 = (1 << 7), /* cleared */

View File

@ -7143,6 +7143,10 @@ void RNA_def_userdef(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Auto-Execution Paths", "");
rna_def_userdef_autoexec_path_collection(brna, prop);
prop = RNA_def_property(srna, "use_recent_searches", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, nullptr, "flag", USER_FLAG_RECENT_SEARCHES_DISABLE);
RNA_def_property_ui_text(prop, "Recent Searches", "Sort the recently searched items at the top");
/* nested structs */
prop = RNA_def_property(srna, "view", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);

View File

@ -20,7 +20,6 @@
#include "BLI_path_util.h"
#include "BLI_set.hh"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLI_utildefines.h"
#include "DNA_collection_types.h"

View File

@ -111,8 +111,10 @@
#include "BLF_api.h"
#include "BLT_lang.h"
#include "UI_interface.hh"
#include "UI_resources.hh"
#include "UI_string_search.hh"
#include "GPU_context.h"
#include "GPU_init_exit.h"
@ -357,6 +359,7 @@ void WM_init(bContext *C, int argc, const char **argv)
ED_render_clear_mtex_copybuf();
wm_history_file_read();
blender::ui::string_search::read_recent_searches_file();
STRNCPY(G.lib, BKE_main_blendfile_path_from_global());
@ -539,6 +542,10 @@ void WM_exit_ex(bContext *C, const bool do_python_exit, const bool do_user_exit_
ED_screen_exit(C, win, WM_window_get_active_screen(win));
}
if (!G.background) {
blender::ui::string_search::write_recent_searches_file();
}
if (do_user_exit_actions) {
if ((U.pref_flag & USER_PREF_FLAG_SAVE) && ((G.f & G_FLAG_USERPREF_NO_SAVE_ON_EXIT) == 0)) {
if (U.runtime.is_dirty) {