diff options
Diffstat (limited to 'source/blender/editors/interface/interface_ops.cc')
-rw-r--r-- | source/blender/editors/interface/interface_ops.cc | 2273 |
1 files changed, 2273 insertions, 0 deletions
diff --git a/source/blender/editors/interface/interface_ops.cc b/source/blender/editors/interface/interface_ops.cc new file mode 100644 index 00000000000..2533a5454a5 --- /dev/null +++ b/source/blender/editors/interface/interface_ops.cc @@ -0,0 +1,2273 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2009 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edinterface + */ + +#include <cstring> + +#include "MEM_guardedalloc.h" + +#include "DNA_armature_types.h" +#include "DNA_material_types.h" +#include "DNA_modifier_types.h" /* for handling geometry nodes properties */ +#include "DNA_object_types.h" /* for OB_DATA_SUPPORT_ID */ +#include "DNA_screen_types.h" +#include "DNA_text_types.h" + +#include "BLI_blenlib.h" +#include "BLI_math_color.h" + +#include "BLF_api.h" +#include "BLT_lang.h" + +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_idprop.h" +#include "BKE_layer.h" +#include "BKE_lib_id.h" +#include "BKE_lib_override.h" +#include "BKE_material.h" +#include "BKE_node.h" +#include "BKE_report.h" +#include "BKE_screen.h" +#include "BKE_text.h" + +#include "IMB_colormanagement.h" + +#include "DEG_depsgraph.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_path.h" +#include "RNA_prototypes.h" +#include "RNA_types.h" + +#include "UI_interface.h" + +#include "interface_intern.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "ED_object.h" +#include "ED_paint.h" + +/* for Copy As Driver */ +#include "ED_keyframing.h" + +/* only for UI_OT_editsource */ +#include "BKE_main.h" +#include "BLI_ghash.h" +#include "ED_screen.h" +#include "ED_text.h" + +/* -------------------------------------------------------------------- */ +/** \name Immediate redraw helper + * + * Generally handlers shouldn't do any redrawing, that includes the layout/button definitions. That + * violates the Model-View-Controller pattern. + * + * But there are some operators which really need to re-run the layout definitions for various + * reasons. For example, "Edit Source" does it to find out which exact Python code added a button. + * Other operators may need to access buttons that aren't currently visible. In Blender's UI code + * design that typically means just not adding the button in the first place, for a particular + * redraw. So the operator needs to change context and re-create the layout, so the button becomes + * available to act on. + * + * \{ */ + +static void ui_region_redraw_immediately(bContext *C, ARegion *region) +{ + ED_region_do_layout(C, region); + WM_draw_region_viewport_bind(region); + ED_region_do_draw(C, region); + WM_draw_region_viewport_unbind(region); + region->do_draw = false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Data Path Operator + * \{ */ + +static bool copy_data_path_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_data_path_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + ID *id; + + const bool full_path = RNA_boolean_get(op->ptr, "full_path"); + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id != nullptr) { + if (full_path) { + if (prop) { + path = RNA_path_full_property_py_ex(bmain, &ptr, prop, index, true); + } + else { + path = RNA_path_full_struct_py(bmain, &ptr); + } + } + else { + path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, 0, -1, &id); + + if (!path) { + path = RNA_path_from_ID_to_property(&ptr, prop); + } + } + + if (path) { + WM_clipboard_text_set(path, false); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_data_path_button(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Copy Data Path"; + ot->idname = "UI_OT_copy_data_path_button"; + ot->description = "Copy the RNA data path for this property to the clipboard"; + + /* callbacks */ + ot->exec = copy_data_path_button_exec; + ot->poll = copy_data_path_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; + + /* properties */ + prop = RNA_def_boolean(ot->srna, "full_path", false, "full_path", "Copy full data path"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy As Driver Operator + * \{ */ + +static bool copy_as_driver_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + char *path; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop && + ELEM(RNA_property_type(prop), PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM) && + (index >= 0 || !RNA_property_array_check(prop))) { + path = RNA_path_from_ID_to_property(&ptr, prop); + + if (path) { + MEM_freeN(path); + return true; + } + } + + return false; +} + +static int copy_as_driver_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to create driver using property retrieved from UI */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.owner_id && ptr.data && prop) { + ID *id; + const int dim = RNA_property_array_dimension(&ptr, prop, nullptr); + char *path = RNA_path_from_real_ID_to_property_index(bmain, &ptr, prop, dim, index, &id); + + if (path) { + ANIM_copy_as_driver(id, path, RNA_property_identifier(prop)); + MEM_freeN(path); + return OPERATOR_FINISHED; + } + + BKE_reportf(op->reports, RPT_ERROR, "Could not compute a valid data path"); + return OPERATOR_CANCELLED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_as_driver_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy as New Driver"; + ot->idname = "UI_OT_copy_as_driver_button"; + ot->description = + "Create a new driver with this property as input, and copy it to the " + "clipboard. Use Paste Driver to add it to the target property, or Paste " + "Driver Variables to extend an existing driver"; + + /* callbacks */ + ot->exec = copy_as_driver_button_exec; + ot->poll = copy_as_driver_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy Python Command Operator + * \{ */ + +static bool copy_python_command_button_poll(bContext *C) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + return true; + } + + return false; +} + +static int copy_python_command_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but && (but->optype != nullptr)) { + PointerRNA *opptr; + char *str; + opptr = UI_but_operator_ptr_get(but); /* allocated when needed, the button owns it */ + + str = WM_operator_pystring_ex(C, nullptr, false, true, but->optype, opptr); + + WM_clipboard_text_set(str, false); + + MEM_freeN(str); + + return OPERATOR_FINISHED; + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_copy_python_command_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy Python Command"; + ot->idname = "UI_OT_copy_python_command_button"; + ot->description = "Copy the Python command matching this button"; + + /* callbacks */ + ot->exec = copy_python_command_button_exec; + ot->poll = copy_python_command_button_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reset to Default Values Button Operator + * \{ */ + +static int operator_button_property_finish(bContext *C, PointerRNA *ptr, PropertyRNA *prop) +{ + ID *id = ptr->owner_id; + + /* perform updates required for this property */ + RNA_property_update(C, ptr, prop); + + /* as if we pressed the button */ + UI_context_active_but_prop_handle(C, false); + + /* Since we don't want to undo _all_ edits to settings, eg window + * edits on the screen or on operator settings. + * it might be better to move undo's inline - campbell */ + if (id && ID_CHECK_UNDO(id)) { + /* do nothing, go ahead with undo */ + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int operator_button_property_finish_with_undo(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop) +{ + /* Perform updates required for this property. */ + RNA_property_update(C, ptr, prop); + + /* As if we pressed the button. */ + UI_context_active_but_prop_handle(C, true); + + return OPERATOR_FINISHED; +} + +static bool reset_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + return (ptr.data && prop && RNA_property_editable(&ptr, prop)); +} + +static int reset_default_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_reset(&ptr, prop, (all) ? -1 : index)) { + return operator_button_property_finish_with_undo(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_reset_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reset to Default Value"; + ot->idname = "UI_OT_reset_default_button"; + ot->description = "Reset this property's value to its default value"; + + /* callbacks */ + ot->poll = reset_default_button_poll; + ot->exec = reset_default_button_exec; + + /* flags */ + /* Don't set #OPTYPE_UNDO because #operator_button_property_finish_with_undo + * is responsible for the undo push. */ + ot->flag = 0; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Assign Value as Default Button Operator + * \{ */ + +static bool assign_default_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + const PropertyType type = RNA_property_type(prop); + + return RNA_property_is_idprop(prop) && !RNA_property_array_check(prop) && + ELEM(type, PROP_INT, PROP_FLOAT); + } + + return false; +} + +static int assign_default_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop)) { + if (RNA_property_assign_default(&ptr, prop)) { + return operator_button_property_finish(C, &ptr, prop); + } + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_assign_default_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Assign Value as Default"; + ot->idname = "UI_OT_assign_default_button"; + ot->description = "Set this property's current value as the new default"; + + /* callbacks */ + ot->poll = assign_default_button_poll; + ot->exec = assign_default_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Unset Property Button Operator + * \{ */ + +static int unset_property_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + /* try to unset the nominated property */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data && prop && RNA_property_editable(&ptr, prop) && + /* RNA_property_is_idprop(prop) && */ + RNA_property_is_set(&ptr, prop)) { + RNA_property_unset(&ptr, prop); + return operator_button_property_finish(C, &ptr, prop); + } + + return OPERATOR_CANCELLED; +} + +static void UI_OT_unset_property_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Unset Property"; + ot->idname = "UI_OT_unset_property_button"; + ot->description = "Clear the property and use default or generated value in operators"; + + /* callbacks */ + ot->poll = ED_operator_regionactive; + ot->exec = unset_property_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Define Override Type Operator + * \{ */ + +/* Note that we use different values for UI/UX than 'real' override operations, user does not care + * whether it's added or removed for the differential operation e.g. */ +enum { + UIOverride_Type_NOOP = 0, + UIOverride_Type_Replace = 1, + UIOverride_Type_Difference = 2, /* Add/subtract */ + UIOverride_Type_Factor = 3, /* Multiply */ + /* TODO: should/can we expose insert/remove ones for collections? Doubt it... */ +}; + +static EnumPropertyItem override_type_items[] = { + {UIOverride_Type_NOOP, + "NOOP", + 0, + "NoOp", + "'No-Operation', place holder preventing automatic override to ever affect the property"}, + {UIOverride_Type_Replace, + "REPLACE", + 0, + "Replace", + "Completely replace value from linked data by local one"}, + {UIOverride_Type_Difference, + "DIFFERENCE", + 0, + "Difference", + "Store difference to linked data value"}, + {UIOverride_Type_Factor, + "FACTOR", + 0, + "Factor", + "Store factor to linked data value (useful e.g. for scale)"}, + {0, nullptr, 0, nullptr, nullptr}, +}; + +static bool override_type_set_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE)); +} + +static int override_type_set_button_exec(bContext *C, wmOperator *op) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + bool created; + const bool all = RNA_boolean_get(op->ptr, "all"); + const int op_type = RNA_enum_get(op->ptr, "type"); + + short operation; + + switch (op_type) { + case UIOverride_Type_NOOP: + operation = IDOVERRIDE_LIBRARY_OP_NOOP; + break; + case UIOverride_Type_Replace: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + break; + case UIOverride_Type_Difference: + /* override code will automatically switch to subtract if needed. */ + operation = IDOVERRIDE_LIBRARY_OP_ADD; + break; + case UIOverride_Type_Factor: + operation = IDOVERRIDE_LIBRARY_OP_MULTIPLY; + break; + default: + operation = IDOVERRIDE_LIBRARY_OP_REPLACE; + BLI_assert(0); + break; + } + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + BLI_assert(ptr.owner_id != nullptr); + + if (all) { + index = -1; + } + + IDOverrideLibraryPropertyOperation *opop = RNA_property_override_property_operation_get( + CTX_data_main(C), &ptr, prop, operation, index, true, nullptr, &created); + + if (opop == nullptr) { + /* Sometimes e.g. RNA cannot generate a path to the given property. */ + BKE_reportf(op->reports, RPT_WARNING, "Failed to create the override operation"); + return OPERATOR_CANCELLED; + } + + if (!created) { + opop->operation = operation; + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static int override_type_set_button_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ +#if 0 /* Disabled for now */ + return WM_menu_invoke_ex(C, op, WM_OP_INVOKE_DEFAULT); +#else + RNA_enum_set(op->ptr, "type", IDOVERRIDE_LIBRARY_OP_REPLACE); + return override_type_set_button_exec(C, op); +#endif +} + +static void UI_OT_override_type_set_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Define Override Type"; + ot->idname = "UI_OT_override_type_set_button"; + ot->description = "Create an override operation, or set the type of an existing one"; + + /* callbacks */ + ot->poll = override_type_set_button_poll; + ot->exec = override_type_set_button_exec; + ot->invoke = override_type_set_button_invoke; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); + ot->prop = RNA_def_enum(ot->srna, + "type", + override_type_items, + UIOverride_Type_Replace, + "Type", + "Type of override operation"); + /* TODO: add itemf callback, not all options are available for all data types... */ +} + +static bool override_remove_button_poll(bContext *C) +{ + PointerRNA ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + const uint override_status = RNA_property_override_library_status( + CTX_data_main(C), &ptr, prop, index); + + return (ptr.data && ptr.owner_id && prop && (override_status & RNA_OVERRIDE_STATUS_OVERRIDDEN)); +} + +static int override_remove_button_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, id_refptr, src; + PropertyRNA *prop; + int index; + const bool all = RNA_boolean_get(op->ptr, "all"); + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + ID *id = ptr.owner_id; + IDOverrideLibraryProperty *oprop = RNA_property_override_property_find(bmain, &ptr, prop, &id); + BLI_assert(oprop != nullptr); + BLI_assert(id != nullptr && id->override_library != nullptr); + + const bool is_template = ID_IS_OVERRIDE_LIBRARY_TEMPLATE(id); + + /* We need source (i.e. linked data) to restore values of deleted overrides... + * If this is an override template, we obviously do not need to restore anything. */ + if (!is_template) { + PropertyRNA *src_prop; + RNA_id_pointer_create(id->override_library->reference, &id_refptr); + if (!RNA_path_resolve_property(&id_refptr, oprop->rna_path, &src, &src_prop)) { + BLI_assert_msg(0, "Failed to create matching source (linked data) RNA pointer"); + } + } + + if (!all && index != -1) { + bool is_strict_find; + /* Remove override operation for given item, + * add singular operations for the other items as needed. */ + IDOverrideLibraryPropertyOperation *opop = BKE_lib_override_library_property_operation_find( + oprop, nullptr, nullptr, index, index, false, &is_strict_find); + BLI_assert(opop != nullptr); + if (!is_strict_find) { + /* No specific override operation, we have to get generic one, + * and create item-specific override operations for all but given index, + * before removing generic one. */ + for (int idx = RNA_property_array_length(&ptr, prop); idx--;) { + if (idx != index) { + BKE_lib_override_library_property_operation_get( + oprop, opop->operation, nullptr, nullptr, idx, idx, true, nullptr, nullptr); + } + } + } + BKE_lib_override_library_property_operation_delete(oprop, opop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, index); + } + if (BLI_listbase_is_empty(&oprop->operations)) { + BKE_lib_override_library_property_delete(id->override_library, oprop); + } + } + else { + /* Just remove whole generic override operation of this property. */ + BKE_lib_override_library_property_delete(id->override_library, oprop); + if (!is_template) { + RNA_property_copy(bmain, &ptr, &src, prop, -1); + } + } + + /* Outliner e.g. has to be aware of this change. */ + WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, nullptr); + + return operator_button_property_finish(C, &ptr, prop); +} + +static void UI_OT_override_remove_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Remove Override"; + ot->idname = "UI_OT_override_remove_button"; + ot->description = "Remove an override operation"; + + /* callbacks */ + ot->poll = override_remove_button_poll; + ot->exec = override_remove_button_exec; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean( + ot->srna, "all", true, "All", "Reset to default values all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Copy To Selected Operator + * \{ */ + +#define NOT_NULL(assignment) ((assignment) != nullptr) +#define NOT_RNA_NULL(assignment) ((assignment).data != nullptr) + +static void ui_context_selected_bones_via_pose(bContext *C, ListBase *r_lb) +{ + ListBase lb; + lb = CTX_data_collection_get(C, "selected_pose_bones"); + + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + bPoseChannel *pchan = static_cast<bPoseChannel *>(link->ptr.data); + RNA_pointer_create(link->ptr.owner_id, &RNA_Bone, pchan->bone, &link->ptr); + } + } + + *r_lb = lb; +} + +bool UI_context_copy_to_selected_list(bContext *C, + PointerRNA *ptr, + PropertyRNA *prop, + ListBase *r_lb, + bool *r_use_path_from_id, + char **r_path) +{ + *r_use_path_from_id = false; + *r_path = nullptr; + /* special case for bone constraints */ + char *path_from_bone = nullptr; + /* Remove links from the collection list which don't contain 'prop'. */ + bool ensure_list_items_contain_prop = false; + + /* PropertyGroup objects don't have a reference to the struct that actually owns + * them, so it is normally necessary to do a brute force search to find it. This + * handles the search for non-ID owners by using the 'active' reference as a hint + * to preserve efficiency. Only properties defined through RNA are handled, as + * custom properties cannot be assumed to be valid for all instances. + * + * Properties owned by the ID are handled by the 'if (ptr->owner_id)' case below. + */ + if (!RNA_property_is_idprop(prop) && RNA_struct_is_a(ptr->type, &RNA_PropertyGroup)) { + PointerRNA owner_ptr; + char *idpath = nullptr; + + /* First, check the active PoseBone and PoseBone->Bone. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type(C, "active_pose_bone", &RNA_PoseBone))) { + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast<IDProperty *>(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else { + bPoseChannel *pchan = static_cast<bPoseChannel *>(owner_ptr.data); + RNA_pointer_create(owner_ptr.owner_id, &RNA_Bone, pchan->bone, &owner_ptr); + + if (NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast<IDProperty *>(ptr->data)))) { + ui_context_selected_bones_via_pose(C, r_lb); + } + } + } + + if (idpath == nullptr) { + /* Check the active EditBone if in edit mode. */ + if (NOT_RNA_NULL( + owner_ptr = CTX_data_pointer_get_type_silent(C, "active_bone", &RNA_EditBone)) && + NOT_NULL(idpath = RNA_path_from_struct_to_idproperty( + &owner_ptr, static_cast<IDProperty *>(ptr->data)))) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + + /* Add other simple cases here (Node, NodeSocket, Sequence, ViewLayer etc). */ + } + + if (idpath) { + *r_path = BLI_sprintfN("%s.%s", idpath, RNA_property_identifier(prop)); + MEM_freeN(idpath); + return true; + } + } + + if (RNA_struct_is_a(ptr->type, &RNA_EditBone)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_PoseBone)) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Bone)) { + ui_context_selected_bones_via_pose(C, r_lb); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Sequence)) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + else if (RNA_struct_is_a(ptr->type, &RNA_FCurve)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_fcurves"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Keyframe)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_keyframes"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Action)) { + *r_lb = CTX_data_collection_get(C, "selected_editable_actions"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_NlaStrip)) { + *r_lb = CTX_data_collection_get(C, "selected_nla_strips"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_MovieTrackingTrack)) { + *r_lb = CTX_data_collection_get(C, "selected_movieclip_tracks"); + } + else if (RNA_struct_is_a(ptr->type, &RNA_Constraint) && + (path_from_bone = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_PoseBone)) != + nullptr) { + *r_lb = CTX_data_collection_get(C, "selected_pose_bones"); + *r_path = path_from_bone; + } + else if (RNA_struct_is_a(ptr->type, &RNA_Node) || RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + ListBase lb = {nullptr, nullptr}; + char *path = nullptr; + bNode *node = nullptr; + + /* Get the node we're editing */ + if (RNA_struct_is_a(ptr->type, &RNA_NodeSocket)) { + bNodeTree *ntree = (bNodeTree *)ptr->owner_id; + bNodeSocket *sock = static_cast<bNodeSocket *>(ptr->data); + if (nodeFindNode(ntree, sock, &node, nullptr)) { + if ((path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Node)) != nullptr) { + /* we're good! */ + } + else { + node = nullptr; + } + } + } + else { + node = static_cast<bNode *>(ptr->data); + } + + /* Now filter by type */ + if (node) { + lb = CTX_data_collection_get(C, "selected_nodes"); + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + bNode *node_data = static_cast<bNode *>(link->ptr.data); + + if (node_data->type != node->type) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (ptr->owner_id) { + ID *id = ptr->owner_id; + + if (GS(id->name) == ID_OB) { + *r_lb = CTX_data_collection_get(C, "selected_editable_objects"); + *r_use_path_from_id = true; + *r_path = RNA_path_from_ID_to_property(ptr, prop); + } + else if (OB_DATA_SUPPORT_ID(GS(id->name))) { + /* check we're using the active object */ + const short id_code = GS(id->name); + ListBase lb = CTX_data_collection_get(C, "selected_editable_objects"); + char *path = RNA_path_from_ID_to_property(ptr, prop); + + /* de-duplicate obdata */ + if (!BLI_listbase_is_empty(&lb)) { + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + if (ob->data) { + ID *id_data = static_cast<ID *>(ob->data); + id_data->tag |= LIB_TAG_DOIT; + } + } + + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, &lb) { + Object *ob = (Object *)link->ptr.owner_id; + ID *id_data = static_cast<ID *>(ob->data); + + if ((id_data == nullptr) || (id_data->tag & LIB_TAG_DOIT) == 0 || + ID_IS_LINKED(id_data) || (GS(id_data->name) != id_code)) { + BLI_remlink(&lb, link); + MEM_freeN(link); + } + else { + /* Avoid prepending 'data' to the path. */ + RNA_id_pointer_create(id_data, &link->ptr); + } + + if (id_data) { + id_data->tag &= ~LIB_TAG_DOIT; + } + } + } + + *r_lb = lb; + *r_path = path; + } + else if (GS(id->name) == ID_SCE) { + /* Sequencer's ID is scene :/ */ + /* Try to recursively find an RNA_Sequence ancestor, + * to handle situations like T41062... */ + if ((*r_path = RNA_path_resolve_from_type_to_property(ptr, prop, &RNA_Sequence)) != + nullptr) { + /* Special case when we do this for 'Sequence.lock'. + * (if the sequence is locked, it won't be in "selected_editable_sequences"). */ + const char *prop_id = RNA_property_identifier(prop); + if (STREQ(prop_id, "lock")) { + *r_lb = CTX_data_collection_get(C, "selected_sequences"); + } + else { + *r_lb = CTX_data_collection_get(C, "selected_editable_sequences"); + } + /* Account for properties only being available for some sequence types. */ + ensure_list_items_contain_prop = true; + } + } + return (*r_path != nullptr); + } + else { + return false; + } + + if (ensure_list_items_contain_prop) { + const char *prop_id = RNA_property_identifier(prop); + LISTBASE_FOREACH_MUTABLE (CollectionPointerLink *, link, r_lb) { + if ((ptr->type != link->ptr.type) && + (RNA_struct_type_find_property(link->ptr.type, prop_id) != prop)) { + BLI_remlink(r_lb, link); + MEM_freeN(link); + } + } + } + + return true; +} + +bool UI_context_copy_to_selected_check(PointerRNA *ptr, + PointerRNA *ptr_link, + PropertyRNA *prop, + const char *path, + bool use_path_from_id, + PointerRNA *r_ptr, + PropertyRNA **r_prop) +{ + PointerRNA idptr; + PropertyRNA *lprop; + PointerRNA lptr; + + if (ptr_link->data == ptr->data) { + return false; + } + + if (use_path_from_id) { + /* Path relative to ID. */ + lprop = nullptr; + RNA_id_pointer_create(ptr_link->owner_id, &idptr); + RNA_path_resolve_property(&idptr, path, &lptr, &lprop); + } + else if (path) { + /* Path relative to elements from list. */ + lprop = nullptr; + RNA_path_resolve_property(ptr_link, path, &lptr, &lprop); + } + else { + lptr = *ptr_link; + lprop = prop; + } + + if (lptr.data == ptr->data) { + /* temp_ptr might not be the same as ptr_link! */ + return false; + } + + /* Skip non-existing properties on link. This was previously covered with the `lprop != prop` + * check but we are now more permissive when it comes to ID properties, see below. */ + if (lprop == nullptr) { + return false; + } + + if (RNA_property_type(lprop) != RNA_property_type(prop)) { + return false; + } + + /* Check property pointers matching. + * For ID properties, these pointers match: + * - If the property is API defined on an existing class (and they are equally named). + * - Never for ID properties on specific ID (even if they are equally named). + * - Never for NodesModifierSettings properties (even if they are equally named). + * + * Be permissive on ID properties in the following cases: + * - #NodesModifierSettings properties + * - (special check: only if the node-group matches, since the 'Input_n' properties are name + * based and similar on potentially very different node-groups). + * - ID properties on specific ID + * - (no special check, copying seems OK [even if type does not match -- does not do anything + * then]) + */ + bool ignore_prop_eq = RNA_property_is_idprop(lprop) && RNA_property_is_idprop(prop); + if (RNA_struct_is_a(lptr.type, &RNA_NodesModifier) && + RNA_struct_is_a(ptr->type, &RNA_NodesModifier)) { + ignore_prop_eq = false; + + NodesModifierData *nmd_link = (NodesModifierData *)lptr.data; + NodesModifierData *nmd_src = (NodesModifierData *)ptr->data; + if (nmd_link->node_group == nmd_src->node_group) { + ignore_prop_eq = true; + } + } + + if ((lprop != prop) && !ignore_prop_eq) { + return false; + } + + if (!RNA_property_editable(&lptr, lprop)) { + return false; + } + + if (r_ptr) { + *r_ptr = lptr; + } + if (r_prop) { + *r_prop = lprop; + } + + return true; +} + +/** + * Called from both exec & poll. + * + * \note Normally we wouldn't call a loop from within a poll function, + * however this is a special case, and for regular poll calls, getting + * the context from the button will fail early. + */ +static bool copy_to_selected_button(bContext *C, bool all, bool poll) +{ + Main *bmain = CTX_data_main(C); + PointerRNA ptr, lptr; + PropertyRNA *prop, *lprop; + bool success = false; + int index; + + /* try to reset the nominated setting to its default value */ + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* if there is a valid property that is editable... */ + if (ptr.data == nullptr || prop == nullptr) { + return false; + } + + char *path = nullptr; + bool use_path_from_id; + ListBase lb = {nullptr}; + + if (!UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path)) { + return false; + } + if (BLI_listbase_is_empty(&lb)) { + MEM_SAFE_FREE(path); + return false; + } + + LISTBASE_FOREACH (CollectionPointerLink *, link, &lb) { + if (link->ptr.data == ptr.data) { + continue; + } + + if (!UI_context_copy_to_selected_check( + &ptr, &link->ptr, prop, path, use_path_from_id, &lptr, &lprop)) { + continue; + } + + if (poll) { + success = true; + break; + } + if (RNA_property_copy(bmain, &lptr, &ptr, prop, (all) ? -1 : index)) { + RNA_property_update(C, &lptr, prop); + success = true; + } + } + + MEM_SAFE_FREE(path); + BLI_freelistN(&lb); + + return success; +} + +static bool copy_to_selected_button_poll(bContext *C) +{ + return copy_to_selected_button(C, false, true); +} + +static int copy_to_selected_button_exec(bContext *C, wmOperator *op) +{ + bool success; + + const bool all = RNA_boolean_get(op->ptr, "all"); + + success = copy_to_selected_button(C, all, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_copy_to_selected_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Copy to Selected"; + ot->idname = "UI_OT_copy_to_selected_button"; + ot->description = + "Copy the property's value from the active item to the same property of all selected items " + "if the same property exists"; + + /* callbacks */ + ot->poll = copy_to_selected_button_poll; + ot->exec = copy_to_selected_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "all", true, "All", "Copy to selected all elements of the array"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Jump to Target Operator + * \{ */ + +/** Jump to the object or bone referenced by the pointer, or check if it is possible. */ +static bool jump_to_target_ptr(bContext *C, PointerRNA ptr, const bool poll) +{ + if (RNA_pointer_is_null(&ptr)) { + return false; + } + + /* Verify pointer type. */ + char bone_name[MAXBONENAME]; + const StructRNA *target_type = nullptr; + + if (ELEM(ptr.type, &RNA_EditBone, &RNA_PoseBone, &RNA_Bone)) { + RNA_string_get(&ptr, "name", bone_name); + if (bone_name[0] != '\0') { + target_type = &RNA_Bone; + } + } + else if (RNA_struct_is_a(ptr.type, &RNA_Object)) { + target_type = &RNA_Object; + } + + if (target_type == nullptr) { + return false; + } + + /* Find the containing Object. */ + ViewLayer *view_layer = CTX_data_view_layer(C); + Base *base = nullptr; + const short id_type = GS(ptr.owner_id->name); + if (id_type == ID_OB) { + base = BKE_view_layer_base_find(view_layer, (Object *)ptr.owner_id); + } + else if (OB_DATA_SUPPORT_ID(id_type)) { + base = ED_object_find_first_by_data_id(view_layer, ptr.owner_id); + } + + bool ok = false; + if ((base == nullptr) || ((target_type == &RNA_Bone) && (base->object->type != OB_ARMATURE))) { + /* pass */ + } + else if (poll) { + ok = true; + } + else { + /* Make optional. */ + const bool reveal_hidden = true; + /* Select and activate the target. */ + if (target_type == &RNA_Bone) { + ok = ED_object_jump_to_bone(C, base->object, bone_name, reveal_hidden); + } + else if (target_type == &RNA_Object) { + ok = ED_object_jump_to_object(C, base->object, reveal_hidden); + } + else { + BLI_assert(0); + } + } + return ok; +} + +/** + * Jump to the object or bone referred to by the current UI field value. + * + * \note quite heavy for a poll callback, but the operator is only + * used as a right click menu item for certain UI field types, and + * this will fail quickly if the context is completely unsuitable. + */ +static bool jump_to_target_button(bContext *C, bool poll) +{ + PointerRNA ptr, target_ptr; + PropertyRNA *prop; + int index; + + UI_context_active_but_prop_get(C, &ptr, &prop, &index); + + /* If there is a valid property... */ + if (ptr.data && prop) { + const PropertyType type = RNA_property_type(prop); + + /* For pointer properties, use their value directly. */ + if (type == PROP_POINTER) { + target_ptr = RNA_property_pointer_get(&ptr, prop); + + return jump_to_target_ptr(C, target_ptr, poll); + } + /* For string properties with prop_search, look up the search collection item. */ + if (type == PROP_STRING) { + const uiBut *but = UI_context_active_but_get(C); + const uiButSearch *search_but = (but->type == UI_BTYPE_SEARCH_MENU) ? (uiButSearch *)but : + nullptr; + + if (search_but && search_but->items_update_fn == ui_rna_collection_search_update_fn) { + uiRNACollectionSearch *coll_search = static_cast<uiRNACollectionSearch *>(search_but->arg); + + char str_buf[MAXBONENAME]; + char *str_ptr = RNA_property_string_get_alloc( + &ptr, prop, str_buf, sizeof(str_buf), nullptr); + + int found = RNA_property_collection_lookup_string( + &coll_search->search_ptr, coll_search->search_prop, str_ptr, &target_ptr); + + if (str_ptr != str_buf) { + MEM_freeN(str_ptr); + } + + if (found) { + return jump_to_target_ptr(C, target_ptr, poll); + } + } + } + } + + return false; +} + +bool ui_jump_to_target_button_poll(bContext *C) +{ + return jump_to_target_button(C, true); +} + +static int jump_to_target_button_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const bool success = jump_to_target_button(C, false); + + return (success) ? OPERATOR_FINISHED : OPERATOR_CANCELLED; +} + +static void UI_OT_jump_to_target_button(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Jump to Target"; + ot->idname = "UI_OT_jump_to_target_button"; + ot->description = "Switch to the target object or bone"; + + /* callbacks */ + ot->poll = ui_jump_to_target_button_poll; + ot->exec = jump_to_target_button_exec; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Python Source Operator + * \{ */ + +#ifdef WITH_PYTHON + +/* ------------------------------------------------------------------------- */ +/* EditSource Utility funcs and operator, + * NOTE: this includes utility functions and button matching checks. */ + +struct uiEditSourceStore { + uiBut but_orig; + GHash *hash; +}; + +struct uiEditSourceButStore { + char py_dbg_fn[FILE_MAX]; + int py_dbg_line_number; +}; + +/* should only ever be set while the edit source operator is running */ +static uiEditSourceStore *ui_editsource_info = nullptr; + +bool UI_editsource_enable_check(void) +{ + return (ui_editsource_info != nullptr); +} + +static void ui_editsource_active_but_set(uiBut *but) +{ + BLI_assert(ui_editsource_info == nullptr); + + ui_editsource_info = MEM_cnew<uiEditSourceStore>(__func__); + memcpy(&ui_editsource_info->but_orig, but, sizeof(uiBut)); + + ui_editsource_info->hash = BLI_ghash_ptr_new(__func__); +} + +static void ui_editsource_active_but_clear() +{ + BLI_ghash_free(ui_editsource_info->hash, nullptr, MEM_freeN); + MEM_freeN(ui_editsource_info); + ui_editsource_info = nullptr; +} + +static bool ui_editsource_uibut_match(uiBut *but_a, uiBut *but_b) +{ +# if 0 + printf("matching buttons: '%s' == '%s'\n", but_a->drawstr, but_b->drawstr); +# endif + + /* this just needs to be a 'good-enough' comparison so we can know beyond + * reasonable doubt that these buttons are the same between redraws. + * if this fails it only means edit-source fails - campbell */ + if (BLI_rctf_compare(&but_a->rect, &but_b->rect, FLT_EPSILON) && (but_a->type == but_b->type) && + (but_a->rnaprop == but_b->rnaprop) && (but_a->optype == but_b->optype) && + (but_a->unit_type == but_b->unit_type) && + STREQLEN(but_a->drawstr, but_b->drawstr, UI_MAX_DRAW_STR)) { + return true; + } + return false; +} + +extern "C" { +void PyC_FileAndNum_Safe(const char **r_filename, int *r_lineno); +} + +void UI_editsource_active_but_test(uiBut *but) +{ + + uiEditSourceButStore *but_store = MEM_cnew<uiEditSourceButStore>(__func__); + + const char *fn; + int line_number = -1; + +# if 0 + printf("comparing buttons: '%s' == '%s'\n", but->drawstr, ui_editsource_info->but_orig.drawstr); +# endif + + PyC_FileAndNum_Safe(&fn, &line_number); + + if (line_number != -1) { + BLI_strncpy(but_store->py_dbg_fn, fn, sizeof(but_store->py_dbg_fn)); + but_store->py_dbg_line_number = line_number; + } + else { + but_store->py_dbg_fn[0] = '\0'; + but_store->py_dbg_line_number = -1; + } + + BLI_ghash_insert(ui_editsource_info->hash, but, but_store); +} + +void UI_editsource_but_replace(const uiBut *old_but, uiBut *new_but) +{ + uiEditSourceButStore *but_store = static_cast<uiEditSourceButStore *>( + BLI_ghash_lookup(ui_editsource_info->hash, old_but)); + if (but_store) { + BLI_ghash_remove(ui_editsource_info->hash, old_but, nullptr, nullptr); + BLI_ghash_insert(ui_editsource_info->hash, new_but, but_store); + } +} + +static int editsource_text_edit(bContext *C, + wmOperator *op, + const char filepath[FILE_MAX], + const int line) +{ + Main *bmain = CTX_data_main(C); + Text *text = nullptr; + + /* Developers may wish to copy-paste to an external editor. */ + printf("%s:%d\n", filepath, line); + + LISTBASE_FOREACH (Text *, text_iter, &bmain->texts) { + if (text_iter->filepath && BLI_path_cmp(text_iter->filepath, filepath) == 0) { + text = text_iter; + break; + } + } + + if (text == nullptr) { + text = BKE_text_load(bmain, filepath, BKE_main_blendfile_path(bmain)); + } + + if (text == nullptr) { + BKE_reportf(op->reports, RPT_WARNING, "File '%s' cannot be opened", filepath); + return OPERATOR_CANCELLED; + } + + txt_move_toline(text, line - 1, false); + + /* naughty!, find text area to set, not good behavior + * but since this is a developer tool lets allow it - campbell */ + if (!ED_text_activate_in_screen(C, text)) { + BKE_reportf(op->reports, RPT_INFO, "See '%s' in the text editor", text->id.name + 2); + } + + WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text); + + return OPERATOR_FINISHED; +} + +static int editsource_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + + if (but) { + GHashIterator ghi; + uiEditSourceButStore *but_store = nullptr; + + ARegion *region = CTX_wm_region(C); + int ret; + + /* needed else the active button does not get tested */ + UI_screen_free_active_but_highlight(C, CTX_wm_screen(C)); + + // printf("%s: begin\n", __func__); + + /* take care not to return before calling ui_editsource_active_but_clear */ + ui_editsource_active_but_set(but); + + /* redraw and get active button python info */ + ui_region_redraw_immediately(C, region); + + for (BLI_ghashIterator_init(&ghi, ui_editsource_info->hash); + BLI_ghashIterator_done(&ghi) == false; + BLI_ghashIterator_step(&ghi)) { + uiBut *but_key = static_cast<uiBut *>(BLI_ghashIterator_getKey(&ghi)); + if (but_key && ui_editsource_uibut_match(&ui_editsource_info->but_orig, but_key)) { + but_store = static_cast<uiEditSourceButStore *>(BLI_ghashIterator_getValue(&ghi)); + break; + } + } + + if (but_store) { + if (but_store->py_dbg_line_number != -1) { + ret = editsource_text_edit(C, op, but_store->py_dbg_fn, but_store->py_dbg_line_number); + } + else { + BKE_report( + op->reports, RPT_ERROR, "Active button is not from a script, cannot edit source"); + ret = OPERATOR_CANCELLED; + } + } + else { + BKE_report(op->reports, RPT_ERROR, "Active button match cannot be found"); + ret = OPERATOR_CANCELLED; + } + + ui_editsource_active_but_clear(); + + // printf("%s: end\n", __func__); + + return ret; + } + + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; +} + +static void UI_OT_editsource(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Source"; + ot->idname = "UI_OT_editsource"; + ot->description = "Edit UI source code of the active button"; + + /* callbacks */ + ot->exec = editsource_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit Translation Operator + * \{ */ + +/** + * EditTranslation utility funcs and operator, + * + * \note this includes utility functions and button matching checks. + * this only works in conjunction with a Python operator! + */ +static void edittranslation_find_po_file(const char *root, + const char *uilng, + char *path, + const size_t maxlen) +{ + char tstr[32]; /* Should be more than enough! */ + + /* First, full lang code. */ + BLI_snprintf(tstr, sizeof(tstr), "%s.po", uilng); + BLI_join_dirfile(path, maxlen, root, uilng); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + + /* Now try without the second iso code part (_ES in es_ES). */ + { + const char *tc = nullptr; + size_t szt = 0; + tstr[0] = '\0'; + + tc = strchr(uilng, '_'); + if (tc) { + szt = tc - uilng; + if (szt < sizeof(tstr)) { /* Paranoid, should always be true! */ + BLI_strncpy(tstr, uilng, szt + 1); /* +1 for '\0' char! */ + } + } + if (tstr[0]) { + /* Because of some codes like sr_SR@latin... */ + tc = strchr(uilng, '@'); + if (tc) { + BLI_strncpy(tstr + szt, tc, sizeof(tstr) - szt); + } + + BLI_join_dirfile(path, maxlen, root, tstr); + strcat(tstr, ".po"); + BLI_path_append(path, maxlen, tstr); + if (BLI_is_file(path)) { + return; + } + } + } + + /* Else no po file! */ + path[0] = '\0'; +} + +static int edittranslation_exec(bContext *C, wmOperator *op) +{ + uiBut *but = UI_context_active_but_get(C); + if (but == nullptr) { + BKE_report(op->reports, RPT_ERROR, "Active button not found"); + return OPERATOR_CANCELLED; + } + + wmOperatorType *ot; + PointerRNA ptr; + char popath[FILE_MAX]; + const char *root = U.i18ndir; + const char *uilng = BLT_lang_get(); + + uiStringInfo but_label = {BUT_GET_LABEL, nullptr}; + uiStringInfo rna_label = {BUT_GET_RNA_LABEL, nullptr}; + uiStringInfo enum_label = {BUT_GET_RNAENUM_LABEL, nullptr}; + uiStringInfo but_tip = {BUT_GET_TIP, nullptr}; + uiStringInfo rna_tip = {BUT_GET_RNA_TIP, nullptr}; + uiStringInfo enum_tip = {BUT_GET_RNAENUM_TIP, nullptr}; + uiStringInfo rna_struct = {BUT_GET_RNASTRUCT_IDENTIFIER, nullptr}; + uiStringInfo rna_prop = {BUT_GET_RNAPROP_IDENTIFIER, nullptr}; + uiStringInfo rna_enum = {BUT_GET_RNAENUM_IDENTIFIER, nullptr}; + uiStringInfo rna_ctxt = {BUT_GET_RNA_LABEL_CONTEXT, nullptr}; + + if (!BLI_is_dir(root)) { + BKE_report(op->reports, + RPT_ERROR, + "Please set your Preferences' 'Translation Branches " + "Directory' path to a valid directory"); + return OPERATOR_CANCELLED; + } + ot = WM_operatortype_find(EDTSRC_I18N_OP_NAME, false); + if (ot == nullptr) { + BKE_reportf(op->reports, + RPT_ERROR, + "Could not find operator '%s'! Please enable ui_translate add-on " + "in the User Preferences", + EDTSRC_I18N_OP_NAME); + return OPERATOR_CANCELLED; + } + /* Try to find a valid po file for current language... */ + edittranslation_find_po_file(root, uilng, popath, FILE_MAX); + // printf("po path: %s\n", popath); + if (popath[0] == '\0') { + BKE_reportf( + op->reports, RPT_ERROR, "No valid po found for language '%s' under %s", uilng, root); + return OPERATOR_CANCELLED; + } + + UI_but_string_info_get(C, + but, + &but_label, + &rna_label, + &enum_label, + &but_tip, + &rna_tip, + &enum_tip, + &rna_struct, + &rna_prop, + &rna_enum, + &rna_ctxt, + nullptr); + + WM_operator_properties_create_ptr(&ptr, ot); + RNA_string_set(&ptr, "lang", uilng); + RNA_string_set(&ptr, "po_file", popath); + RNA_string_set(&ptr, "but_label", but_label.strinfo); + RNA_string_set(&ptr, "rna_label", rna_label.strinfo); + RNA_string_set(&ptr, "enum_label", enum_label.strinfo); + RNA_string_set(&ptr, "but_tip", but_tip.strinfo); + RNA_string_set(&ptr, "rna_tip", rna_tip.strinfo); + RNA_string_set(&ptr, "enum_tip", enum_tip.strinfo); + RNA_string_set(&ptr, "rna_struct", rna_struct.strinfo); + RNA_string_set(&ptr, "rna_prop", rna_prop.strinfo); + RNA_string_set(&ptr, "rna_enum", rna_enum.strinfo); + RNA_string_set(&ptr, "rna_ctxt", rna_ctxt.strinfo); + const int ret = WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &ptr, nullptr); + + /* Clean up */ + if (but_label.strinfo) { + MEM_freeN(but_label.strinfo); + } + if (rna_label.strinfo) { + MEM_freeN(rna_label.strinfo); + } + if (enum_label.strinfo) { + MEM_freeN(enum_label.strinfo); + } + if (but_tip.strinfo) { + MEM_freeN(but_tip.strinfo); + } + if (rna_tip.strinfo) { + MEM_freeN(rna_tip.strinfo); + } + if (enum_tip.strinfo) { + MEM_freeN(enum_tip.strinfo); + } + if (rna_struct.strinfo) { + MEM_freeN(rna_struct.strinfo); + } + if (rna_prop.strinfo) { + MEM_freeN(rna_prop.strinfo); + } + if (rna_enum.strinfo) { + MEM_freeN(rna_enum.strinfo); + } + if (rna_ctxt.strinfo) { + MEM_freeN(rna_ctxt.strinfo); + } + + return ret; +} + +static void UI_OT_edittranslation_init(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Edit Translation"; + ot->idname = "UI_OT_edittranslation_init"; + ot->description = "Edit i18n in current language for the active button"; + + /* callbacks */ + ot->exec = edittranslation_exec; +} + +#endif /* WITH_PYTHON */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Reload Translation Operator + * \{ */ + +static int reloadtranslation_exec(bContext *UNUSED(C), wmOperator *UNUSED(op)) +{ + BLT_lang_init(); + BLF_cache_clear(); + BLT_lang_set(nullptr); + UI_reinit_font(); + return OPERATOR_FINISHED; +} + +static void UI_OT_reloadtranslation(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Reload Translation"; + ot->idname = "UI_OT_reloadtranslation"; + ot->description = "Force a full reload of UI translation"; + + /* callbacks */ + ot->exec = reloadtranslation_exec; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Press Button Operator + * \{ */ + +static int ui_button_press_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + bScreen *screen = CTX_wm_screen(C); + const bool skip_depressed = RNA_boolean_get(op->ptr, "skip_depressed"); + ARegion *region_prev = CTX_wm_region(C); + ARegion *region = screen ? BKE_screen_find_region_xy(screen, RGN_TYPE_ANY, event->xy) : nullptr; + + if (region == nullptr) { + region = region_prev; + } + + if (region == nullptr) { + return OPERATOR_PASS_THROUGH; + } + + CTX_wm_region_set(C, region); + uiBut *but = UI_context_active_but_get(C); + CTX_wm_region_set(C, region_prev); + + if (but == nullptr) { + return OPERATOR_PASS_THROUGH; + } + if (skip_depressed && (but->flag & (UI_SELECT | UI_SELECT_DRAW))) { + return OPERATOR_PASS_THROUGH; + } + + /* Weak, this is a workaround for 'UI_but_is_tool', which checks the operator type, + * having this avoids a minor drawing glitch. */ + void *but_optype = but->optype; + + UI_but_execute(C, region, but); + + but->optype = static_cast<wmOperatorType *>(but_optype); + + WM_event_add_mousemove(CTX_wm_window(C)); + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_execute(wmOperatorType *ot) +{ + ot->name = "Press Button"; + ot->idname = "UI_OT_button_execute"; + ot->description = "Presses active button"; + + ot->invoke = ui_button_press_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_boolean(ot->srna, "skip_depressed", false, "Skip Depressed", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Text Button Clear Operator + * \{ */ + +static int button_string_clear_exec(bContext *C, wmOperator *UNUSED(op)) +{ + uiBut *but = UI_context_active_but_get_respect_menu(C); + + if (but) { + ui_but_active_string_clear_and_exit(C, but); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_button_string_clear(wmOperatorType *ot) +{ + ot->name = "Clear Button String"; + ot->idname = "UI_OT_button_string_clear"; + ot->description = "Unsets the text of the active button"; + + ot->poll = ED_operator_regionactive; + ot->exec = button_string_clear_exec; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Color Operator + * \{ */ + +bool UI_drop_color_poll(struct bContext *C, wmDrag *drag, const wmEvent *UNUSED(event)) +{ + /* should only return true for regions that include buttons, for now + * return true always */ + if (drag->type == WM_DRAG_COLOR) { + SpaceImage *sima = CTX_wm_space_image(C); + ARegion *region = CTX_wm_region(C); + + if (UI_but_active_drop_color(C)) { + return true; + } + + if (sima && (sima->mode == SI_MODE_PAINT) && sima->image && + (region && region->regiontype == RGN_TYPE_WINDOW)) { + return true; + } + } + + return false; +} + +void UI_drop_color_copy(bContext *UNUSED(C), wmDrag *drag, wmDropBox *drop) +{ + uiDragColorHandle *drag_info = static_cast<uiDragColorHandle *>(drag->poin); + + RNA_float_set_array(drop->ptr, "color", drag_info->color); + RNA_boolean_set(drop->ptr, "gamma", drag_info->gamma_corrected); +} + +static int drop_color_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiBut *but = nullptr; + float color[4]; + bool gamma; + + RNA_float_get_array(op->ptr, "color", color); + gamma = RNA_boolean_get(op->ptr, "gamma"); + + /* find button under mouse, check if it has RNA color property and + * if it does copy the data */ + but = ui_region_find_active_but(region); + + if (but && but->type == UI_BTYPE_COLOR && but->rnaprop) { + const int color_len = RNA_property_array_length(&but->rnapoin, but->rnaprop); + BLI_assert(color_len <= 4); + + /* keep alpha channel as-is */ + if (color_len == 4) { + color[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3); + } + + if (RNA_property_subtype(but->rnaprop) == PROP_COLOR_GAMMA) { + if (!gamma) { + IMB_colormanagement_scene_linear_to_srgb_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + else if (RNA_property_subtype(but->rnaprop) == PROP_COLOR) { + if (gamma) { + IMB_colormanagement_srgb_to_scene_linear_v3(color, color); + } + RNA_property_float_set_array(&but->rnapoin, but->rnaprop, color); + RNA_property_update(C, &but->rnapoin, but->rnaprop); + } + } + else { + if (gamma) { + srgb_to_linearrgb_v3_v3(color, color); + } + + ED_imapaint_bucket_fill(C, color, op, event->mval); + } + + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_color(wmOperatorType *ot) +{ + ot->name = "Drop Color"; + ot->idname = "UI_OT_drop_color"; + ot->description = "Drop colors to buttons"; + + ot->invoke = drop_color_invoke; + ot->flag = OPTYPE_INTERNAL; + + RNA_def_float_color( + ot->srna, "color", 3, nullptr, 0.0, FLT_MAX, "Color", "Source color", 0.0, 1.0); + RNA_def_boolean( + ot->srna, "gamma", false, "Gamma Corrected", "The source color is gamma corrected"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Drop Name Operator + * \{ */ + +static bool drop_name_poll(bContext *C) +{ + if (!ED_operator_regionactive(C)) { + return false; + } + + const uiBut *but = UI_but_active_drop_name_button(C); + if (!but) { + return false; + } + + if (but->flag & UI_BUT_DISABLED) { + return false; + } + + return true; +} + +static int drop_name_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + uiBut *but = UI_but_active_drop_name_button(C); + char *str = RNA_string_get_alloc(op->ptr, "string", nullptr, 0, nullptr); + + if (str) { + ui_but_set_string_interactive(C, but, str); + MEM_freeN(str); + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_name(wmOperatorType *ot) +{ + ot->name = "Drop Name"; + ot->idname = "UI_OT_drop_name"; + ot->description = "Drop name to button"; + + ot->poll = drop_name_poll; + ot->invoke = drop_name_invoke; + ot->flag = OPTYPE_UNDO | OPTYPE_INTERNAL; + + RNA_def_string( + ot->srna, "string", nullptr, 0, "String", "The string value to drop into the button"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI List Search Operator + * \{ */ + +static bool ui_list_focused_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + if (!region) { + return false; + } + const wmWindow *win = CTX_wm_window(C); + const uiList *list = UI_list_find_mouse_over(region, win->eventstate); + + return list != nullptr; +} + +/** + * Ensure the filter options are set to be visible in the UI list. + * \return if the visibility changed, requiring a redraw. + */ +static bool ui_list_unhide_filter_options(uiList *list) +{ + if (list->filter_flag & UILST_FLT_SHOW) { + /* Nothing to be done. */ + return false; + } + + list->filter_flag |= UILST_FLT_SHOW; + return true; +} + +static int ui_list_start_filter_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + ARegion *region = CTX_wm_region(C); + uiList *list = UI_list_find_mouse_over(region, event); + /* Poll should check. */ + BLI_assert(list != nullptr); + + if (ui_list_unhide_filter_options(list)) { + ui_region_redraw_immediately(C, region); + } + + if (!UI_textbutton_activate_rna(C, region, list, "filter_name")) { + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_list_start_filter(wmOperatorType *ot) +{ + ot->name = "List Filter"; + ot->idname = "UI_OT_list_start_filter"; + ot->description = "Start entering filter text for the list in focus"; + + ot->invoke = ui_list_start_filter_invoke; + ot->poll = ui_list_focused_poll; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI Tree-View Drop Operator + * \{ */ + +static bool ui_view_drop_poll(bContext *C) +{ + const wmWindow *win = CTX_wm_window(C); + const ARegion *region = CTX_wm_region(C); + const uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, win->eventstate->xy); + + return hovered_item != nullptr; +} + +static int ui_view_drop_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event) +{ + if (event->custom != EVT_DATA_DRAGDROP) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + const ARegion *region = CTX_wm_region(C); + uiViewItemHandle *hovered_item = UI_region_views_find_item_at(region, event->xy); + + if (!UI_view_item_drop_handle( + C, hovered_item, static_cast<const ListBase *>(event->customdata))) { + return OPERATOR_CANCELLED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_drop(wmOperatorType *ot) +{ + ot->name = "View drop"; + ot->idname = "UI_OT_view_drop"; + ot->description = "Drag and drop items onto a data-set item"; + + ot->invoke = ui_view_drop_invoke; + ot->poll = ui_view_drop_poll; + + ot->flag = OPTYPE_INTERNAL; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name UI View Item Rename Operator + * + * General purpose renaming operator for views. Thanks to this, to add a rename button to context + * menus for example, view API users don't have to implement their own renaming operators with the + * same logic as they already have for their #ui::AbstractViewItem::rename() override. + * + * \{ */ + +static bool ui_view_item_rename_poll(bContext *C) +{ + const ARegion *region = CTX_wm_region(C); + const uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + return active_item != nullptr && UI_view_item_can_rename(active_item); +} + +static int ui_view_item_rename_exec(bContext *C, wmOperator *UNUSED(op)) +{ + ARegion *region = CTX_wm_region(C); + uiViewItemHandle *active_item = UI_region_views_find_active_item(region); + + UI_view_item_begin_rename(active_item); + ED_region_tag_redraw(region); + + return OPERATOR_FINISHED; +} + +static void UI_OT_view_item_rename(wmOperatorType *ot) +{ + ot->name = "Rename View Item"; + ot->idname = "UI_OT_view_item_rename"; + ot->description = "Rename the active item in the data-set view"; + + ot->exec = ui_view_item_rename_exec; + ot->poll = ui_view_item_rename_poll; + /* Could get a custom tooltip via the `get_description()` callback and another overridable + * function of the view. */ + + ot->flag = OPTYPE_INTERNAL; +} +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Material Drag/Drop Operator + * + * \{ */ + +static bool ui_drop_material_poll(bContext *C) +{ + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + const Object *ob = static_cast<const Object *>(ptr.data); + if (ob == nullptr) { + return false; + } + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + if (RNA_pointer_is_null(&mat_slot)) { + return false; + } + + return true; +} + +static int ui_drop_material_exec(bContext *C, wmOperator *op) +{ + Main *bmain = CTX_data_main(C); + + Material *ma = (Material *)WM_operator_properties_id_lookup_from_name_or_session_uuid( + bmain, op->ptr, ID_MA); + if (ma == nullptr) { + return OPERATOR_CANCELLED; + } + + PointerRNA ptr = CTX_data_pointer_get_type(C, "object", &RNA_Object); + Object *ob = static_cast<Object *>(ptr.data); + BLI_assert(ob); + + PointerRNA mat_slot = CTX_data_pointer_get_type(C, "material_slot", &RNA_MaterialSlot); + BLI_assert(mat_slot.data); + const int target_slot = RNA_int_get(&mat_slot, "slot_index") + 1; + + /* only drop grease pencil material on grease pencil objects */ + if ((ma->gp_style != nullptr) && (ob->type != OB_GPENCIL)) { + return OPERATOR_CANCELLED; + } + + BKE_object_material_assign(bmain, ob, ma, target_slot, BKE_MAT_ASSIGN_USERPREF); + + WM_event_add_notifier(C, NC_OBJECT | ND_OB_SHADING, ob); + WM_event_add_notifier(C, NC_SPACE | ND_SPACE_VIEW3D, nullptr); + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, ma); + DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM); + + return OPERATOR_FINISHED; +} + +static void UI_OT_drop_material(wmOperatorType *ot) +{ + ot->name = "Drop Material in Material slots"; + ot->description = "Drag material to Material slots in Properties"; + ot->idname = "UI_OT_drop_material"; + + ot->poll = ui_drop_material_poll; + ot->exec = ui_drop_material_exec; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL; + + WM_operator_properties_id_lookup(ot, false); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Operator & Keymap Registration + * \{ */ + +void ED_operatortypes_ui(void) +{ + WM_operatortype_append(UI_OT_copy_data_path_button); + WM_operatortype_append(UI_OT_copy_as_driver_button); + WM_operatortype_append(UI_OT_copy_python_command_button); + WM_operatortype_append(UI_OT_reset_default_button); + WM_operatortype_append(UI_OT_assign_default_button); + WM_operatortype_append(UI_OT_unset_property_button); + WM_operatortype_append(UI_OT_override_type_set_button); + WM_operatortype_append(UI_OT_override_remove_button); + WM_operatortype_append(UI_OT_copy_to_selected_button); + WM_operatortype_append(UI_OT_jump_to_target_button); + WM_operatortype_append(UI_OT_drop_color); + WM_operatortype_append(UI_OT_drop_name); + WM_operatortype_append(UI_OT_drop_material); +#ifdef WITH_PYTHON + WM_operatortype_append(UI_OT_editsource); + WM_operatortype_append(UI_OT_edittranslation_init); +#endif + WM_operatortype_append(UI_OT_reloadtranslation); + WM_operatortype_append(UI_OT_button_execute); + WM_operatortype_append(UI_OT_button_string_clear); + + WM_operatortype_append(UI_OT_list_start_filter); + + WM_operatortype_append(UI_OT_view_drop); + WM_operatortype_append(UI_OT_view_item_rename); + + /* external */ + WM_operatortype_append(UI_OT_eyedropper_color); + WM_operatortype_append(UI_OT_eyedropper_colorramp); + WM_operatortype_append(UI_OT_eyedropper_colorramp_point); + WM_operatortype_append(UI_OT_eyedropper_id); + WM_operatortype_append(UI_OT_eyedropper_depth); + WM_operatortype_append(UI_OT_eyedropper_driver); + WM_operatortype_append(UI_OT_eyedropper_gpencil_color); +} + +void ED_keymap_ui(wmKeyConfig *keyconf) +{ + WM_keymap_ensure(keyconf, "User Interface", 0, 0); + + eyedropper_modal_keymap(keyconf); + eyedropper_colorband_modal_keymap(keyconf); +} + +/** \} */ |