diff options
author | Julian Eisel <julian@blender.org> | 2020-12-18 20:12:11 +0300 |
---|---|---|
committer | Julian Eisel <julian@blender.org> | 2020-12-18 20:28:04 +0300 |
commit | 2250b5cefee7f7cce31e388cb83515543ffe60f0 (patch) | |
tree | cbac53205d453f4b1c7ec221b6377a672d9eaa09 /source/blender/editors/interface/interface_templates.c | |
parent | 7bee1489c1d81ecf2634df96ba039549c64313c3 (diff) |
UI: Redesigned data-block selectors
The previous design is rather old and has a couple of problems:
* Scalability: The current solution of adding little icon buttons next to the
data-block name field doesn't scale well. It only works if there's a small
number of operations. We need to be able to place more items there for better
data-block management. Especially with the introduction of library overrides.
* Discoverability: It's not obvious what some of the icons do. They appear and
disappear, but it's not obvious why some are available at times and others
not.
* Unclear Status: Currently their library status (linked, indirectly linked,
broken link, library override) isn't really clear.
* Unusual behavior: Some of the icon buttons allow Shift or Ctrl clicking to
invoke alternative behaviors. This is not a usual pattern in Blender.
This patch does the following changes:
* Adds a menu to the right of the name button to access all kinds of operations
(create, delete, unlink, user management, library overrides, etc).
* Make good use of the "disabled hint" for tooltips, to explain why buttons are
disabled. The UI team wants to establish this as a good practise.
* Use superimposed icons for duplicate and unlink, rather than extra buttons
(uses less space, looks less distracting and is a nice + consistent design
language).
* Remove fake user and user count button, they are available from the menu now.
* Support tooltips for superimposed icons (committed mouse hover feedback to
master already).
* Slightly increase size of the name button - it was already a bit small
before, and the move from real buttons to superimposed icons reduces usable
space for the name itself.
* More clearly differentiate between duplicate and creating a new data-block.
The latter is only available in the menu.
* Display library status icon on the left (linked, missing library, overridden,
asset)
* Disables "Make Single User" button - in review we weren't sure if there are
good use-cases for it, so better to see if we can remove it.
Note that I do expect some aspects of this design to change still. I think some
changes are problematic, but others disagreed. I will open a feedback thread on
devtalk to see what others think.
Differential Revision: https://developer.blender.org/D8554
Reviewed by: Bastien Montagne
Design discussed and agreed on with the UI team, also see T79959.
Diffstat (limited to 'source/blender/editors/interface/interface_templates.c')
-rw-r--r-- | source/blender/editors/interface/interface_templates.c | 1086 |
1 files changed, 680 insertions, 406 deletions
diff --git a/source/blender/editors/interface/interface_templates.c b/source/blender/editors/interface/interface_templates.c index b895f1702f4..6e96704271f 100644 --- a/source/blender/editors/interface/interface_templates.c +++ b/source/blender/editors/interface/interface_templates.c @@ -102,9 +102,16 @@ // #define USE_OP_RESET_BUT /* defines for templateID/TemplateSearch */ -#define TEMPLATE_SEARCH_TEXTBUT_WIDTH (UI_UNIT_X * 6) +#define TEMPLATE_SEARCH_TEXTBUT_WIDTH (UI_UNIT_X * 8) #define TEMPLATE_SEARCH_TEXTBUT_HEIGHT UI_UNIT_Y +/* Add "Make Single User" button to templateID. Users can just manually duplicate an ID, it's + * unclear what the use-case of this specific button is. So for now disabling it, we can bring it + * back or remove it later. + * - Julian + */ +//#define USE_TEMPLATE_ID_MAKE_SINGLE_USER + void UI_template_fix_linking(void) { } @@ -308,6 +315,12 @@ typedef struct TemplateID { ListBase *idlb; short idcode; + + const char *new_op; + const char *duplicate_op; + const char *unlink_op; + const char *open_op; + short filter; int prv_rows, prv_cols; bool preview; @@ -564,80 +577,64 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) memset(&idptr, 0, sizeof(idptr)); RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); RNA_property_update(C, &template_ui->ptr, template_ui->prop); - - if (id && CTX_wm_window(C)->eventstate->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"; - } - break; - case UI_ID_FAKE_USER: + case UI_ID_MAKE_LOCAL: if (id) { - if (id->flag & LIB_FAKEUSER) { - id_us_plus(id); - } - else { - id_us_min(id); + Main *bmain = CTX_data_main(C); + if (BKE_lib_id_make_local(bmain, id, false, 0)) { + BKE_main_id_clear_newpoins(bmain); + + /* reassign to get 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, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + undo_push_label = "Make Local"; } - undo_push_label = "Fake User"; - } - else { - return; } break; - case UI_ID_LOCAL: - if (id) { + case UI_ID_LIB_OVERRIDE_ADD: + if (id && ID_IS_OVERRIDABLE_LIBRARY(id)) { Main *bmain = CTX_data_main(C); - if (CTX_wm_window(C)->eventstate->shift) { - if (ID_IS_OVERRIDABLE_LIBRARY(id)) { - /* Only remap that specific ID usage to overriding local data-block. */ - ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false); - if (override_id != NULL) { - BKE_main_id_clear_newpoins(bmain); - - if (GS(override_id->name) == ID_OB) { - Scene *scene = CTX_data_scene(C); - if (!BKE_collection_has_object_recursive(scene->master_collection, - (Object *)override_id)) { - BKE_collection_object_add_from( - bmain, scene, (Object *)id, (Object *)override_id); - } - } - - /* Assign new pointer, takes care of updates/notifiers */ - RNA_id_pointer_create(override_id, &idptr); + /* Only remap that specific ID usage to overriding local data-block. */ + ID *override_id = BKE_lib_override_library_create_from_id(bmain, id, false); + if (override_id != NULL) { + BKE_main_id_clear_newpoins(bmain); + + if (GS(override_id->name) == ID_OB) { + Scene *scene = CTX_data_scene(C); + if (!BKE_collection_has_object_recursive(scene->master_collection, + (Object *)override_id)) { + BKE_collection_object_add_from(bmain, scene, (Object *)id, (Object *)override_id); } - undo_push_label = "Make Library Override"; } - } - else { - if (BKE_lib_id_make_local(bmain, id, false, 0)) { - BKE_main_id_clear_newpoins(bmain); - /* reassign to get get proper updates/notifiers */ - idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); - undo_push_label = "Make Local"; - } - } - if (undo_push_label != NULL) { - RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); - RNA_property_update(C, &template_ui->ptr, template_ui->prop); + /* Assign new pointer, takes care of updates/notifiers */ + RNA_id_pointer_create(override_id, &idptr); } + /* reassign to get get proper updates/notifiers */ + RNA_property_pointer_set(&template_ui->ptr, template_ui->prop, idptr, NULL); + RNA_property_update(C, &template_ui->ptr, template_ui->prop); + undo_push_label = "Make Library Override"; } break; - case UI_ID_OVERRIDE: + case UI_ID_LIB_OVERRIDE_RESET: + if (id && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + Main *bmain = CTX_data_main(C); + BKE_lib_override_library_id_reset(bmain, id); + undo_push_label = "Reset Library Override"; + } + break; + case UI_ID_LIB_OVERRIDE_REMOVE: if (id && ID_IS_OVERRIDE_LIBRARY(id)) { BKE_lib_override_library_free(&id->override_library, true); /* reassign to get 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, NULL); RNA_property_update(C, &template_ui->ptr, template_ui->prop); - undo_push_label = "Override Data-Block"; + undo_push_label = "Remove Library Override"; } break; +#ifdef USE_TEMPLATE_ID_MAKE_SINGLE_USER case UI_ID_ALONE: if (id) { const bool do_scene_obj = ((GS(id->name) == ID_OB) && @@ -659,6 +656,7 @@ static void template_id_cb(bContext *C, void *arg_litem, void *arg_event) undo_push_label = "Make Single User"; } break; +#endif #if 0 case UI_ID_AUTO_NAME: break; @@ -772,17 +770,221 @@ static const char *template_id_context(StructRNA *type) # 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) -{ +static void template_id_linked_operation_button( + uiBlock *block, Main *bmain, ID *id, TemplateID *template_ui, int operation) +{ + BLI_assert(ELEM(operation, UI_ID_MAKE_LOCAL, UI_ID_LIB_OVERRIDE_ADD)); + + const char *label = (operation == UI_ID_MAKE_LOCAL) ? + CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Make Local") : + CTX_N_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Library Override"); + const char *tip = (operation == UI_ID_MAKE_LOCAL) ? + N_("Make library linked data-block local to this file") : + N_("Create a local override of this library linked data-block"); + BIFIconID icon = (operation == UI_ID_MAKE_LOCAL) ? ICON_BLANK1 : ICON_LIBRARY_DATA_OVERRIDE; + + uiBut *but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + icon, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, label), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_(tip)); + + bool disabled = false; + + if (!ID_IS_LINKED(id)) { + disabled = true; + but->disabled_info = TIP_("Data-block is not linked"); + } + else if (id->tag & LIB_TAG_INDIRECT) { + disabled = true; + but->disabled_info = TIP_("Indirect library data-block, cannot change"); + } + else if (!BKE_lib_id_make_local(bmain, id, true /* test */, 0)) { + disabled = true; + but->disabled_info = TIP_("Data-blocks of this type cannot be made local"); + } + else if (!RNA_property_editable_info( + &template_ui->ptr, template_ui->prop, &but->disabled_info)) { + disabled = true; + } + + if (disabled) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else { + UI_but_funcN_set(but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(operation)); + } +} + +static void template_id_library_overridden_button(uiBlock *block, + ID *id, + TemplateID *template_ui, + int operation) +{ + BLI_assert(ELEM(operation, UI_ID_LIB_OVERRIDE_RESET, UI_ID_LIB_OVERRIDE_REMOVE) && + ID_IS_OVERRIDE_LIBRARY(id)); + + if (operation == UI_ID_LIB_OVERRIDE_RESET && ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + uiBut *but = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_BLANK1, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset Library Override"), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Reset the local override to the state of " + "the overridden data-block")); + if (!ID_IS_OVERRIDE_LIBRARY_REAL(id)) { + but->disabled_info = TIP_("Data-block is a virtual, not a real override"); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + else { + UI_but_funcN_set(but, + template_id_cb, + MEM_dupallocN(template_ui), + POINTER_FROM_INT(UI_ID_LIB_OVERRIDE_RESET)); + } + } + else if (operation == UI_ID_LIB_OVERRIDE_REMOVE) { + uiBut *but = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_BLANK1, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Make Local"), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Remove library override and make the library linked data-block " + "fully local to this file")); + UI_but_funcN_set(but, + template_id_cb, + MEM_dupallocN(template_ui), + POINTER_FROM_INT(UI_ID_LIB_OVERRIDE_REMOVE)); + } +} + +#ifdef USE_TEMPLATE_ID_MAKE_SINGLE_USER +static uiBut *template_id_make_single_user_button(uiBlock *block, ID *id, TemplateID *template_ui) +{ + uiBut *but = uiDefIconTextBut( + block, + UI_BTYPE_BUT, + 0, + ICON_BLANK1, + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Make Single-User Copy"), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Duplicate the data-block and assign the newly created copy")); + + if (ID_REAL_USERS(id) <= 1) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + but->disabled_info = TIP_("Data-block already is a single-user"); + /* No need for further setup. */ + return but; + } + + UI_but_funcN_set(but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ALONE)); + ID *idfrom = template_ui->ptr.owner_id; + bool disabled = false; + + if (!RNA_property_editable_info(&template_ui->ptr, template_ui->prop, &but->disabled_info)) { + disabled = true; + } + if (!BKE_id_copy_is_allowed(id)) { + but->disabled_info = TIP_("Data-blocks of this type cannot be copied"); + disabled = true; + } + /* object in editmode - don't change data */ + if (idfrom && GS(idfrom->name) == ID_OB && (((Object *)idfrom)->mode & OB_MODE_EDIT)) { + but->disabled_info = TIP_("Cannot change object data in Edit Mode"); + disabled = true; + } + + if (disabled) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + + return but; +} +#endif + +static void template_id_use_fake_user_button(uiBlock *block, PointerRNA *idptr) +{ + const bool use_fake_user = ID_FAKE_USERS(idptr->data) > 0; + uiBut *but = uiDefIconTextButR( + block, + UI_BTYPE_ICON_TOGGLE, + 0, + ICON_FAKE_USER_OFF, + use_fake_user ? CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Fake User") : + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Fake User"), + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + idptr, + "use_fake_user", + -1, + 0, + 0, + -1, + -1, + TIP_("When set, ensures the data-block is kept when reloading the file, even if not used at " + "all")); + + if ((ELEM(GS(((ID *)idptr->data)->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS))) { + but->disabled_info = TIP_("Data-block type does not support fake user"); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } +} + +enum TemplateIDCreateType { + TEMPLATE_ID_CREATE_NEW, + TEMPLATE_ID_CREATE_DUPLICATE, +}; + +static uiBut *template_id_new_button(uiBlock *block, + const ID *id, + TemplateID *template_ui, + enum TemplateIDCreateType create_type, + StructRNA *type, + const bool id_open, + const bool use_tab_but, + int but_height) +{ uiBut *but; const int w = id ? UI_UNIT_X : id_open ? UI_UNIT_X * 3 : UI_UNIT_X * 6; const int but_type = use_tab_but ? UI_BTYPE_TAB : UI_BTYPE_BUT; @@ -820,54 +1022,384 @@ static uiBut *template_id_def_new_but(uiBlock *block, BLT_I18NCONTEXT_ID_POINTCLOUD, BLT_I18NCONTEXT_ID_VOLUME, BLT_I18NCONTEXT_ID_SIMULATION, ); + BLT_I18N_MSGID_MULTI_CTXT("Duplicate", + BLT_I18NCONTEXT_DEFAULT, + BLT_I18NCONTEXT_ID_SCENE, + BLT_I18NCONTEXT_ID_OBJECT, + BLT_I18NCONTEXT_ID_MESH, + BLT_I18NCONTEXT_ID_CURVE, + BLT_I18NCONTEXT_ID_METABALL, + BLT_I18NCONTEXT_ID_MATERIAL, + BLT_I18NCONTEXT_ID_TEXTURE, + BLT_I18NCONTEXT_ID_IMAGE, + BLT_I18NCONTEXT_ID_LATTICE, + BLT_I18NCONTEXT_ID_LIGHT, + BLT_I18NCONTEXT_ID_CAMERA, + BLT_I18NCONTEXT_ID_WORLD, + BLT_I18NCONTEXT_ID_SCREEN, + BLT_I18NCONTEXT_ID_TEXT, ); + BLT_I18N_MSGID_MULTI_CTXT("Duplicate", + BLT_I18NCONTEXT_ID_SPEAKER, + BLT_I18NCONTEXT_ID_SOUND, + BLT_I18NCONTEXT_ID_ARMATURE, + BLT_I18NCONTEXT_ID_ACTION, + BLT_I18NCONTEXT_ID_NODETREE, + BLT_I18NCONTEXT_ID_BRUSH, + BLT_I18NCONTEXT_ID_PARTICLESETTINGS, + BLT_I18NCONTEXT_ID_GPENCIL, + BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, + BLT_I18NCONTEXT_ID_WORKSPACE, + BLT_I18NCONTEXT_ID_LIGHTPROBE, + BLT_I18NCONTEXT_ID_HAIR, + BLT_I18NCONTEXT_ID_POINTCLOUD, + BLT_I18NCONTEXT_ID_VOLUME, + BLT_I18NCONTEXT_ID_SIMULATION, ); /* 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. */ - if (newop) { + const char *text; + const char *op; + BIFIconID icon; + + switch (create_type) { + case TEMPLATE_ID_CREATE_NEW: + icon = ICON_ADD; + text = CTX_IFACE_(template_id_context(type), "New"); + op = template_ui->new_op; + break; + case TEMPLATE_ID_CREATE_DUPLICATE: + icon = ICON_DUPLICATE; + text = CTX_IFACE_(template_id_context(type), "Duplicate"); + op = template_ui->duplicate_op; + break; + } + + const bool icon_only = use_tab_but; + if (icon_only) { + text = ""; + } + + if (op) { + but = uiDefIconTextButO( + block, but_type, op, WM_OP_INVOKE_DEFAULT, icon, text, 0, 0, w, but_height, NULL); + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); + + if (!RNA_property_editable_info(&template_ui->ptr, template_ui->prop, &but->disabled_info)) { + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + else { + but = uiDefIconTextBut(block, + but_type, + 0, + icon, + text, + 0, + 0, + w, + but_height, + NULL, + 0, + 0, + 0, + 0, + TIP_("Create a new data-block")); + but->disabled_info = TIP_("Creating a new data-block is not supported here"); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + if (icon_only) { + UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT); + } + +#ifndef WITH_INTERNATIONAL + UNUSED_VARS(type); +#endif + + return but; +} + +static void template_id_unlink_button(uiBlock *block, + TemplateID *template_ui, + bool hide_if_disabled) +{ + const char *disable_info; + const bool is_menu = ui_block_is_menu(block); + const bool editable = RNA_property_editable_info( + &template_ui->ptr, template_ui->prop, &disable_info); + /* allow unlink if 'unlinkop' is passed, even when 'PROP_NEVER_UNLINK' is set */ + uiBut *but = NULL; + + if (hide_if_disabled && !editable) { + /* Add no button. */ + } + else if (template_ui->unlink_op) { + but = uiDefIconTextButO(block, + UI_BTYPE_BUT, + template_ui->unlink_op, + WM_OP_INVOKE_DEFAULT, + ICON_X, + is_menu ? NULL : "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + /* so we can access the template from operators, font unlinking needs this */ + UI_but_funcN_set(but, NULL, MEM_dupallocN(template_ui), NULL); + } + else { + const bool never_unlink = RNA_property_flag(template_ui->prop) & + (PROP_NEVER_UNLINK | PROP_NEVER_NULL); + + if (!never_unlink || !hide_if_disabled) { + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_X, + is_menu ? CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Unlink") : "", + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + TIP_("Remove this usage of the data-block")); + if (!never_unlink) { + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_DELETE)); + } + if (!is_menu) { + UI_but_drawflag_disable(but, UI_BUT_ICON_LEFT); + } + } + + if (but && never_unlink) { + but->disabled_info = TIP_("Property must never be in an unlinked state"); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } + + if (but) { + if (!editable) { + but->disabled_info = disable_info; + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + } +} + +static void template_id_open_button(uiBlock *block, + ID *id, + TemplateID *template_ui, + const bool compact) +{ + const bool is_menu = ui_block_is_menu(block); + const int w = compact ? UI_UNIT_X * 3 : UI_UNIT_X * 6; + char text[UI_MAX_NAME_STR] = ""; + const bool icon_only = !is_menu && id; + uiBut *but; + + if (!icon_only) { + BLI_snprintf(text, + sizeof(text), + "%s%s", + CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open"), + is_menu ? "..." : ""); + } + + if (template_ui->open_op) { but = uiDefIconTextButO(block, - but_type, - newop, + UI_BTYPE_BUT, + template_ui->open_op, WM_OP_INVOKE_DEFAULT, - (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, - (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), + ICON_FILEBROWSER, + text, 0, 0, w, - but_height, + UI_UNIT_Y, NULL); UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); } else { but = uiDefIconTextBut(block, - but_type, + UI_BTYPE_BUT, 0, - (id && !use_tab_but) ? ICON_DUPLICATE : ICON_ADD, - (id) ? "" : CTX_IFACE_(template_id_context(type), "New"), + ICON_FILEBROWSER, + text, 0, 0, w, - but_height, + UI_UNIT_Y, NULL, 0, 0, 0, 0, NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ADD_NEW)); + but->disabled_info = TIP_("Browsing for a new data-block is not supported here"); + UI_but_flag_enable(but, UI_BUT_DISABLED); } - if ((idfrom && idfrom->lib) || !editable) { + if (!RNA_property_editable_info(&template_ui->ptr, template_ui->prop, &but->disabled_info)) { UI_but_flag_enable(but, UI_BUT_DISABLED); } +} -#ifndef WITH_INTERNATIONAL - UNUSED_VARS(type); +static void template_id_unpack_button(uiBlock *block, ID *id) +{ + wmOperatorType *optype = WM_operatortype_find("FILE_OT_unpack_item", false); + uiBut *but; + + but = uiDefIconTextButO_ptr(block, + UI_BTYPE_BUT, + optype, + WM_OP_INVOKE_REGION_WIN, + ICON_PACKAGE, + NULL, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + NULL); + + if (!BKE_packedfile_id_check(id)) { + but->disabled_info = TIP_("File is not packed"); + UI_but_flag_enable(but, UI_BUT_DISABLED); + } + UI_but_operator_ptr_get(but); + + RNA_string_set(but->opptr, "id_name", id->name + 2); + RNA_int_set(but->opptr, "id_type", GS(id->name)); +} + +static void template_id_user_count_label(uiLayout *layout, const ID *id) +{ + char numstr[UI_MAX_NAME_STR]; + BLI_snprintf(numstr, + sizeof(numstr), + "%d %s", + ID_REAL_USERS(id), + ID_REAL_USERS(id) > 1 ? IFACE_("Users") : IFACE_("User")); + uiItemL(layout, numstr, ICON_NONE); +} + +static void template_id_menu(bContext *C, uiLayout *layout, void *arg) +{ + /* Button that spawned the menu. */ + const uiBut *menu_parent_but = arg; + TemplateID *template_ui = menu_parent_but->func_argN; + PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + ID *id = idptr.data; + uiBlock *block = uiLayoutGetBlock(layout); + + BLI_assert(id); + + template_id_new_button( + block, id, template_ui, TEMPLATE_ID_CREATE_NEW, idptr.type, false, false, UI_UNIT_X); + template_id_new_button( + block, id, template_ui, TEMPLATE_ID_CREATE_DUPLICATE, idptr.type, false, false, UI_UNIT_X); + template_id_unlink_button(block, template_ui, false); + +#ifdef USE_TEMPLATE_ID_MAKE_SINGLE_USER + template_id_make_single_user_button(block, id, template_ui); #endif + template_id_use_fake_user_button(block, &idptr); - return but; + if (template_ui->open_op || BKE_packedfile_id_check(id)) { + uiItemS(layout); + + template_id_open_button(block, id, template_ui, false); + template_id_unpack_button(block, id); + } + + /* Library operators. */ + if (ID_IS_OVERRIDE_LIBRARY(id)) { + uiItemS(layout); + + template_id_library_overridden_button(block, id, template_ui, UI_ID_LIB_OVERRIDE_RESET); + template_id_library_overridden_button(block, id, template_ui, UI_ID_LIB_OVERRIDE_REMOVE); + } + else if (ID_IS_LINKED(id)) { + uiItemS(layout); + + Main *bmain = CTX_data_main(C); + template_id_linked_operation_button(block, bmain, id, template_ui, UI_ID_MAKE_LOCAL); + template_id_linked_operation_button(block, bmain, id, template_ui, UI_ID_LIB_OVERRIDE_ADD); + } + + uiItemS(layout); + template_id_user_count_label(layout, id); +} + +static void template_id_name_button( + uiBlock *block, StructRNA *type, TemplateID *template_ui, int flag, const bool hide_extras) +{ + PointerRNA idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); + ID *id = idptr.data; + BIFIconID lib_icon = UI_icon_from_library(id); + + char name[UI_MAX_NAME_STR] = ""; + uiBut *but = uiDefIconTextButR(block, + UI_BTYPE_TEXT, + 0, + lib_icon, + name, + 0, + 0, + TEMPLATE_SEARCH_TEXTBUT_WIDTH, + TEMPLATE_SEARCH_TEXTBUT_HEIGHT, + &idptr, + "name", + -1, + 0, + 0, + 0, + 0, + RNA_struct_ui_description(type)); + /* Note: Also needed for the extra icons below (their operators access the TemplateID). */ + UI_but_funcN_set( + but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_RENAME)); + + const bool user_alert = (id->us <= 0); + if (user_alert) { + UI_but_flag_enable(but, UI_BUT_REDALERT); + } + + if (hide_extras) { + return; + } + + if (template_ui->duplicate_op && (flag & UI_ID_ADD_NEW)) { + UI_but_extra_operator_icon_add( + but, template_ui->duplicate_op, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE); + } + + if (id && (flag & UI_ID_DELETE)) { + const bool never_unlink = RNA_property_flag(template_ui->prop) & + (PROP_NEVER_UNLINK | PROP_NEVER_NULL); + if (template_ui->unlink_op) { + UI_but_extra_operator_icon_add(but, template_ui->unlink_op, WM_OP_INVOKE_DEFAULT, ICON_X); + } + else if (!never_unlink) { + UI_but_extra_operator_icon_add(but, "ED_OT_lib_unlink", WM_OP_INVOKE_DEFAULT, ICON_X); + } + } + + /* Disable fake user icon for now, only have it in the menu. */ + const bool add_extra_fake_user_icon = false; + if (add_extra_fake_user_icon && id->lib == NULL && + !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, ID_OB, ID_WS))) { + UI_but_extra_operator_icon_add(but, + "ED_OT_lib_fake_user_toggle", + WM_OP_INVOKE_DEFAULT, + ID_FAKE_USERS(id) ? ICON_FAKE_USER_ON : ICON_FAKE_USER_OFF); + } } static void template_ID(const bContext *C, @@ -875,9 +1407,6 @@ static void template_ID(const bContext *C, TemplateID *template_ui, StructRNA *type, int flag, - const char *newop, - const char *openop, - const char *unlinkop, const char *text, const bool live_icon, const bool hide_buttons) @@ -885,15 +1414,12 @@ static void template_ID(const bContext *C, uiBut *but; uiBlock *block; PointerRNA idptr; - // ListBase *lb; // UNUSED - ID *id, *idfrom; + ID *id; const bool editable = RNA_property_editable(&template_ui->ptr, template_ui->prop); const bool use_previews = template_ui->preview = (flag & UI_ID_PREVIEWS) != 0; idptr = RNA_property_pointer_get(&template_ui->ptr, template_ui->prop); id = idptr.data; - idfrom = template_ui->ptr.owner_id; - // lb = template_ui->idlb; /* Allow opertators to take the ID from context. */ uiLayoutSetContextPointer(layout, "id", &idptr); @@ -926,277 +1452,39 @@ static void template_ID(const bContext *C, /* text button with name */ if (id) { - char name[UI_MAX_NAME_STR]; - const bool user_alert = (id->us <= 0); - - // text_idbutton(id, name); - name[0] = '\0'; - but = uiDefButR(block, - UI_BTYPE_TEXT, - 0, - name, - 0, - 0, - TEMPLATE_SEARCH_TEXTBUT_WIDTH, - TEMPLATE_SEARCH_TEXTBUT_HEIGHT, - &idptr, - "name", - -1, - 0, - 0, - -1, - -1, - RNA_struct_ui_description(type)); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_RENAME)); - if (user_alert) { - UI_but_flag_enable(but, UI_BUT_REDALERT); - } - - if (id->lib) { - if (id->tag & LIB_TAG_INDIRECT) { - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_INDIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Indirect library data-block, cannot change")); - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - else { - const bool disabled = (!BKE_lib_id_make_local(CTX_data_main(C), id, true /* test */, 0) || - (idfrom && idfrom->lib)); - but = uiDefIconBut(block, - UI_BTYPE_BUT, - 0, - ICON_LIBRARY_DATA_DIRECT, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL, - 0, - 0, - 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_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_LOCAL)); - } - } - } - 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, - NULL, - 0, - 0, - 0, - 0, - TIP_("Library override of linked data-block, click to make fully local")); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OVERRIDE)); - } - - if ((ID_REAL_USERS(id) > 1) && (hide_buttons == false)) { - char numstr[32]; - short numstr_len; - - numstr_len = BLI_snprintf(numstr, sizeof(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, - NULL, - 0, - 0, - 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_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_ALONE)); - if ((!BKE_id_copy_is_allowed(id)) || (idfrom && idfrom->lib) || (!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->lib == NULL && !(ELEM(GS(id->name), ID_GR, ID_SCE, ID_SCR, ID_TXT, 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, - -1, - -1, - NULL); - } + template_id_name_button(block, type, template_ui, flag, hide_buttons); } - - 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); + /* If no ID is set, show a "new" button instead of a text button. */ + else if ((flag & UI_ID_ADD_NEW) && (hide_buttons == false)) { + template_id_new_button( + block, id, template_ui, TEMPLATE_ID_CREATE_NEW, type, 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_get(but); - - RNA_string_set(but->opptr, "id_name", id->name + 2); - RNA_int_set(but->opptr, "id_type", GS(id->name)); + if ((flag & UI_ID_OPEN) && !id) { + template_id_open_button(block, id, template_ui, flag & UI_ID_ADD_NEW); } - else if (flag & UI_ID_OPEN) { - const int w = id ? UI_UNIT_X : (flag & UI_ID_ADD_NEW) ? UI_UNIT_X * 3 : UI_UNIT_X * 6; - - if (openop) { - but = uiDefIconTextButO(block, - UI_BTYPE_BUT, - openop, - WM_OP_INVOKE_DEFAULT, - ICON_FILEBROWSER, - (id) ? "" : IFACE_("Open"), - 0, - 0, - w, - UI_UNIT_Y, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); - } - else { - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_FILEBROWSER, - (id) ? "" : IFACE_("Open"), - 0, - 0, - w, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - NULL); - UI_but_funcN_set( - but, template_id_cb, MEM_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_OPEN)); - } - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } + if (template_ui->idcode == ID_TE) { + uiTemplateTextureShow(layout, C, &template_ui->ptr, template_ui->prop); } - /* 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 = NULL; - - if (unlinkop) { - but = uiDefIconButO(block, - UI_BTYPE_BUT, - unlinkop, - WM_OP_INVOKE_DEFAULT, - ICON_X, - 0, - 0, - UI_UNIT_X, - UI_UNIT_Y, - NULL); - /* so we can access the template from operators, font unlinking needs this */ - UI_but_funcN_set(but, NULL, MEM_dupallocN(template_ui), NULL); - } - 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, - NULL, - 0, - 0, - 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_dupallocN(template_ui), POINTER_FROM_INT(UI_ID_DELETE)); - - if (RNA_property_flag(template_ui->prop) & PROP_NEVER_NULL) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } - } - - if (but) { - if ((idfrom && idfrom->lib) || !editable) { - UI_but_flag_enable(but, UI_BUT_DISABLED); - } - } + if (id && !hide_buttons) { + /* Additional operations menu ("Make Local", overrides, etc). */ + but = uiDefIconMenuBut(block, + template_id_menu, + NULL, + ICON_DOWNARROW_HLT, + 0, + 0, + UI_UNIT_X, + UI_UNIT_Y, + TIP_("Show more operations for the selected data-block")); + UI_but_type_set_menu_from_pulldown(but); + /* Same hack as ui_item_menu() to allow allocated arg. */ + but->poin = (char *)but; + but->func_argN = MEM_dupallocN(template_ui); } - if (template_ui->idcode == ID_TE) { - uiTemplateTextureShow(layout, C, &template_ui->ptr, template_ui->prop); - } UI_block_align_end(block); } @@ -1215,7 +1503,6 @@ static void template_ID_tabs(const bContext *C, TemplateID *template, StructRNA *type, int flag, - const char *newop, const char *menu) { const ARegion *region = CTX_wm_region(C); @@ -1263,22 +1550,20 @@ static void template_ID_tabs(const bContext *C, BLI_freelistN(&ordered); if (flag & UI_ID_ADD_NEW) { - const bool editable = RNA_property_editable(&template->ptr, template->prop); uiBut *but; if (active_ptr.type) { type = active_ptr.type; } - but = template_id_def_new_but(block, - active_ptr.data, - template, - type, - newop, - editable, - flag & UI_ID_OPEN, - true, - but_height); + but = template_id_new_button(block, + active_ptr.data, + template, + TEMPLATE_ID_CREATE_NEW, + type, + flag & UI_ID_OPEN, + true, + but_height); UI_but_drawflag_enable(but, but_align); } } @@ -1288,6 +1573,7 @@ static void ui_template_id(uiLayout *layout, PointerRNA *ptr, const char *propname, const char *newop, + const char *duplicateop, const char *openop, const char *unlinkop, /* Only respected by tabs (use_tabs). */ @@ -1317,6 +1603,10 @@ static void ui_template_id(uiLayout *layout, template_ui = MEM_callocN(sizeof(TemplateID), "TemplateID"); template_ui->ptr = *ptr; template_ui->prop = prop; + template_ui->new_op = newop; + template_ui->duplicate_op = duplicateop; + template_ui->unlink_op = unlinkop; + template_ui->open_op = openop; template_ui->prv_rows = prv_rows; template_ui->prv_cols = prv_cols; template_ui->scale = scale; @@ -1328,7 +1618,7 @@ static void ui_template_id(uiLayout *layout, template_ui->filter = 0; } - if (newop) { + if (newop || duplicateop) { flag |= UI_ID_ADD_NEW; } if (openop) { @@ -1346,21 +1636,11 @@ static void ui_template_id(uiLayout *layout, if (template_ui->idlb) { if (use_tabs) { layout = uiLayoutRow(layout, true); - template_ID_tabs(C, layout, template_ui, type, flag, newop, menu); + template_ID_tabs(C, layout, template_ui, type, flag, menu); } else { layout = uiLayoutRow(layout, true); - template_ID(C, - layout, - template_ui, - type, - flag, - newop, - openop, - unlinkop, - text, - live_icon, - hide_buttons); + template_ID(C, layout, template_ui, type, flag, text, live_icon, hide_buttons); } } @@ -1372,6 +1652,7 @@ void uiTemplateID(uiLayout *layout, PointerRNA *ptr, const char *propname, const char *newop, + const char *duplicateop, const char *openop, const char *unlinkop, int filter, @@ -1383,6 +1664,7 @@ void uiTemplateID(uiLayout *layout, ptr, propname, newop, + duplicateop, openop, unlinkop, NULL, @@ -1412,6 +1694,7 @@ void uiTemplateIDBrowse(uiLayout *layout, ptr, propname, newop, + NULL, openop, unlinkop, NULL, @@ -1443,6 +1726,7 @@ void uiTemplateIDPreview(uiLayout *layout, ptr, propname, newop, + NULL, openop, unlinkop, NULL, @@ -1475,6 +1759,7 @@ void uiTemplateGpencilColorPreview(uiLayout *layout, NULL, NULL, NULL, + NULL, UI_ID_BROWSE | UI_ID_PREVIEWS | UI_ID_DELETE, rows, cols, @@ -1503,6 +1788,7 @@ void uiTemplateIDTabs(uiLayout *layout, newop, NULL, NULL, + NULL, menu, NULL, UI_ID_BROWSE | UI_ID_RENAME, @@ -1662,35 +1948,25 @@ static void template_search_add_button_searchmenu(const bContext *C, static void template_search_add_button_name(uiBlock *block, PointerRNA *active_ptr, - const StructRNA *type) + const StructRNA *type, + const char *newop, + const char *unlinkop) { - uiDefAutoButR(block, - active_ptr, - RNA_struct_name_property(type), - 0, - "", - ICON_NONE, - 0, - 0, - TEMPLATE_SEARCH_TEXTBUT_WIDTH, - TEMPLATE_SEARCH_TEXTBUT_HEIGHT); -} - -static void template_search_add_button_operator(uiBlock *block, - const char *const operator_name, - const int opcontext, - const int icon, - const bool editable) -{ - if (!operator_name) { - return; + uiBut *but = uiDefAutoButR(block, + active_ptr, + RNA_struct_name_property(type), + 0, + "", + ICON_NONE, + 0, + 0, + TEMPLATE_SEARCH_TEXTBUT_WIDTH, + TEMPLATE_SEARCH_TEXTBUT_HEIGHT); + if (newop) { + UI_but_extra_operator_icon_add(but, newop, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE); } - - uiBut *but = uiDefIconButO( - block, UI_BTYPE_BUT, operator_name, opcontext, icon, 0, 0, UI_UNIT_X, UI_UNIT_Y, NULL); - - if (!editable) { - UI_but_drawflag_enable(but, UI_BUT_DISABLED); + if (unlinkop) { + UI_but_extra_operator_icon_add(but, unlinkop, WM_OP_INVOKE_REGION_WIN, ICON_X); } } @@ -1716,10 +1992,7 @@ static void template_search_buttons(const bContext *C, UI_block_align_begin(block); template_search_add_button_searchmenu(C, layout, block, template_search, editable, false); - template_search_add_button_name(block, &active_ptr, type); - template_search_add_button_operator( - block, newop, WM_OP_INVOKE_DEFAULT, ICON_DUPLICATE, editable); - template_search_add_button_operator(block, unlinkop, WM_OP_INVOKE_REGION_WIN, ICON_X, editable); + template_search_add_button_name(block, &active_ptr, type, newop, unlinkop); UI_block_align_end(block); } @@ -7191,6 +7464,7 @@ void uiTemplateCacheFile(uiLayout *layout, ptr, propname, NULL, + NULL, "CACHEFILE_OT_open", NULL, UI_TEMPLATE_ID_FILTER_ALL, |