Files
test/source/blender/editors/interface/templates/interface_template_id.cc
Guillermo Venegas 858abf43c3 Refactor: UI: Replace uiItemFullR with class method uiLayout::prop
This converts the public `uiItemFullR` function to an object oriented
API (an overload of `uiLayout::prop`), matching the python API.
This reduces the difference between the C++ API with the python version,
its also helps while converting code from python to C++ code (or vice-versa),
making it almost seamless.

Part of: #117604

Pull Request: https://projects.blender.org/blender/blender/pulls/138683
2025-05-10 03:39:31 +02:00

1824 lines
63 KiB
C++

/* SPDX-FileCopyrightText: 2024 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edinterface
*/
#include "BKE_anim_data.hh"
#include "BKE_collection.hh"
#include "BKE_context.hh"
#include "BKE_idtype.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_lib_override.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_main_invariants.hh"
#include "BKE_packedFile.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_string_search.hh"
#include "BLT_translation.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_collection_types.h"
#include "DNA_scene_types.h"
#include "DNA_workspace_types.h"
#include "ED_id_management.hh"
#include "ED_node.hh"
#include "ED_object.hh"
#include "ED_undo.hh"
#include "RNA_access.hh"
#include "RNA_prototypes.hh"
#include "WM_api.hh"
#include "UI_interface.hh"
#include "UI_string_search.hh"
#include "interface_intern.hh"
#include "interface_templates_intern.hh"
using blender::StringRef;
using blender::StringRefNull;
struct TemplateID {
PointerRNA ptr = {};
PropertyRNA *prop = nullptr;
ListBase *idlb = nullptr;
short idcode = 0;
short filter = 0;
int prv_rows = 0;
int prv_cols = 0;
bool preview = false;
float scale = 0.0f;
};
/* Search browse menu, assign. */
static void template_ID_set_property_exec_fn(bContext *C, void *arg_template, void *item)
{
TemplateID *template_ui = (TemplateID *)arg_template;
/* ID */
if (item) {
PointerRNA idptr = RNA_id_pointer_create(static_cast<ID *>(item));
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr);
RNA_property_update(C, &template_ui->ptr, template_ui->prop);
}
}
static bool id_search_allows_id(TemplateID *template_ui, const int flag, ID *id, const char *query)
{
ID *id_from = template_ui->ptr.owner_id;
/* Do self check. */
if ((flag & PROP_ID_SELF_CHECK) && id == id_from) {
return false;
}
/* Use filter. */
if (RNA_property_type(template_ui->prop) == PROP_POINTER) {
PointerRNA ptr = RNA_id_pointer_create(id);
if (RNA_property_pointer_poll(&template_ui->ptr, template_ui->prop, &ptr) == 0) {
return false;
}
}
/* Hide dot prefixed data-blocks, but only if filter does not force them visible. */
if (U.uiflag & USER_HIDE_DOT) {
if ((id->name[2] == '.') && (query[0] != '.')) {
return false;
}
}
return true;
}
static bool id_search_add(const bContext *C, TemplateID *template_ui, uiSearchItems *items, ID *id)
{
/* +1 is needed because BKE_id_ui_prefix used 3 letter prefix
* followed by ID_NAME-2 characters from id->name
*/
char name_ui[MAX_ID_FULL_NAME_UI];
int iconid = ui_id_icon_get(C, id, template_ui->preview);
const bool use_lib_prefix = template_ui->preview || iconid;
const bool has_sep_char = ID_IS_LINKED(id);
/* When using previews, the library hint (linked, overridden, missing) is added with a
* character prefix, otherwise we can use a icon. */
int name_prefix_offset;
BKE_id_full_name_ui_prefix_get(name_ui, id, use_lib_prefix, UI_SEP_CHAR, &name_prefix_offset);
if (!use_lib_prefix) {
iconid = UI_icon_from_library(id);
}
if (!UI_search_item_add(items,
name_ui,
id,
iconid,
has_sep_char ? int(UI_BUT_HAS_SEP_CHAR) : 0,
name_prefix_offset))
{
return false;
}
return true;
}
/* ID Search browse menu, do the search */
static void id_search_cb(const bContext *C,
void *arg_template,
const char *str,
uiSearchItems *items,
const bool /*is_first*/)
{
TemplateID *template_ui = (TemplateID *)arg_template;
ListBase *lb = template_ui->idlb;
const int flag = RNA_property_flag(template_ui->prop);
blender::ui::string_search::StringSearch<ID> search;
/* ID listbase */
LISTBASE_FOREACH (ID *, id, lb) {
if (id_search_allows_id(template_ui, flag, id, str)) {
search.add(id->name + 2, id);
}
}
const blender::Vector<ID *> filtered_ids = search.query(str);
for (ID *id : filtered_ids) {
if (!id_search_add(C, template_ui, items, id)) {
break;
}
}
}
/**
* Use id tags for filtering.
*/
static void id_search_cb_tagged(const bContext *C,
void *arg_template,
const char *str,
uiSearchItems *items)
{
TemplateID *template_ui = (TemplateID *)arg_template;
ListBase *lb = template_ui->idlb;
const int flag = RNA_property_flag(template_ui->prop);
blender::string_search::StringSearch<ID> search{nullptr,
blender::string_search::MainWordsHeuristic::All};
/* ID listbase */
LISTBASE_FOREACH (ID *, id, lb) {
if (id->tag & ID_TAG_DOIT) {
if (id_search_allows_id(template_ui, flag, id, str)) {
search.add(id->name + 2, id);
}
id->tag &= ~ID_TAG_DOIT;
}
}
blender::Vector<ID *> filtered_ids = search.query(str);
for (ID *id : filtered_ids) {
if (!id_search_add(C, template_ui, items, id)) {
break;
}
}
}
/**
* A version of 'id_search_cb' that lists scene objects.
*/
static void id_search_cb_objects_from_scene(const bContext *C,
void *arg_template,
const char *str,
uiSearchItems *items,
const bool /*is_first*/)
{
TemplateID *template_ui = (TemplateID *)arg_template;
ListBase *lb = template_ui->idlb;
Scene *scene = nullptr;
ID *id_from = template_ui->ptr.owner_id;
if (id_from && GS(id_from->name) == ID_SCE) {
scene = (Scene *)id_from;
}
else {
scene = CTX_data_scene(C);
}
BKE_main_id_flag_listbase(lb, ID_TAG_DOIT, false);
FOREACH_SCENE_OBJECT_BEGIN (scene, ob_iter) {
ob_iter->id.tag |= ID_TAG_DOIT;
}
FOREACH_SCENE_OBJECT_END;
id_search_cb_tagged(C, arg_template, str, items);
}
static ARegion *template_ID_search_menu_item_tooltip(
bContext *C, ARegion *region, const rcti *item_rect, void * /*arg*/, void *active)
{
ID *active_id = static_cast<ID *>(active);
return UI_tooltip_create_from_search_item_generic(C, region, item_rect, active_id);
}
/* ID Search browse menu, open */
static uiBlock *id_search_menu(bContext *C, ARegion *region, void *arg_litem)
{
static TemplateID template_ui;
PointerRNA active_item_ptr;
void (*id_search_update_fn)(
const bContext *, void *, const char *, uiSearchItems *, const bool) = id_search_cb;
/* arg_litem is malloced, can be freed by parent button */
template_ui = *((TemplateID *)arg_litem);
active_item_ptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop);
if (template_ui.filter) {
/* Currently only used for objects. */
if (template_ui.idcode == ID_OB) {
if (template_ui.filter == UI_TEMPLATE_ID_FILTER_AVAILABLE) {
id_search_update_fn = id_search_cb_objects_from_scene;
}
}
}
return template_common_search_menu(C,
region,
id_search_update_fn,
&template_ui,
template_ID_set_property_exec_fn,
active_item_ptr.data,
template_ID_search_menu_item_tooltip,
template_ui.prv_rows,
template_ui.prv_cols,
template_ui.scale);
}
static void template_id_cb(bContext *C, void *arg_litem, void *arg_event);
void UI_context_active_but_prop_get_templateID(const bContext *C,
PointerRNA *r_ptr,
PropertyRNA **r_prop)
{
uiBut *but = UI_context_active_but_get(C);
*r_ptr = {};
*r_prop = nullptr;
if (but && (but->funcN == template_id_cb) && but->func_argN) {
TemplateID *template_ui = static_cast<TemplateID *>(but->func_argN);
*r_ptr = template_ui->ptr;
*r_prop = template_ui->prop;
}
}
static void template_id_liboverride_hierarchy_collection_root_find_recursive(
Collection *collection,
const int parent_level,
Collection **r_collection_parent_best,
int *r_parent_level_best)
{
if (!ID_IS_LINKED(collection) && !ID_IS_OVERRIDE_LIBRARY_REAL(collection)) {
return;
}
if (ID_IS_OVERRIDABLE_LIBRARY(collection) || ID_IS_OVERRIDE_LIBRARY_REAL(collection)) {
if (parent_level > *r_parent_level_best) {
*r_parent_level_best = parent_level;
*r_collection_parent_best = collection;
}
}
for (CollectionParent *iter = static_cast<CollectionParent *>(collection->runtime.parents.first);
iter != nullptr;
iter = iter->next)
{
if (iter->collection->id.lib != collection->id.lib && ID_IS_LINKED(iter->collection)) {
continue;
}
template_id_liboverride_hierarchy_collection_root_find_recursive(
iter->collection, parent_level + 1, r_collection_parent_best, r_parent_level_best);
}
}
static void template_id_liboverride_hierarchy_collections_tag_recursive(
Collection *root_collection, ID *target_id, const bool do_parents)
{
root_collection->id.tag |= ID_TAG_DOIT;
/* Tag all local parents of the root collection, so that usages of the root collection and other
* linked ones can be replaced by the local overrides in those parents too. */
if (do_parents) {
for (CollectionParent *iter =
static_cast<CollectionParent *>(root_collection->runtime.parents.first);
iter != nullptr;
iter = iter->next)
{
if (ID_IS_LINKED(iter->collection)) {
continue;
}
iter->collection->id.tag |= ID_TAG_DOIT;
}
}
for (CollectionChild *iter = static_cast<CollectionChild *>(root_collection->children.first);
iter != nullptr;
iter = iter->next)
{
if (ID_IS_LINKED(iter->collection) && iter->collection->id.lib != target_id->lib) {
continue;
}
if (GS(target_id->name) == ID_OB &&
!BKE_collection_has_object_recursive(iter->collection, (Object *)target_id))
{
continue;
}
if (GS(target_id->name) == ID_GR &&
!BKE_collection_has_collection(iter->collection, (Collection *)target_id))
{
continue;
}
template_id_liboverride_hierarchy_collections_tag_recursive(
iter->collection, target_id, false);
}
}
ID *ui_template_id_liboverride_hierarchy_make(
bContext *C, Main *bmain, ID *owner_id, ID *id, const char **r_undo_push_label)
{
const char *undo_push_label;
if (r_undo_push_label == nullptr) {
r_undo_push_label = &undo_push_label;
}
/* If this is called on an already local override, 'toggle' between user-editable state, and
* system override with reset. */
if (!ID_IS_LINKED(id) && ID_IS_OVERRIDE_LIBRARY(id)) {
if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) {
BKE_lib_override_library_get(bmain, id, nullptr, &id);
}
if (id->override_library->flag & LIBOVERRIDE_FLAG_SYSTEM_DEFINED) {
id->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
*r_undo_push_label = "Make Library Override Hierarchy Editable";
}
else {
BKE_lib_override_library_id_reset(bmain, id, true);
*r_undo_push_label = "Clear Library Override Hierarchy";
}
WM_event_add_notifier(C, NC_WM | ND_DATACHANGED, nullptr);
WM_event_add_notifier(C, NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr);
return id;
}
/* Attempt to perform a hierarchy override, based on contextual data available.
* NOTE: do not attempt to perform such hierarchy override at all cost, if there is not enough
* context, better to abort than create random overrides all over the place. */
if (!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(id)) {
WM_global_reportf(RPT_ERROR, "The data-block %s is not overridable", id->name);
return nullptr;
}
Object *object_active = CTX_data_active_object(C);
if (object_active == nullptr && GS(owner_id->name) == ID_OB) {
object_active = (Object *)owner_id;
}
if (object_active != nullptr) {
if (ID_IS_LINKED(object_active)) {
if (object_active->id.lib != id->lib || !ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(object_active))
{
/* The active object is from a different library than the overridden ID, or otherwise
* cannot be used in hierarchy. */
object_active = nullptr;
}
}
else if (!ID_IS_OVERRIDE_LIBRARY_REAL(object_active)) {
/* Fully local object cannot be used in override hierarchy either. */
object_active = nullptr;
}
}
Collection *collection_active_context = CTX_data_collection(C);
Collection *collection_active = collection_active_context;
if (collection_active == nullptr && GS(owner_id->name) == ID_GR) {
collection_active = (Collection *)owner_id;
}
if (collection_active != nullptr) {
if (ID_IS_LINKED(collection_active)) {
if (collection_active->id.lib != id->lib ||
!ID_IS_OVERRIDABLE_LIBRARY_HIERARCHY(collection_active))
{
/* The active collection is from a different library than the overridden ID, or otherwise
* cannot be used in hierarchy. */
collection_active = nullptr;
}
else {
int parent_level_best = -1;
Collection *collection_parent_best = nullptr;
template_id_liboverride_hierarchy_collection_root_find_recursive(
collection_active, 0, &collection_parent_best, &parent_level_best);
collection_active = collection_parent_best;
}
}
else if (!ID_IS_OVERRIDE_LIBRARY_REAL(collection_active)) {
/* Fully local collection cannot be used in override hierarchy either. */
collection_active = nullptr;
}
}
if (collection_active == nullptr && object_active != nullptr &&
(ID_IS_LINKED(object_active) || ID_IS_OVERRIDE_LIBRARY_REAL(object_active)))
{
/* If we failed to find a valid 'active' collection so far for our override hierarchy, but do
* have a valid 'active' object, try to find a collection from that object. */
LISTBASE_FOREACH (Collection *, collection_iter, &bmain->collections) {
if (ID_IS_LINKED(collection_iter) && collection_iter->id.lib != id->lib) {
continue;
}
if (!ID_IS_OVERRIDE_LIBRARY_REAL(collection_iter)) {
continue;
}
if (!BKE_collection_has_object_recursive(collection_iter, object_active)) {
continue;
}
int parent_level_best = -1;
Collection *collection_parent_best = nullptr;
template_id_liboverride_hierarchy_collection_root_find_recursive(
collection_iter, 0, &collection_parent_best, &parent_level_best);
collection_active = collection_parent_best;
break;
}
}
ID *id_override = nullptr;
Scene *scene = CTX_data_scene(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
switch (GS(id->name)) {
case ID_GR:
if (collection_active != nullptr &&
BKE_collection_has_collection(collection_active, (Collection *)id))
{
template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true);
if (object_active != nullptr) {
object_active->id.tag |= ID_TAG_DOIT;
}
BKE_lib_override_library_create(bmain,
scene,
view_layer,
nullptr,
id,
&collection_active->id,
nullptr,
&id_override,
false);
}
else if (object_active != nullptr && !ID_IS_LINKED(object_active) &&
&object_active->instance_collection->id == id)
{
object_active->id.tag |= ID_TAG_DOIT;
BKE_lib_override_library_create(bmain,
scene,
view_layer,
id->lib,
id,
&object_active->id,
&object_active->id,
&id_override,
false);
}
break;
case ID_OB:
if (collection_active != nullptr &&
BKE_collection_has_object_recursive(collection_active, (Object *)id))
{
template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true);
if (object_active != nullptr) {
object_active->id.tag |= ID_TAG_DOIT;
}
BKE_lib_override_library_create(bmain,
scene,
view_layer,
nullptr,
id,
&collection_active->id,
nullptr,
&id_override,
false);
}
else {
if (object_active != nullptr) {
object_active->id.tag |= ID_TAG_DOIT;
}
BKE_lib_override_library_create(
bmain, scene, view_layer, nullptr, id, nullptr, nullptr, &id_override, false);
BKE_scene_collections_object_remove(bmain, scene, (Object *)id, true);
WM_event_add_notifier(C, NC_ID | NA_REMOVED, nullptr);
}
break;
case ID_ME:
case ID_CU_LEGACY:
case ID_MB:
case ID_LT:
case ID_LA:
case ID_CA:
case ID_SPK:
case ID_AR:
case ID_GD_LEGACY:
case ID_CV:
case ID_PT:
case ID_VO:
case ID_NT: /* Essentially geometry nodes from modifier currently. */
if (object_active != nullptr) {
if (collection_active != nullptr &&
BKE_collection_has_object_recursive(collection_active, object_active))
{
template_id_liboverride_hierarchy_collections_tag_recursive(collection_active, id, true);
object_active->id.tag |= ID_TAG_DOIT;
BKE_lib_override_library_create(bmain,
scene,
view_layer,
nullptr,
id,
&collection_active->id,
nullptr,
&id_override,
false);
}
else {
object_active->id.tag |= ID_TAG_DOIT;
BKE_lib_override_library_create(bmain,
scene,
view_layer,
nullptr,
id,
&object_active->id,
nullptr,
&id_override,
false);
}
}
else {
BKE_lib_override_library_create(
bmain, scene, view_layer, nullptr, id, id, nullptr, &id_override, false);
}
break;
case ID_MA:
case ID_TE:
case ID_IM:
WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name);
break;
case ID_WO:
WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name);
break;
case ID_PA:
WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name);
break;
default:
WM_global_reportf(RPT_WARNING, "The type of data-block %s is not yet implemented", id->name);
break;
}
if (id_override != nullptr) {
id_override->override_library->flag &= ~LIBOVERRIDE_FLAG_SYSTEM_DEFINED;
/* Ensure that the hierarchy root of the newly overridden data is instantiated in the scene, in
* case it's a collection or object. */
ID *hierarchy_root = id_override->override_library->hierarchy_root;
if (GS(hierarchy_root->name) == ID_OB) {
Object *object_hierarchy_root = reinterpret_cast<Object *>(hierarchy_root);
if (!BKE_scene_has_object(scene, object_hierarchy_root)) {
if (!ID_IS_LINKED(collection_active_context)) {
BKE_collection_object_add(bmain, collection_active_context, object_hierarchy_root);
}
else {
BKE_collection_object_add(bmain, scene->master_collection, object_hierarchy_root);
}
}
}
else if (GS(hierarchy_root->name) == ID_GR) {
Collection *collection_hierarchy_root = reinterpret_cast<Collection *>(hierarchy_root);
if (!BKE_collection_has_collection(scene->master_collection, collection_hierarchy_root)) {
if (!ID_IS_LINKED(collection_active_context)) {
BKE_collection_child_add(bmain, collection_active_context, collection_hierarchy_root);
}
else {
BKE_collection_child_add(bmain, scene->master_collection, collection_hierarchy_root);
}
}
}
*r_undo_push_label = "Make Library Override Hierarchy";
/* In theory we could rely on setting/updating the RNA ID pointer property (as done by calling
* code) to be enough.
*
* However, some rare ID pointers properties (like the "active object in view-layer" one used
* for the Object templateID in the Object properties) use notifiers that do not enforce a
* rebuild of outliner trees, leading to crashes.
*
* So for now, add some extra notifiers here. */
WM_event_add_notifier(C, NC_ID | NA_ADDED, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr);
}
return id_override;
}
static void template_id_liboverride_hierarchy_make(bContext *C,
Main *bmain,
TemplateID *template_ui,
PointerRNA *idptr,
const char **r_undo_push_label)
{
ID *id = static_cast<ID *>(idptr->data);
ID *owner_id = template_ui->ptr.owner_id;
ID *id_override = ui_template_id_liboverride_hierarchy_make(
C, bmain, owner_id, id, r_undo_push_label);
if (id_override != nullptr) {
/* `idptr` is re-assigned to owner property to ensure proper updates etc. Here we also use it
* to ensure remapping of the owner property from the linked data to the newly created
* liboverride (note that in theory this remapping has already been done by code above), but
* only in case owner ID was already local ID (override or pure local data).
*
* Otherwise, owner ID will also have been overridden, and remapped already to use it's
* override of the data too. */
if (!ID_IS_LINKED(owner_id)) {
*idptr = RNA_id_pointer_create(id_override);
}
}
else {
WM_global_reportf(RPT_ERROR, "The data-block %s could not be overridden", id->name);
}
}
static void template_id_cb(bContext *C, void *arg_litem, void *arg_event)
{
TemplateID *template_ui = (TemplateID *)arg_litem;
PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
ID *id = static_cast<ID *>(idptr.data);
const int event = POINTER_AS_INT(arg_event);
const char *undo_push_label = nullptr;
switch (event) {
case UI_ID_NOP:
/* Don't do anything, typically set for buttons that execute an operator instead. They may
* still assign the callback so the button can be identified as part of an ID-template. See
* #UI_context_active_but_prop_get_templateID(). */
break;
case UI_ID_RENAME:
/* Only for the undo push. */
undo_push_label = "Rename Data-Block";
break;
case UI_ID_BROWSE:
case UI_ID_PIN:
RNA_warning("warning, id event %d shouldn't come here", event);
break;
case UI_ID_OPEN:
case UI_ID_ADD_NEW:
/* these call UI_context_active_but_prop_get_templateID */
break;
case UI_ID_DELETE:
idptr = {};
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr);
RNA_property_update(C, &template_ui->ptr, template_ui->prop);
if (id && CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
/* only way to force-remove data (on save) */
id_us_clear_real(id);
id_fake_user_clear(id);
id->us = 0;
undo_push_label = "Delete Data-Block";
}
else {
undo_push_label = "Unlink Data-Block";
}
break;
case UI_ID_FAKE_USER:
if (id) {
if (id->flag & ID_FLAG_FAKEUSER) {
id_us_plus(id);
}
else {
id_us_min(id);
}
undo_push_label = "Fake User";
}
else {
return;
}
break;
case UI_ID_LOCAL:
if (id) {
Main *bmain = CTX_data_main(C);
if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label);
}
else {
if (BKE_lib_id_make_local(bmain, id, LIB_ID_MAKELOCAL_ASSET_DATA_CLEAR)) {
BKE_id_newptr_and_tag_clear(id);
/* Reassign to get proper updates/notifiers. */
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
undo_push_label = "Make Local";
}
}
if (undo_push_label != nullptr) {
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr);
RNA_property_update(C, &template_ui->ptr, template_ui->prop);
}
}
break;
case UI_ID_OVERRIDE:
if (id && ID_IS_OVERRIDE_LIBRARY(id)) {
Main *bmain = CTX_data_main(C);
if (CTX_wm_window(C)->eventstate->modifier & KM_SHIFT) {
template_id_liboverride_hierarchy_make(C, bmain, template_ui, &idptr, &undo_push_label);
}
else {
BKE_lib_override_library_make_local(bmain, id);
/* Reassign to get proper updates/notifiers. */
idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop);
RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, nullptr);
RNA_property_update(C, &template_ui->ptr, template_ui->prop);
undo_push_label = "Make Local";
}
}
break;
case UI_ID_ALONE:
if (id) {
const bool do_scene_obj = ((GS(id->name) == ID_OB) &&
(template_ui->ptr.type == &RNA_LayerObjects));
/* make copy */
if (do_scene_obj) {
Main *bmain = CTX_data_main(C);
Scene *scene = CTX_data_scene(C);
blender::ed::object::object_single_user_make(bmain, scene, (Object *)id);
WM_event_add_notifier(C, NC_WINDOW, nullptr);
DEG_relations_tag_update(bmain);
}
else {
Main *bmain = CTX_data_main(C);
id_single_user(C, id, &template_ui->ptr, template_ui->prop);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr);
DEG_relations_tag_update(bmain);
}
BKE_main_ensure_invariants(*CTX_data_main(C));
undo_push_label = "Make Single User";
}
break;
#if 0
case UI_ID_AUTO_NAME:
break;
#endif
}
if (undo_push_label != nullptr) {
ED_undo_push(C, undo_push_label);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr);
}
}
static StringRef template_id_browse_tip(const StructRNA *type)
{
if (type) {
switch ((ID_Type)RNA_type_to_ID_code(type)) {
case ID_SCE:
return N_("Browse Scene to be linked");
case ID_OB:
return N_("Browse Object to be linked");
case ID_ME:
return N_("Browse Mesh Data to be linked");
case ID_CU_LEGACY:
return N_("Browse Curve Data to be linked");
case ID_MB:
return N_("Browse Metaball Data to be linked");
case ID_MA:
return N_("Browse Material to be linked");
case ID_TE:
return N_("Browse Texture to be linked");
case ID_IM:
return N_("Browse Image to be linked");
case ID_LS:
return N_("Browse Line Style Data to be linked");
case ID_LT:
return N_("Browse Lattice Data to be linked");
case ID_LA:
return N_("Browse Light Data to be linked");
case ID_CA:
return N_("Browse Camera Data to be linked");
case ID_WO:
return N_("Browse World Settings to be linked");
case ID_SCR:
return N_("Choose Screen layout");
case ID_TXT:
return N_("Browse Text to be linked");
case ID_SPK:
return N_("Browse Speaker Data to be linked");
case ID_SO:
return N_("Browse Sound to be linked");
case ID_AR:
return N_("Browse Armature data to be linked");
case ID_AC:
return N_("Browse Action to be linked");
case ID_NT:
return N_("Browse Node Tree to be linked");
case ID_BR:
return N_("Browse Brush to be linked");
case ID_PA:
return N_("Browse Particle Settings to be linked");
case ID_GD_LEGACY:
return N_("Browse Grease Pencil Data to be linked");
case ID_MC:
return N_("Browse Movie Clip to be linked");
case ID_MSK:
return N_("Browse Mask to be linked");
case ID_PAL:
return N_("Browse Palette Data to be linked");
case ID_PC:
return N_("Browse Paint Curve Data to be linked");
case ID_CF:
return N_("Browse Cache Files to be linked");
case ID_WS:
return N_("Browse Workspace to be linked");
case ID_LP:
return N_("Browse LightProbe to be linked");
case ID_CV:
return N_("Browse Curves Data to be linked");
case ID_PT:
return N_("Browse Point Cloud Data to be linked");
case ID_VO:
return N_("Browse Volume Data to be linked");
case ID_GP:
return N_("Browse Grease Pencil Data to be linked");
/* Use generic text. */
case ID_LI:
case ID_IP:
case ID_KE:
case ID_VF:
case ID_GR:
case ID_WM:
break;
}
}
return N_("Browse ID data to be linked");
}
/**
* Add a superimposed extra icon to \a but, for workspace pinning.
* Rather ugly special handling, but this is really a special case at this point, nothing worth
* generalizing.
*/
static void template_id_workspace_pin_extra_icon(const TemplateID &template_ui, uiBut *but)
{
if ((template_ui.idcode != ID_SCE) || (template_ui.ptr.type != &RNA_Window)) {
return;
}
const wmWindow *win = static_cast<const wmWindow *>(template_ui.ptr.data);
const WorkSpace *workspace = WM_window_get_active_workspace(win);
UI_but_extra_operator_icon_add(but,
"WORKSPACE_OT_scene_pin_toggle",
WM_OP_INVOKE_DEFAULT,
(workspace->flags & WORKSPACE_USE_PIN_SCENE) ? ICON_PINNED :
ICON_UNPINNED);
}
/**
* \return a type-based i18n context, needed e.g. by "New" button.
* In most languages, this adjective takes different form based on gender of type name...
*/
#ifdef WITH_INTERNATIONAL
static const char *template_id_context(StructRNA *type)
{
if (type) {
return BKE_idtype_idcode_to_translation_context(RNA_type_to_ID_code(type));
}
return BLT_I18NCONTEXT_DEFAULT;
}
#else
# define template_id_context(type) 0
#endif
static uiBut *template_id_def_new_but(uiBlock *block,
const ID *id,
const TemplateID &template_ui,
StructRNA *type,
const char *const newop,
const bool editable,
const bool id_open,
const bool use_tab_but,
int but_height)
{
ID *idfrom = template_ui.ptr.owner_id;
uiBut *but;
const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT;
/* i18n markup, does nothing! */
BLT_I18N_MSGID_MULTI_CTXT("New",
BLT_I18NCONTEXT_DEFAULT,
BLT_I18NCONTEXT_ID_ACTION,
BLT_I18NCONTEXT_ID_ARMATURE,
BLT_I18NCONTEXT_ID_BRUSH,
BLT_I18NCONTEXT_ID_CAMERA,
BLT_I18NCONTEXT_ID_CURVES,
BLT_I18NCONTEXT_ID_CURVE_LEGACY,
BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE,
BLT_I18NCONTEXT_ID_GPENCIL,
BLT_I18NCONTEXT_ID_IMAGE,
BLT_I18NCONTEXT_ID_LATTICE,
BLT_I18NCONTEXT_ID_LIGHT,
BLT_I18NCONTEXT_ID_LIGHTPROBE,
BLT_I18NCONTEXT_ID_MASK,
BLT_I18NCONTEXT_ID_MATERIAL,
BLT_I18NCONTEXT_ID_MESH, );
BLT_I18N_MSGID_MULTI_CTXT("New",
BLT_I18NCONTEXT_ID_METABALL,
BLT_I18NCONTEXT_ID_NODETREE,
BLT_I18NCONTEXT_ID_OBJECT,
BLT_I18NCONTEXT_ID_PAINTCURVE,
BLT_I18NCONTEXT_ID_PALETTE,
BLT_I18NCONTEXT_ID_PARTICLESETTINGS,
BLT_I18NCONTEXT_ID_POINTCLOUD,
BLT_I18NCONTEXT_ID_SCENE,
BLT_I18NCONTEXT_ID_SCREEN,
BLT_I18NCONTEXT_ID_SOUND,
BLT_I18NCONTEXT_ID_SPEAKER,
BLT_I18NCONTEXT_ID_TEXT,
BLT_I18NCONTEXT_ID_TEXTURE,
BLT_I18NCONTEXT_ID_VOLUME,
BLT_I18NCONTEXT_ID_WORKSPACE,
BLT_I18NCONTEXT_ID_WORLD, );
/* NOTE: BLT_I18N_MSGID_MULTI_CTXT takes a maximum number of parameters,
* check the definition to see if a new call must be added when the limit
* is exceeded. */
const char *button_text = (id) ? "" : CTX_IFACE_(template_id_context(type), "New");
const int icon = (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD;
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6;
if (!id) {
w = std::max(UI_fontstyle_string_width(fstyle, button_text) + int(UI_UNIT_X * 1.5f), w);
}
if (newop) {
but = uiDefIconTextButO(block,
but_type,
newop,
WM_OP_INVOKE_DEFAULT,
icon,
button_text,
0,
0,
w,
but_height,
std::nullopt);
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_ADD_NEW),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
else {
but = uiDefIconTextBut(
block, but_type, 0, icon, button_text, 0, 0, w, but_height, nullptr, 0, 0, std::nullopt);
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_ADD_NEW),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
#ifndef WITH_INTERNATIONAL
UNUSED_VARS(type);
#endif
return but;
}
static void template_ID(const bContext *C,
uiLayout *layout,
TemplateID &template_ui,
StructRNA *type,
int flag,
const char *newop,
const char *openop,
const char *unlinkop,
const std::optional<StringRef> text,
const bool live_icon,
const bool hide_buttons)
{
uiBut *but;
const bool editable = RNA_property_editable(&template_ui.ptr, template_ui.prop);
const bool use_previews = template_ui.preview = (flag & UI_ID_PREVIEWS) != 0;
PointerRNA idptr = RNA_property_pointer_get(&template_ui.ptr, template_ui.prop);
ID *id = static_cast<ID *>(idptr.data);
ID *idfrom = template_ui.ptr.owner_id;
// lb = template_ui->idlb;
/* Allow operators to take the ID from context. */
uiLayoutSetContextPointer(layout, "id", &idptr);
uiBlock *block = uiLayoutGetBlock(layout);
UI_block_align_begin(block);
if (idptr.type) {
type = idptr.type;
}
if (text && !text->is_empty()) {
/* Add label respecting the separated layout property split state. */
uiItemL_respect_property_split(layout, *text, ICON_NONE);
}
if (flag & UI_ID_BROWSE) {
template_add_button_search_menu(C,
layout,
block,
&template_ui.ptr,
template_ui.prop,
id_search_menu,
MEM_new<TemplateID>(__func__, template_ui),
TIP_(template_id_browse_tip(type)),
use_previews,
editable,
live_icon,
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
/* text button with name */
if (id) {
char name[UI_MAX_NAME_STR];
const bool user_alert = (id->us <= 0);
int width = template_search_textbut_width(&idptr, RNA_struct_find_property(&idptr, "name"));
if ((template_ui.idcode == ID_SCE) && (template_ui.ptr.type == &RNA_Window)) {
/* More room needed for "pin" icon. */
width += UI_UNIT_X;
}
const int height = template_search_textbut_height();
// text_idbutton(id, name);
name[0] = '\0';
but = uiDefButR(block,
UI_BTYPE_TEXT,
0,
name,
0,
0,
width,
height,
&idptr,
"name",
-1,
0,
0,
RNA_struct_ui_description(type));
/* Handle undo through the #template_id_cb set below. Default undo handling from the button
* code (see #ui_apply_but_undo) would not work here, as the new name is not yet applied to the
* ID. */
UI_but_flag_disable(but, UI_BUT_UNDO);
Main *bmain = CTX_data_main(C);
UI_but_func_rename_full_set(
but, [bmain, id](std::string &new_name) { ED_id_rename(*bmain, *id, new_name); });
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_RENAME),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
if (user_alert) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
template_id_workspace_pin_extra_icon(template_ui, but);
if (!hide_buttons && !(idfrom && ID_IS_LINKED(idfrom))) {
if (ID_IS_LINKED(id)) {
const bool disabled = !BKE_idtype_idcode_is_localizable(GS(id->name));
if (id->tag & ID_TAG_INDIRECT) {
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
ICON_LIBRARY_DATA_INDIRECT,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0,
0,
TIP_("Indirect library data-block, cannot be made local, "
"Shift + Click to create a library override hierarchy"));
}
else {
but = uiDefIconBut(block,
UI_BTYPE_BUT,
0,
ICON_LIBRARY_DATA_DIRECT,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0,
0,
TIP_("Direct linked library data-block, click to make local, "
"Shift + Click to create a library override"));
}
if (disabled) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
else {
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_LOCAL),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
}
else if (ID_IS_OVERRIDE_LIBRARY(id)) {
but = uiDefIconBut(
block,
UI_BTYPE_BUT,
0,
ICON_LIBRARY_DATA_OVERRIDE,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0,
0,
TIP_("Library override of linked data-block, click to make fully local, "
"Shift + Click to clear the library override and toggle if it can be edited"));
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_OVERRIDE),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
}
if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) {
char numstr[32];
short numstr_len;
numstr_len = SNPRINTF_RLEN(numstr, "%d", ID_REAL_USERS(id));
but = uiDefBut(
block,
UI_BTYPE_BUT,
0,
numstr,
0,
0,
numstr_len * 0.2f * UI_UNIT_X + UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0,
0,
TIP_("Display number of users of this data (click to make a single-user copy)"));
but->flag |= UI_BUT_UNDO;
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_ALONE),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
if (!BKE_id_copy_is_allowed(id) || (idfrom && !ID_IS_EDITABLE(idfrom)) || (!editable) ||
/* object in editmode - don't change data */
(idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT)))
{
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}
if (user_alert) {
UI_but_flag_enable(but, UI_BUT_REDALERT);
}
if (!ID_IS_LINKED(id)) {
if (ID_IS_ASSET(id)) {
uiDefIconButO(block,
/* Using `_N` version allows us to get the 'active' state by default. */
UI_BTYPE_ICON_TOGGLE_N,
"ASSET_OT_clear_single",
WM_OP_INVOKE_DEFAULT,
/* 'active' state of a toggle button uses icon + 1, so to get proper asset
* icon we need to pass its value - 1 here. */
ICON_ASSET_MANAGER - 1,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
std::nullopt);
}
else if (!ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_OB, ID_WS) && (hide_buttons == false))
{
uiDefIconButR(block,
UI_BTYPE_ICON_TOGGLE,
0,
ICON_FAKE_USER_OFF,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
&idptr,
"use_fake_user",
-1,
0,
0,
std::nullopt);
}
}
}
if ((flag & UI_ID_ADD_NEW) && (hide_buttons == false)) {
template_id_def_new_but(
block, id, template_ui, type, newop, editable, flag & UI_ID_OPEN, false, UI_UNIT_X);
}
/* Due to space limit in UI - skip the "open" icon for packed data, and allow to unpack.
* Only for images, sound and fonts */
if (id && BKE_packedfile_id_check(id)) {
but = uiDefIconButO(block,
UI_BTYPE_BUT,
"FILE_OT_unpack_item",
WM_OP_INVOKE_REGION_WIN,
ICON_PACKAGE,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
TIP_("Packed File, click to unpack"));
UI_but_operator_ptr_ensure(but);
RNA_string_set(but->opptr, "id_name", id->name + 2);
RNA_int_set(but->opptr, "id_type", GS(id->name));
if (!ID_IS_EDITABLE(id)) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}
else if (flag & UI_ID_OPEN) {
const char *button_text = (id) ? "" : IFACE_("Open");
const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6;
if (!id) {
w = std::max(UI_fontstyle_string_width(fstyle, button_text) + int(UI_UNIT_X * 1.5f), w);
}
if (openop) {
but = uiDefIconTextButO(block,
UI_BTYPE_BUT,
openop,
WM_OP_INVOKE_DEFAULT,
ICON_FILEBROWSER,
(id) ? "" : IFACE_("Open"),
0,
0,
w,
UI_UNIT_Y,
std::nullopt);
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_OPEN),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
else {
but = uiDefIconTextBut(block,
UI_BTYPE_BUT,
0,
ICON_FILEBROWSER,
(id) ? "" : IFACE_("Open"),
0,
0,
w,
UI_UNIT_Y,
nullptr,
0,
0,
std::nullopt);
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_OPEN),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}
/* delete button */
/* don't use RNA_property_is_unlink here */
if (id && (flag & UI_ID_DELETE) && (hide_buttons == false)) {
/* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */
but = nullptr;
if (unlinkop) {
but = uiDefIconButO(block,
UI_BTYPE_BUT,
unlinkop,
WM_OP_INVOKE_DEFAULT,
ICON_X,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
std::nullopt);
/* so we can access the template from operators, font unlinking needs this */
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_NOP),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
}
else {
if ((RNA_property_flag(template_ui.prop) & PROP_NEVER_UNLINK) == 0) {
but = uiDefIconBut(
block,
UI_BTYPE_BUT,
0,
ICON_X,
0,
0,
UI_UNIT_X,
UI_UNIT_Y,
nullptr,
0,
0,
TIP_("Unlink data-block "
"(Shift + Click to set users to zero, data will then not be saved)"));
UI_but_funcN_set(but,
template_id_cb,
MEM_new<TemplateID>(__func__, template_ui),
POINTER_FROM_INT(UI_ID_DELETE),
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
if (RNA_property_flag(template_ui.prop) & PROP_NEVER_NULL) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}
}
if (but) {
if ((idfrom && !ID_IS_EDITABLE(idfrom)) || !editable) {
UI_but_flag_enable(but, UI_BUT_DISABLED);
}
}
}
if (template_ui.idcode == ID_TE) {
uiTemplateTextureShow(layout, C, &template_ui.ptr, template_ui.prop);
}
UI_block_align_end(block);
}
ID *UI_context_active_but_get_tab_ID(bContext *C)
{
uiBut *but = UI_context_active_but_get(C);
if (but && but->type == UI_BTYPE_TAB) {
return static_cast<ID *>(but->custom_data);
}
return nullptr;
}
static void template_ID_tabs(const bContext *C,
uiLayout *layout,
TemplateID &template_id,
StructRNA *type,
int flag,
const char *newop,
const char *menu)
{
const ARegion *region = CTX_wm_region(C);
const PointerRNA active_ptr = RNA_property_pointer_get(&template_id.ptr, template_id.prop);
MenuType *mt = menu ? WM_menutype_find(menu, false) : nullptr;
const int but_align = ui_but_align_opposite_to_area_align_get(region);
const int but_height = UI_UNIT_Y * 1.1;
uiBlock *block = uiLayoutGetBlock(layout);
const uiStyle *style = UI_style_get_dpi();
for (ID *id : BKE_id_ordered_list(template_id.idlb)) {
const int name_width = UI_fontstyle_string_width(&style->widget, id->name + 2);
const int but_width = name_width + UI_UNIT_X;
uiButTab *tab = (uiButTab *)uiDefButR_prop(block,
UI_BTYPE_TAB,
0,
id->name + 2,
0,
0,
but_width,
but_height,
&template_id.ptr,
template_id.prop,
0,
0.0f,
sizeof(id->name) - 2,
"");
UI_but_funcN_set(tab,
template_ID_set_property_exec_fn,
MEM_new<TemplateID>(__func__, template_id),
id,
but_func_argN_free<TemplateID>,
but_func_argN_copy<TemplateID>);
UI_but_drag_set_id(tab, id);
tab->custom_data = (void *)id;
tab->menu = mt;
UI_but_drawflag_enable(tab, but_align);
}
if (flag & UI_ID_ADD_NEW) {
const bool editable = RNA_property_editable(&template_id.ptr, template_id.prop);
uiBut *but;
if (active_ptr.type) {
type = active_ptr.type;
}
but = template_id_def_new_but(block,
static_cast<const ID *>(active_ptr.data),
template_id,
type,
newop,
editable,
flag & UI_ID_OPEN,
true,
but_height);
UI_but_drawflag_enable(but, but_align);
}
}
static void ui_template_id(uiLayout *layout,
const bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
const char *newop,
const char *openop,
const char *unlinkop,
/* Only respected by tabs (use_tabs). */
const char *menu,
const std::optional<StringRef> text,
int flag,
int prv_rows,
int prv_cols,
int filter,
bool use_tabs,
float scale,
const bool live_icon,
const bool hide_buttons)
{
PropertyRNA *prop = RNA_struct_find_property(ptr, propname.c_str());
if (!prop || RNA_property_type(prop) != PROP_POINTER) {
RNA_warning(
"pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname.c_str());
return;
}
TemplateID template_ui = {};
template_ui.ptr = *ptr;
template_ui.prop = prop;
template_ui.prv_rows = prv_rows;
template_ui.prv_cols = prv_cols;
template_ui.scale = scale;
if ((flag & UI_ID_PIN) == 0) {
template_ui.filter = filter;
}
else {
template_ui.filter = 0;
}
if (newop) {
flag |= UI_ID_ADD_NEW;
}
if (openop) {
flag |= UI_ID_OPEN;
}
StructRNA *type = RNA_property_pointer_type(ptr, prop);
short idcode = RNA_type_to_ID_code(type);
template_ui.idcode = idcode;
template_ui.idlb = which_libbase(CTX_data_main(C), idcode);
/* create UI elements for this template
* - template_ID makes a copy of the template data and assigns it to the relevant buttons
*/
if (template_ui.idlb) {
if (use_tabs) {
layout = &layout->row(true);
template_ID_tabs(C, layout, template_ui, type, flag, newop, menu);
}
else {
layout = &layout->row(true);
template_ID(C,
layout,
template_ui,
type,
flag,
newop,
openop,
unlinkop,
text,
live_icon,
hide_buttons);
}
}
}
void uiTemplateID(uiLayout *layout,
const bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
const char *newop,
const char *openop,
const char *unlinkop,
int filter,
const bool live_icon,
const std::optional<StringRef> text)
{
ui_template_id(layout,
C,
ptr,
propname,
newop,
openop,
unlinkop,
nullptr,
text,
UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE,
0,
0,
filter,
false,
1.0f,
live_icon,
false);
}
void uiTemplateAction(uiLayout *layout,
const bContext *C,
ID *id,
const char *newop,
const char *unlinkop,
const std::optional<StringRef> text)
{
if (!id_can_have_animdata(id)) {
RNA_warning("Cannot show Action selector for non-animatable ID: %s", id->name + 2);
return;
}
PropertyRNA *adt_action_prop = RNA_struct_type_find_property(&RNA_AnimData, "action");
BLI_assert(adt_action_prop);
BLI_assert(RNA_property_type(adt_action_prop) == PROP_POINTER);
/* Construct a pointer with the animated ID as owner, even when `adt` may be `nullptr`.
* This way it is possible to use this RNA pointer to get/set `adt->action`, as that RNA property
* has a `getter` & `setter` that only need the owner ID and are null-safe regarding the `adt`
* itself.
* FIXME: This is a very dirty hack, would be good to find a way to not rely on typed-but-null
* PointerRNA.
*/
AnimData *adt = BKE_animdata_from_id(id);
PointerRNA adt_ptr = PointerRNA{id, &RNA_AnimData, adt, RNA_id_pointer_create(id)};
TemplateID template_ui = {};
template_ui.ptr = adt_ptr;
template_ui.prop = adt_action_prop;
template_ui.prv_rows = 0;
template_ui.prv_cols = 0;
template_ui.scale = 1.0f;
template_ui.filter = UI_TEMPLATE_ID_FILTER_ALL;
int flag = UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE;
if (newop) {
flag |= UI_ID_ADD_NEW;
}
template_ui.idcode = ID_AC;
template_ui.idlb = which_libbase(CTX_data_main(C), ID_AC);
BLI_assert(template_ui.idlb);
uiLayout *row = &layout->row(true);
template_ID(
C, row, template_ui, &RNA_Action, flag, newop, nullptr, unlinkop, text, false, false);
}
void uiTemplateIDBrowse(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
const char *newop,
const char *openop,
const char *unlinkop,
int filter,
const char *text)
{
ui_template_id(layout,
C,
ptr,
propname,
newop,
openop,
unlinkop,
nullptr,
text,
UI_ID_BROWSE | UI_ID_RENAME,
0,
0,
filter,
false,
1.0f,
false,
false);
}
void uiTemplateIDPreview(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
const char *newop,
const char *openop,
const char *unlinkop,
int rows,
int cols,
int filter,
const bool hide_buttons)
{
ui_template_id(layout,
C,
ptr,
propname,
newop,
openop,
unlinkop,
nullptr,
nullptr,
UI_ID_BROWSE | UI_ID_RENAME | UI_ID_DELETE | UI_ID_PREVIEWS,
rows,
cols,
filter,
false,
1.0f,
false,
hide_buttons);
}
void uiTemplateGpencilColorPreview(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
int rows,
int cols,
float scale,
int filter)
{
ui_template_id(layout,
C,
ptr,
propname,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE,
rows,
cols,
filter,
false,
scale < 0.5f ? 0.5f : scale,
false,
false);
}
void uiTemplateIDTabs(uiLayout *layout,
bContext *C,
PointerRNA *ptr,
const StringRefNull propname,
const char *newop,
const char *menu,
int filter)
{
ui_template_id(layout,
C,
ptr,
propname,
newop,
nullptr,
nullptr,
menu,
nullptr,
UI_ID_BROWSE | UI_ID_RENAME,
0,
0,
filter,
true,
1.0f,
false,
false);
}
void uiTemplateAnyID(uiLayout *layout,
PointerRNA *ptr,
const StringRefNull propname,
const StringRefNull proptypename,
const std::optional<StringRef> text)
{
/* get properties... */
PropertyRNA *propID = RNA_struct_find_property(ptr, propname.c_str());
PropertyRNA *propType = RNA_struct_find_property(ptr, proptypename.c_str());
if (!propID || RNA_property_type(propID) != PROP_POINTER) {
RNA_warning(
"pointer property not found: %s.%s", RNA_struct_identifier(ptr->type), propname.c_str());
return;
}
if (!propType || RNA_property_type(propType) != PROP_ENUM) {
RNA_warning("pointer-type property not found: %s.%s",
RNA_struct_identifier(ptr->type),
proptypename.c_str());
return;
}
/* Start drawing UI Elements using standard defines */
/* NOTE: split amount here needs to be synced with normal labels */
uiLayout *split = &layout->split(0.33f, false);
/* FIRST PART ................................................ */
uiLayout *row = &split->row(false);
/* Label - either use the provided text, or will become "ID-Block:" */
if (text) {
if (!text->is_empty()) {
row->label(*text, ICON_NONE);
}
}
else {
row->label(IFACE_("ID-Block:"), ICON_NONE);
}
/* SECOND PART ................................................ */
row = &split->row(true);
/* ID-Type Selector - just have a menu of icons */
/* HACK: special group just for the enum,
* otherwise we get ugly layout with text included too... */
uiLayout *sub = &row->row(true);
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_LEFT);
sub->prop(ptr, propType, 0, 0, UI_ITEM_R_ICON_ONLY, "", ICON_NONE);
/* ID-Block Selector - just use pointer widget... */
/* HACK: special group to counteract the effects of the previous enum,
* which now pushes everything too far right. */
sub = &row->row(true);
uiLayoutSetAlignment(sub, UI_LAYOUT_ALIGN_EXPAND);
sub->prop(ptr, propID, 0, 0, UI_ITEM_NONE, "", ICON_NONE);
}