2023-08-16 00:20:26 +10:00
|
|
|
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
2023-05-31 16:19:06 +02:00
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2019-02-18 08:08:12 +11:00
|
|
|
/** \file
|
|
|
|
|
* \ingroup edinterface
|
2018-06-24 10:01:13 +02:00
|
|
|
*
|
|
|
|
|
* Generic context popup menus.
|
|
|
|
|
*/
|
|
|
|
|
|
2022-11-25 17:09:47 -06:00
|
|
|
#include <cstring>
|
2018-06-24 10:45:42 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
|
|
|
|
|
|
#include "DNA_screen_types.h"
|
|
|
|
|
|
2023-10-03 18:44:56 +02:00
|
|
|
#include "BLI_fileops.h"
|
2024-09-26 21:13:39 +10:00
|
|
|
#include "BLI_path_utils.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
#include "BLI_string.h"
|
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
|
|
2024-02-09 18:59:42 +01:00
|
|
|
#include "BLT_translation.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2023-11-16 11:41:55 +01:00
|
|
|
#include "BKE_context.hh"
|
2024-03-26 12:57:30 -04:00
|
|
|
#include "BKE_idprop.hh"
|
2023-09-25 17:48:21 -04:00
|
|
|
#include "BKE_screen.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_asset.hh"
|
2025-04-08 18:43:18 +02:00
|
|
|
#include "ED_buttons.hh"
|
2023-08-05 02:57:52 +02:00
|
|
|
#include "ED_keyframing.hh"
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "ED_screen.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2024-03-08 09:16:00 -05:00
|
|
|
#include "UI_abstract_view.hh"
|
2023-06-26 16:56:05 +02:00
|
|
|
#include "UI_interface.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2022-11-26 00:21:17 -06:00
|
|
|
#include "interface_intern.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2023-08-10 22:40:27 +02:00
|
|
|
#include "RNA_access.hh"
|
|
|
|
|
#include "RNA_path.hh"
|
2024-07-10 18:30:02 +02:00
|
|
|
#include "RNA_prototypes.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2020-08-17 17:46:06 +10:00
|
|
|
#ifdef WITH_PYTHON
|
2024-09-24 17:07:49 +02:00
|
|
|
# include "BPY_extern.hh"
|
|
|
|
|
# include "BPY_extern_run.hh"
|
2020-08-17 17:46:06 +10:00
|
|
|
#endif
|
2019-05-27 16:24:48 +10:00
|
|
|
|
2023-08-04 23:11:22 +02:00
|
|
|
#include "WM_api.hh"
|
|
|
|
|
#include "WM_types.hh"
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2019-01-15 23:24:20 +11:00
|
|
|
/* This hack is needed because we don't have a good way to
|
2023-02-12 14:37:16 +11:00
|
|
|
* re-reference keymap items once added: #42944 */
|
2018-06-24 10:01:13 +02:00
|
|
|
#define USE_KEYMAP_ADD_HACK
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Button Context Menu
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
static IDProperty *shortcut_property_from_rna(bContext *C, uiBut *but)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
2024-03-26 15:39:34 -04:00
|
|
|
using namespace blender;
|
2019-05-10 11:55:52 +02:00
|
|
|
/* Compute data path from context to property. */
|
2019-10-21 19:57:43 +11:00
|
|
|
|
|
|
|
|
/* If this returns null, we won't be able to bind shortcuts to these RNA properties.
|
|
|
|
|
* Support can be added at #wm_context_member_from_ptr. */
|
2024-01-30 15:43:16 +11:00
|
|
|
std::optional<std::string> final_data_path = WM_context_path_resolve_property_full(
|
2021-08-31 12:30:45 +10:00
|
|
|
C, &but->rnapoin, but->rnaprop, but->rnaindex);
|
2024-01-30 15:43:16 +11:00
|
|
|
if (!final_data_path.has_value()) {
|
2022-11-25 17:09:47 -06:00
|
|
|
return nullptr;
|
2019-10-21 19:57:43 +11:00
|
|
|
}
|
|
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
/* Create ID property of data path, to pass to the operator. */
|
2024-03-26 15:39:34 -04:00
|
|
|
IDProperty *prop = bke::idprop::create_group(__func__).release();
|
|
|
|
|
IDP_AddToGroup(prop, bke::idprop::create("data_path", final_data_path.value()).release());
|
2019-05-10 11:55:52 +02:00
|
|
|
return prop;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-21 19:57:43 +11:00
|
|
|
static const char *shortcut_get_operator_property(bContext *C, uiBut *but, IDProperty **r_prop)
|
2019-05-10 11:55:52 +02:00
|
|
|
{
|
2024-03-26 15:39:34 -04:00
|
|
|
using namespace blender;
|
2019-05-10 11:55:52 +02:00
|
|
|
if (but->optype) {
|
|
|
|
|
/* Operator */
|
2022-11-25 17:09:47 -06:00
|
|
|
*r_prop = (but->opptr && but->opptr->data) ?
|
|
|
|
|
IDP_CopyProperty(static_cast<IDProperty *>(but->opptr->data)) :
|
|
|
|
|
nullptr;
|
2019-05-10 11:55:52 +02:00
|
|
|
return but->optype->idname;
|
|
|
|
|
}
|
2020-07-03 14:20:10 +02:00
|
|
|
|
|
|
|
|
if (but->rnaprop) {
|
2019-10-21 19:57:43 +11:00
|
|
|
const PropertyType rnaprop_type = RNA_property_type(but->rnaprop);
|
|
|
|
|
|
|
|
|
|
if (rnaprop_type == PROP_BOOLEAN) {
|
2019-05-10 11:55:52 +02:00
|
|
|
/* Boolean */
|
2019-10-21 19:57:43 +11:00
|
|
|
*r_prop = shortcut_property_from_rna(C, but);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (*r_prop == nullptr) {
|
|
|
|
|
return nullptr;
|
2019-10-21 19:57:43 +11:00
|
|
|
}
|
2019-05-10 11:55:52 +02:00
|
|
|
return "WM_OT_context_toggle";
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2020-07-03 14:20:10 +02:00
|
|
|
if (rnaprop_type == PROP_ENUM) {
|
2019-05-10 11:55:52 +02:00
|
|
|
/* Enum */
|
2019-10-21 19:57:43 +11:00
|
|
|
*r_prop = shortcut_property_from_rna(C, but);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (*r_prop == nullptr) {
|
|
|
|
|
return nullptr;
|
2019-10-21 19:57:43 +11:00
|
|
|
}
|
2019-05-10 11:55:52 +02:00
|
|
|
return "WM_OT_context_menu_enum";
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-05-10 11:55:52 +02:00
|
|
|
|
2024-01-11 11:10:53 +11:00
|
|
|
if (MenuType *mt = UI_but_menutype_get(but)) {
|
2024-03-26 15:39:34 -04:00
|
|
|
IDProperty *prop = bke::idprop::create_group(__func__).release();
|
|
|
|
|
IDP_AddToGroup(prop, bke::idprop::create("name", mt->idname).release());
|
2024-01-11 11:10:53 +11:00
|
|
|
*r_prop = prop;
|
|
|
|
|
return "WM_OT_call_menu";
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-07 20:00:35 +02:00
|
|
|
if (std::optional asset_shelf_idname = UI_but_asset_shelf_type_idname_get(but)) {
|
|
|
|
|
IDProperty *prop = blender::bke::idprop::create_group(__func__).release();
|
|
|
|
|
IDP_AddToGroup(prop, bke::idprop::create("name", *asset_shelf_idname).release());
|
|
|
|
|
*r_prop = prop;
|
|
|
|
|
return "WM_OT_call_asset_shelf_popover";
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-11 11:10:53 +11:00
|
|
|
if (PanelType *pt = UI_but_paneltype_get(but)) {
|
2024-03-26 15:39:34 -04:00
|
|
|
IDProperty *prop = blender::bke::idprop::create_group(__func__).release();
|
|
|
|
|
IDP_AddToGroup(prop, bke::idprop::create("name", pt->idname).release());
|
2024-01-11 11:10:53 +11:00
|
|
|
*r_prop = prop;
|
|
|
|
|
return "WM_OT_call_panel";
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:09:47 -06:00
|
|
|
*r_prop = nullptr;
|
|
|
|
|
return nullptr;
|
2019-05-10 11:55:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void shortcut_free_operator_property(IDProperty *prop)
|
|
|
|
|
{
|
|
|
|
|
if (prop) {
|
|
|
|
|
IDP_FreeProperty(prop);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-20 15:51:47 -03:00
|
|
|
static void but_shortcut_name_func(bContext *C, void *arg1, int /*event*/)
|
2019-05-10 11:55:52 +02:00
|
|
|
{
|
|
|
|
|
uiBut *but = (uiBut *)arg1;
|
|
|
|
|
|
|
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (idname == nullptr) {
|
2019-05-10 11:55:52 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* complex code to change name of button */
|
2024-02-07 14:22:54 +01:00
|
|
|
if (std::optional<std::string> shortcut_str = WM_key_event_operator_string(
|
|
|
|
|
C, idname, but->opcontext, prop, true))
|
2019-05-10 11:55:52 +02:00
|
|
|
{
|
2024-02-07 14:22:54 +01:00
|
|
|
ui_but_add_shortcut(but, shortcut_str->c_str(), true);
|
2019-05-10 11:55:52 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* simply strip the shortcut */
|
2022-11-25 17:09:47 -06:00
|
|
|
ui_but_add_shortcut(but, nullptr, true);
|
2019-05-10 11:55:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
shortcut_free_operator_property(prop);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
2020-03-06 16:56:42 +01:00
|
|
|
static uiBlock *menu_change_shortcut(bContext *C, ARegion *region, void *arg)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
|
uiBut *but = (uiBut *)arg;
|
2020-03-15 17:32:25 +11:00
|
|
|
const uiStyle *style = UI_style_get_dpi();
|
2019-05-10 11:55:52 +02:00
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMap *km;
|
|
|
|
|
wmKeyMapItem *kmi = WM_key_event_operator(C,
|
|
|
|
|
idname,
|
|
|
|
|
but->opcontext,
|
|
|
|
|
prop,
|
|
|
|
|
EVT_TYPE_MASK_HOTKEY_INCLUDE,
|
|
|
|
|
EVT_TYPE_MASK_HOTKEY_EXCLUDE,
|
|
|
|
|
&km);
|
2019-05-14 12:27:28 +02:00
|
|
|
U.runtime.is_dirty = true;
|
|
|
|
|
|
2022-11-25 17:09:47 -06:00
|
|
|
BLI_assert(kmi != nullptr);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-01-24 16:45:32 +01:00
|
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&wm->id, &RNA_KeyMapItem, kmi);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-03-31 00:36:46 +02:00
|
|
|
uiBlock *block = UI_block_begin(C, region, "_popup", blender::ui::EmbossType::Emboss);
|
2018-06-24 10:01:13 +02:00
|
|
|
UI_block_func_handle_set(block, but_shortcut_name_func, but);
|
|
|
|
|
UI_block_flag_enable(block, UI_BLOCK_MOVEMOUSE_QUIT);
|
|
|
|
|
UI_block_direction_set(block, UI_DIR_CENTER_Y);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
uiLayout *layout = UI_block_layout(block,
|
|
|
|
|
UI_LAYOUT_VERTICAL,
|
|
|
|
|
UI_LAYOUT_PANEL,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
U.widget_unit * 10,
|
|
|
|
|
U.widget_unit * 2,
|
|
|
|
|
0,
|
|
|
|
|
style);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-08 17:21:08 +02:00
|
|
|
layout->label(CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"), ICON_HAND);
|
2025-05-08 20:45:37 +02:00
|
|
|
layout->prop(&ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-03-17 04:19:05 +01:00
|
|
|
const int bounds_offset[2] = {int(-100 * UI_SCALE_FAC), int(36 * UI_SCALE_FAC)};
|
|
|
|
|
UI_block_bounds_set_popup(block, 6 * UI_SCALE_FAC, bounds_offset);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
shortcut_free_operator_property(prop);
|
|
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
return block;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef USE_KEYMAP_ADD_HACK
|
|
|
|
|
static int g_kmi_id_hack;
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-03-06 16:56:42 +01:00
|
|
|
static uiBlock *menu_add_shortcut(bContext *C, ARegion *region, void *arg)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
|
uiBut *but = (uiBut *)arg;
|
2020-03-15 17:32:25 +11:00
|
|
|
const uiStyle *style = UI_style_get_dpi();
|
2019-05-10 11:55:52 +02:00
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2019-01-15 23:24:20 +11:00
|
|
|
/* XXX this guess_opname can potentially return a different keymap
|
|
|
|
|
* than being found on adding later... */
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMap *km = WM_keymap_guess_opname(C, idname);
|
2022-11-25 17:09:47 -06:00
|
|
|
KeyMapItem_Params params{};
|
|
|
|
|
params.type = EVT_AKEY;
|
|
|
|
|
params.value = KM_PRESS;
|
|
|
|
|
params.modifier = 0;
|
|
|
|
|
params.direction = KM_ANY;
|
|
|
|
|
wmKeyMapItem *kmi = WM_keymap_add_item(km, idname, ¶ms);
|
2021-01-04 17:02:13 +11:00
|
|
|
const int kmi_id = kmi->id;
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2022-11-25 17:09:47 -06:00
|
|
|
/* This takes ownership of prop, or prop can be nullptr for reset. */
|
2019-05-02 11:42:46 +10:00
|
|
|
WM_keymap_item_properties_reset(kmi, prop);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
|
|
|
|
/* update and get pointers again */
|
|
|
|
|
WM_keyconfig_update(wm);
|
2019-05-14 12:27:28 +02:00
|
|
|
U.runtime.is_dirty = true;
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
km = WM_keymap_guess_opname(C, idname);
|
2018-06-24 10:01:13 +02:00
|
|
|
kmi = WM_keymap_item_find_id(km, kmi_id);
|
|
|
|
|
|
2025-01-24 16:45:32 +01:00
|
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&wm->id, &RNA_KeyMapItem, kmi);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2025-03-31 00:36:46 +02:00
|
|
|
uiBlock *block = UI_block_begin(C, region, "_popup", blender::ui::EmbossType::Emboss);
|
2018-06-24 10:01:13 +02:00
|
|
|
UI_block_func_handle_set(block, but_shortcut_name_func, but);
|
|
|
|
|
UI_block_direction_set(block, UI_DIR_CENTER_Y);
|
|
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
uiLayout *layout = UI_block_layout(block,
|
|
|
|
|
UI_LAYOUT_VERTICAL,
|
|
|
|
|
UI_LAYOUT_PANEL,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
U.widget_unit * 10,
|
|
|
|
|
U.widget_unit * 2,
|
|
|
|
|
0,
|
|
|
|
|
style);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2025-05-08 17:21:08 +02:00
|
|
|
layout->label(CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"), ICON_HAND);
|
2025-05-08 20:45:37 +02:00
|
|
|
layout->prop(&ptr, "type", UI_ITEM_R_FULL_EVENT | UI_ITEM_R_IMMEDIATE, "", ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
2023-03-17 04:19:05 +01:00
|
|
|
const int bounds_offset[2] = {int(-100 * UI_SCALE_FAC), int(36 * UI_SCALE_FAC)};
|
|
|
|
|
UI_block_bounds_set_popup(block, 6 * UI_SCALE_FAC, bounds_offset);
|
2018-06-24 10:01:13 +02:00
|
|
|
|
|
|
|
|
#ifdef USE_KEYMAP_ADD_HACK
|
|
|
|
|
g_kmi_id_hack = kmi_id;
|
|
|
|
|
#endif
|
2019-05-14 12:27:28 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
return block;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-25 17:09:47 -06:00
|
|
|
static void menu_add_shortcut_cancel(bContext *C, void *arg1)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
|
|
|
|
uiBut *but = (uiBut *)arg1;
|
|
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
|
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
#ifdef USE_KEYMAP_ADD_HACK
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMap *km = WM_keymap_guess_opname(C, idname);
|
2021-01-04 17:02:13 +11:00
|
|
|
const int kmi_id = g_kmi_id_hack;
|
2018-06-24 10:01:13 +02:00
|
|
|
UNUSED_VARS(but);
|
|
|
|
|
#else
|
2020-12-04 08:03:14 -06:00
|
|
|
int kmi_id = WM_key_event_operator_id(C, idname, but->opcontext, prop, true, &km);
|
2018-06-24 10:01:13 +02:00
|
|
|
#endif
|
|
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
shortcut_free_operator_property(prop);
|
|
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMapItem *kmi = WM_keymap_item_find_id(km, kmi_id);
|
2018-06-24 10:01:13 +02:00
|
|
|
WM_keymap_remove_item(km, kmi);
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-26 16:56:05 +02:00
|
|
|
static void remove_shortcut_func(bContext *C, uiBut *but)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
2019-05-10 11:55:52 +02:00
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMap *km;
|
|
|
|
|
wmKeyMapItem *kmi = WM_key_event_operator(C,
|
|
|
|
|
idname,
|
|
|
|
|
but->opcontext,
|
|
|
|
|
prop,
|
|
|
|
|
EVT_TYPE_MASK_HOTKEY_INCLUDE,
|
|
|
|
|
EVT_TYPE_MASK_HOTKEY_EXCLUDE,
|
|
|
|
|
&km);
|
2022-11-25 17:09:47 -06:00
|
|
|
BLI_assert(kmi != nullptr);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
WM_keymap_remove_item(km, kmi);
|
2019-05-14 12:27:28 +02:00
|
|
|
U.runtime.is_dirty = true;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
shortcut_free_operator_property(prop);
|
2018-06-24 10:01:13 +02:00
|
|
|
but_shortcut_name_func(C, but, 0);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-30 20:59:10 +02:00
|
|
|
static bool ui_but_is_user_menu_compatible(bContext *C, uiBut *but)
|
2018-06-24 10:45:42 +02:00
|
|
|
{
|
2021-08-31 11:46:47 +10:00
|
|
|
bool result = false;
|
|
|
|
|
if (but->optype) {
|
|
|
|
|
result = true;
|
|
|
|
|
}
|
|
|
|
|
else if (but->rnaprop) {
|
|
|
|
|
if (RNA_property_type(but->rnaprop) == PROP_BOOLEAN) {
|
2024-01-30 15:43:16 +11:00
|
|
|
std::optional<std::string> data_path = WM_context_path_resolve_full(C, &but->rnapoin);
|
|
|
|
|
if (data_path.has_value()) {
|
2021-08-31 11:46:47 +10:00
|
|
|
result = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (UI_but_menutype_get(but)) {
|
|
|
|
|
result = true;
|
|
|
|
|
}
|
2023-05-08 09:14:45 +02:00
|
|
|
else if (UI_but_operatortype_get_from_enum_menu(but, nullptr)) {
|
|
|
|
|
result = true;
|
|
|
|
|
}
|
2021-08-31 11:46:47 +10:00
|
|
|
|
|
|
|
|
return result;
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-30 20:59:10 +02:00
|
|
|
static bUserMenuItem *ui_but_user_menu_find(bContext *C, uiBut *but, bUserMenu *um)
|
2018-06-30 12:08:08 +02:00
|
|
|
{
|
|
|
|
|
if (but->optype) {
|
2022-11-25 17:09:47 -06:00
|
|
|
IDProperty *prop = (but->opptr) ? static_cast<IDProperty *>(but->opptr->data) : nullptr;
|
2018-06-30 12:08:08 +02:00
|
|
|
return (bUserMenuItem *)ED_screen_user_menu_item_find_operator(
|
2023-05-08 09:14:45 +02:00
|
|
|
&um->items, but->optype, prop, "", but->opcontext);
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
2020-07-03 14:20:10 +02:00
|
|
|
if (but->rnaprop) {
|
2024-01-30 15:43:16 +11:00
|
|
|
std::optional<std::string> member_id_data_path = WM_context_path_resolve_full(C,
|
|
|
|
|
&but->rnapoin);
|
|
|
|
|
/* NOTE(@ideasman42): It's highly unlikely a this ever occurs since the path must be resolved
|
|
|
|
|
* for this to be added in the first place, there might be some cases where manually
|
|
|
|
|
* constructed RNA paths don't resolve and in this case a crash should be avoided. */
|
|
|
|
|
if (UNLIKELY(!member_id_data_path.has_value())) {
|
|
|
|
|
/* Assert because this should never happen for typical usage. */
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2023-06-08 10:37:36 +02:00
|
|
|
/* Ignore the actual array index [pass -1] since the index is handled separately. */
|
2024-01-31 17:08:09 +01:00
|
|
|
const std::string prop_id = RNA_property_is_idprop(but->rnaprop) ?
|
|
|
|
|
RNA_path_property_py(&but->rnapoin, but->rnaprop, -1) :
|
|
|
|
|
RNA_property_identifier(but->rnaprop);
|
2018-12-04 15:11:26 +11:00
|
|
|
bUserMenuItem *umi = (bUserMenuItem *)ED_screen_user_menu_item_find_prop(
|
2024-01-31 17:08:09 +01:00
|
|
|
&um->items, member_id_data_path->c_str(), prop_id.c_str(), but->rnaindex);
|
2018-12-04 15:11:26 +11:00
|
|
|
return umi;
|
2018-06-30 20:59:10 +02:00
|
|
|
}
|
2020-12-04 08:03:14 -06:00
|
|
|
|
2023-05-08 09:14:45 +02:00
|
|
|
wmOperatorType *ot = nullptr;
|
|
|
|
|
PropertyRNA *prop_enum = nullptr;
|
|
|
|
|
if ((ot = UI_but_operatortype_get_from_enum_menu(but, &prop_enum))) {
|
|
|
|
|
return (bUserMenuItem *)ED_screen_user_menu_item_find_operator(
|
|
|
|
|
&um->items, ot, nullptr, RNA_property_identifier(prop_enum), but->opcontext);
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
MenuType *mt = UI_but_menutype_get(but);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (mt != nullptr) {
|
2018-06-30 12:08:08 +02:00
|
|
|
return (bUserMenuItem *)ED_screen_user_menu_item_find_menu(&um->items, mt);
|
|
|
|
|
}
|
2022-11-25 17:09:47 -06:00
|
|
|
return nullptr;
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-30 20:59:10 +02:00
|
|
|
static void ui_but_user_menu_add(bContext *C, uiBut *but, bUserMenu *um)
|
2018-06-30 12:08:08 +02:00
|
|
|
{
|
2018-06-30 20:59:10 +02:00
|
|
|
BLI_assert(ui_but_is_user_menu_compatible(C, but));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-01-22 14:54:44 -05:00
|
|
|
std::string drawstr = ui_but_drawstr_without_sep_char(but);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-05-08 09:14:45 +02:00
|
|
|
/* Used for USER_MENU_TYPE_MENU. */
|
2022-11-25 17:09:47 -06:00
|
|
|
MenuType *mt = nullptr;
|
2023-05-08 09:14:45 +02:00
|
|
|
/* Used for USER_MENU_TYPE_OPERATOR (property enum used). */
|
|
|
|
|
wmOperatorType *ot = nullptr;
|
|
|
|
|
PropertyRNA *prop = nullptr;
|
2018-06-30 12:08:08 +02:00
|
|
|
if (but->optype) {
|
2018-12-04 16:14:21 +11:00
|
|
|
if (drawstr[0] == '\0') {
|
|
|
|
|
/* Hard code overrides for generic operators. */
|
|
|
|
|
if (UI_but_is_tool(but)) {
|
2019-05-27 16:24:48 +10:00
|
|
|
char idname[64];
|
|
|
|
|
RNA_string_get(but->opptr, "name", idname);
|
|
|
|
|
#ifdef WITH_PYTHON
|
|
|
|
|
{
|
2022-11-25 17:09:47 -06:00
|
|
|
const char *expr_imports[] = {"bpy", "bl_ui", nullptr};
|
2019-05-27 16:24:48 +10:00
|
|
|
char expr[256];
|
|
|
|
|
SNPRINTF(expr,
|
|
|
|
|
"bl_ui.space_toolsystem_common.item_from_id("
|
|
|
|
|
"bpy.context, "
|
|
|
|
|
"bpy.context.space_data.type, "
|
|
|
|
|
"'%s').label",
|
|
|
|
|
idname);
|
2022-11-25 17:09:47 -06:00
|
|
|
char *expr_result = nullptr;
|
|
|
|
|
if (BPY_run_string_as_string(C, expr_imports, expr, nullptr, &expr_result)) {
|
2024-01-22 14:54:44 -05:00
|
|
|
drawstr = expr_result;
|
2019-05-27 16:24:48 +10:00
|
|
|
MEM_freeN(expr_result);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert(0);
|
2024-01-22 14:54:44 -05:00
|
|
|
drawstr = idname;
|
2019-05-27 16:24:48 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#else
|
2025-02-02 14:39:34 +11:00
|
|
|
drawstr = idname;
|
2019-05-27 16:24:48 +10:00
|
|
|
#endif
|
2018-12-04 16:14:21 +11:00
|
|
|
}
|
2025-05-09 13:34:35 +02:00
|
|
|
else if (but->tip_quick_func) {
|
2024-05-01 12:53:53 -04:00
|
|
|
/* The "quick tooltip" often contains a short string that can be used as a fallback. */
|
2025-05-09 13:34:35 +02:00
|
|
|
drawstr = but->tip_quick_func(but);
|
2024-05-01 12:53:53 -04:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-30 12:08:08 +02:00
|
|
|
ED_screen_user_menu_item_add_operator(
|
2022-11-25 17:09:47 -06:00
|
|
|
&um->items,
|
2024-01-22 14:54:44 -05:00
|
|
|
drawstr.c_str(),
|
2022-11-25 17:09:47 -06:00
|
|
|
but->optype,
|
|
|
|
|
but->opptr ? static_cast<const IDProperty *>(but->opptr->data) : nullptr,
|
2023-05-08 09:14:45 +02:00
|
|
|
"",
|
2022-11-25 17:09:47 -06:00
|
|
|
but->opcontext);
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
2018-06-30 20:59:10 +02:00
|
|
|
else if (but->rnaprop) {
|
2021-07-03 23:08:40 +10:00
|
|
|
/* NOTE: 'member_id' may be a path. */
|
2024-01-30 15:43:16 +11:00
|
|
|
std::optional<std::string> member_id_data_path = WM_context_path_resolve_full(C,
|
|
|
|
|
&but->rnapoin);
|
|
|
|
|
if (!member_id_data_path.has_value()) {
|
|
|
|
|
/* See #ui_but_user_menu_find code-comment. */
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
/* Ignore the actual array index [pass -1] since the index is handled separately. */
|
2024-01-31 17:08:09 +01:00
|
|
|
const std::string prop_id = RNA_property_is_idprop(but->rnaprop) ?
|
|
|
|
|
RNA_path_property_py(&but->rnapoin, but->rnaprop, -1) :
|
|
|
|
|
RNA_property_identifier(but->rnaprop);
|
2024-01-30 15:43:16 +11:00
|
|
|
/* NOTE: ignore 'drawstr', use property idname always. */
|
|
|
|
|
ED_screen_user_menu_item_add_prop(
|
2024-01-31 17:08:09 +01:00
|
|
|
&um->items, "", member_id_data_path->c_str(), prop_id.c_str(), but->rnaindex);
|
2024-01-30 15:43:16 +11:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-30 12:08:08 +02:00
|
|
|
else if ((mt = UI_but_menutype_get(but))) {
|
2024-01-22 14:54:44 -05:00
|
|
|
ED_screen_user_menu_item_add_menu(&um->items, drawstr.c_str(), mt);
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
2023-05-08 09:14:45 +02:00
|
|
|
else if ((ot = UI_but_operatortype_get_from_enum_menu(but, &prop))) {
|
|
|
|
|
ED_screen_user_menu_item_add_operator(&um->items,
|
2023-08-11 19:11:27 +02:00
|
|
|
WM_operatortype_name(ot, nullptr).c_str(),
|
2023-05-08 09:14:45 +02:00
|
|
|
ot,
|
|
|
|
|
nullptr,
|
|
|
|
|
RNA_property_identifier(prop),
|
|
|
|
|
but->opcontext);
|
|
|
|
|
}
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
|
|
|
|
|
2023-10-03 18:44:56 +02:00
|
|
|
static bool ui_but_menu_add_path_operators(uiLayout *layout, PointerRNA *ptr, PropertyRNA *prop)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
|
|
|
|
const PropertySubType subtype = RNA_property_subtype(prop);
|
|
|
|
|
wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true);
|
|
|
|
|
char filepath[FILE_MAX];
|
|
|
|
|
char dir[FILE_MAXDIR];
|
|
|
|
|
char file[FILE_MAXFILE];
|
|
|
|
|
PointerRNA props_ptr;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
BLI_assert(ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH));
|
|
|
|
|
UNUSED_VARS_NDEBUG(subtype);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
RNA_property_string_get(ptr, prop, filepath);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-10-03 18:44:56 +02:00
|
|
|
if (!BLI_exists(filepath)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (BLI_is_file(filepath)) {
|
2018-06-24 10:01:13 +02:00
|
|
|
BLI_assert(subtype == PROP_FILEPATH);
|
2025-05-21 02:55:04 +02:00
|
|
|
props_ptr = layout->op(ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open File Externally"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
RNA_string_set(&props_ptr, "filepath", filepath);
|
|
|
|
|
}
|
2023-10-03 18:44:56 +02:00
|
|
|
else {
|
|
|
|
|
/* This is a directory, so ensure it ends in a slash. */
|
|
|
|
|
BLI_path_slash_ensure(filepath, ARRAY_SIZE(filepath));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BLI_path_split_dir_file(filepath, dir, sizeof(dir), file, sizeof(file));
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-21 02:55:04 +02:00
|
|
|
props_ptr = layout->op(ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Location Externally"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
RNA_string_set(&props_ptr, "filepath", dir);
|
2023-10-03 18:44:56 +02:00
|
|
|
|
|
|
|
|
return true;
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-03 15:29:08 +02:00
|
|
|
static void set_layout_context_from_button(bContext *C, uiLayout *layout, uiBut *but)
|
|
|
|
|
{
|
|
|
|
|
if (!but->context) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
uiLayoutContextCopy(layout, but->context);
|
|
|
|
|
CTX_store_set(C, uiLayoutGetContextStore(layout));
|
|
|
|
|
}
|
|
|
|
|
|
UI: Support UI list tooltips, defined via Python scripts
Makes it possible to create tooltips for UI list rows, which can be
filled in .py scripts, similar to how they can extend other menus. This
is used by the (to be committed) Pose Library add-on to display pose
operations (selecting bones of a pose, blending a pose, etc).
It's important that the Python scripts check if the UI list is the
correct one by checking the list ID.
For this to work, a new `bpy.context.ui_list` can be checked. For
example, the Pose Library add-on does the following check:
```
def is_pose_asset_view() -> bool:
# Important: Must check context first, or the menu is added for every kind of list.
list = getattr(context, "ui_list", None)
if not list or list.bl_idname != "UI_UL_asset_view" or list.list_id != "pose_assets":
return False
if not context.asset_handle:
return False
return True
```
2021-07-13 15:01:00 +02:00
|
|
|
bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *event)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
2024-01-26 18:30:55 -05:00
|
|
|
using namespace blender::ed;
|
2019-09-02 15:30:56 +02:00
|
|
|
/* ui_but_is_interactive() may let some buttons through that should not get a context menu - it
|
|
|
|
|
* doesn't make sense for them. */
|
|
|
|
|
if (ELEM(but->type, UI_BTYPE_LABEL, UI_BTYPE_IMAGE)) {
|
2018-06-24 10:01:13 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
uiPopupMenu *pup;
|
|
|
|
|
uiLayout *layout;
|
2023-08-31 11:59:58 -04:00
|
|
|
const bContextStore *previous_ctx = CTX_store_get(C);
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
2024-06-05 11:28:44 +02:00
|
|
|
pup = UI_popup_menu_begin(C, UI_but_context_menu_title_from_button(*but).c_str(), ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
layout = UI_popup_menu_layout(pup);
|
2020-12-11 22:20:31 +01:00
|
|
|
|
2023-07-03 15:29:08 +02:00
|
|
|
set_layout_context_from_button(C, layout, but);
|
2018-06-24 10:01:13 +02:00
|
|
|
uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT);
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-11-20 17:34:56 +11:00
|
|
|
const bool is_disabled = but->flag & UI_BUT_DISABLED;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-11-20 17:34:56 +11:00
|
|
|
if (is_disabled) {
|
|
|
|
|
/* Suppress editing commands. */
|
|
|
|
|
}
|
|
|
|
|
else if (but->type == UI_BTYPE_TAB) {
|
2018-08-23 19:58:54 +02:00
|
|
|
uiButTab *tab = (uiButTab *)but;
|
|
|
|
|
if (tab->menu) {
|
|
|
|
|
UI_menutype_draw(C, tab->menu, layout);
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-08-23 19:58:54 +02:00
|
|
|
}
|
|
|
|
|
else if (but->rnapoin.data && but->rnaprop) {
|
2018-06-24 10:01:13 +02:00
|
|
|
PointerRNA *ptr = &but->rnapoin;
|
|
|
|
|
PropertyRNA *prop = but->rnaprop;
|
|
|
|
|
const PropertyType type = RNA_property_type(prop);
|
|
|
|
|
const PropertySubType subtype = RNA_property_subtype(prop);
|
2024-03-06 10:02:53 +01:00
|
|
|
bool is_anim = RNA_property_anim_editable(ptr, prop);
|
2020-08-26 10:11:13 +10:00
|
|
|
const bool is_idprop = RNA_property_is_idprop(prop);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-01-15 23:24:20 +11:00
|
|
|
/* second slower test,
|
|
|
|
|
* saved people finding keyframe items in menus when its not possible */
|
|
|
|
|
if (is_anim) {
|
|
|
|
|
is_anim = RNA_property_path_from_ID_check(&but->rnapoin, but->rnaprop);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-15 23:24:20 +11:00
|
|
|
/* determine if we can key a single component of an array */
|
2018-06-24 10:01:13 +02:00
|
|
|
const bool is_array = RNA_property_array_length(&but->rnapoin, but->rnaprop) != 0;
|
|
|
|
|
const bool is_array_component = (is_array && but->rnaindex != -1);
|
2019-08-14 19:17:52 +03:00
|
|
|
const bool is_whole_array = (is_array && but->rnaindex == -1);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-07-16 11:03:11 +02:00
|
|
|
const uint override_status = RNA_property_override_library_status(
|
|
|
|
|
CTX_data_main(C), ptr, prop, -1);
|
2018-06-24 10:01:13 +02:00
|
|
|
const bool is_overridable = (override_status & RNA_OVERRIDE_STATUS_OVERRIDABLE) != 0;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Set the (button_pointer, button_prop)
|
2021-07-08 13:26:55 +10:00
|
|
|
* and pointer data for Python access to the hovered UI element. */
|
2018-06-24 10:01:13 +02:00
|
|
|
uiLayoutSetContextFromBut(layout, but);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Keyframes */
|
|
|
|
|
if (but->flag & UI_BUT_ANIMATED_KEY) {
|
2021-03-10 15:47:50 +11:00
|
|
|
/* Replace/delete keyframes. */
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframes"),
|
|
|
|
|
ICON_KEY_HLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Single Keyframe"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_keyframe_delete_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframes"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_keyframe_delete_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Keyframe"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Replace Keyframe"),
|
|
|
|
|
ICON_KEY_HLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_keyframe_delete_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Keyframe"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* keyframe settings */
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else if (but->flag & UI_BUT_DRIVEN) {
|
|
|
|
|
/* pass */
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-03-25 10:15:20 +11:00
|
|
|
else if (is_anim) {
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframes"),
|
|
|
|
|
ICON_KEY_HLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Single Keyframe"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_insert_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Insert Keyframe"),
|
|
|
|
|
ICON_KEY_HLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if ((but->flag & UI_BUT_ANIMATED) && (but->rnapoin.type != &RNA_NlaStrip)) {
|
|
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_clear_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"),
|
|
|
|
|
ICON_KEY_DEHLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_keyframe_clear_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Single Keyframes"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyframe_clear_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Keyframes"),
|
|
|
|
|
ICON_KEY_DEHLT);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2023-11-21 14:06:53 +01:00
|
|
|
if (but->flag & UI_BUT_ANIMATED) {
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2023-11-21 14:06:53 +01:00
|
|
|
if (is_array_component) {
|
|
|
|
|
PointerRNA op_ptr;
|
|
|
|
|
wmOperatorType *ot;
|
|
|
|
|
ot = WM_operatortype_find("ANIM_OT_view_curve_in_graph_editor", false);
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(
|
|
|
|
|
ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View All in Graph Editor"),
|
|
|
|
|
ICON_GRAPH,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
Anim: reorder "View in Graph Editor" buttons in the context menu
In the property context menu, the operator to View in Graph Editor can
apply to a single value or all values of a vector property.
Currently, these two options are presented in this order (Single, then
All), while all other operators (replace, delete, clear, add to keying
set, reset, copy to selected) follow the opposite order (All, then
Single).
This commit inverts this order for consistency's sake.
Pull Request: https://projects.blender.org/blender/blender/pulls/115962
2023-12-12 09:29:03 +01:00
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(
|
2023-11-21 14:06:53 +01:00
|
|
|
ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View Single in Graph Editor"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
2025-05-21 02:55:04 +02:00
|
|
|
UI_ITEM_NONE);
|
2023-11-21 14:06:53 +01:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
PointerRNA op_ptr;
|
|
|
|
|
wmOperatorType *ot;
|
|
|
|
|
ot = WM_operatortype_find("ANIM_OT_view_curve_in_graph_editor", false);
|
|
|
|
|
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "View in Graph Editor"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2023-11-21 14:06:53 +01:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Drivers */
|
|
|
|
|
if (but->flag & UI_BUT_DRIVEN) {
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_driver_button_remove",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Drivers"),
|
|
|
|
|
ICON_X);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("ANIM_OT_driver_button_remove",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Single Driver"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_driver_button_remove",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Delete Driver"),
|
|
|
|
|
ICON_X);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2024-04-29 18:34:57 +02:00
|
|
|
if (is_whole_array) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_copy_driver_to_selected_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Drivers to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2024-04-29 18:34:57 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_copy_driver_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Driver"),
|
|
|
|
|
ICON_NONE);
|
2019-08-14 19:17:52 +03:00
|
|
|
if (ANIM_driver_can_paste()) {
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_paste_driver_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"),
|
|
|
|
|
ICON_NONE);
|
2019-08-14 19:17:52 +03:00
|
|
|
}
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_copy_driver_to_selected_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Driver to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2024-04-29 18:34:57 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
2024-04-29 18:34:57 +02:00
|
|
|
"UI_OT_copy_driver_to_selected_button",
|
2025-05-23 01:07:12 +02:00
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All Drivers to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2024-04-29 18:34:57 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_driver_button_edit",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Edit Driver"),
|
|
|
|
|
ICON_DRIVER);
|
2019-08-14 19:17:52 +03:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("SCREEN_OT_drivers_editor_show",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"),
|
|
|
|
|
ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else if (but->flag & (UI_BUT_ANIMATED_KEY | UI_BUT_ANIMATED)) {
|
|
|
|
|
/* pass */
|
|
|
|
|
}
|
|
|
|
|
else if (is_anim) {
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_driver_button_add",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Driver"),
|
|
|
|
|
ICON_DRIVER);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-14 19:17:52 +03:00
|
|
|
if (!is_whole_array) {
|
|
|
|
|
if (ANIM_driver_can_paste()) {
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_paste_driver_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Paste Driver"),
|
|
|
|
|
ICON_NONE);
|
2019-08-14 19:17:52 +03:00
|
|
|
}
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("SCREEN_OT_drivers_editor_show",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open Drivers Editor"),
|
|
|
|
|
ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Keying Sets */
|
2024-06-20 17:26:05 +10:00
|
|
|
/* TODO: check on modifiability of Keying Set when doing this. */
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_anim) {
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyingset_button_add",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add All to Keying Set"),
|
|
|
|
|
ICON_KEYINGSET);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyingset_button_add",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add Single to Keying Set"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_keyingset_button_remove",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"),
|
|
|
|
|
ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"ANIM_OT_keyingset_button_add",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Keying Set"),
|
|
|
|
|
ICON_KEYINGSET);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("ANIM_OT_keyingset_button_remove",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Keying Set"),
|
|
|
|
|
ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2018-06-24 10:45:42 +02:00
|
|
|
if (is_overridable) {
|
|
|
|
|
wmOperatorType *ot;
|
|
|
|
|
PointerRNA op_ptr;
|
|
|
|
|
/* Override Operators */
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-28 10:07:36 +01:00
|
|
|
if (but->flag & UI_BUT_OVERRIDDEN) {
|
2018-06-24 10:45:42 +02:00
|
|
|
if (is_array_component) {
|
|
|
|
|
#if 0 /* Disabled for now. */
|
|
|
|
|
ot = WM_operatortype_find("UI_OT_override_type_set_button", false);
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(ot, "Overrides Type", ICON_NONE, WM_OP_INVOKE_DEFAULT, 0);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(ot, "Single Override Type", ICON_NONE, WM_OP_INVOKE_DEFAULT, 0);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
#endif
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_override_remove_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Overrides"),
|
|
|
|
|
ICON_X);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op(
|
|
|
|
|
"UI_OT_override_remove_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Single Override"),
|
|
|
|
|
ICON_X);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:45:42 +02:00
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
#if 0 /* Disabled for now. */
|
2025-05-20 15:19:34 +02:00
|
|
|
op_ptr = layout->op("UI_OT_override_type_set_button",
|
|
|
|
|
"Override Type",
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
0);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
#endif
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_override_remove_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Override"),
|
|
|
|
|
ICON_X);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:45:42 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-24 10:45:42 +02:00
|
|
|
else {
|
|
|
|
|
if (is_array_component) {
|
|
|
|
|
ot = WM_operatortype_find("UI_OT_override_type_set_button", false);
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Overrides"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2025-05-21 02:55:04 +02:00
|
|
|
op_ptr = layout->op(
|
|
|
|
|
ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Single Override"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
}
|
|
|
|
|
else {
|
2025-05-20 15:19:34 +02:00
|
|
|
op_ptr = layout->op("UI_OT_override_type_set_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Define Override"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2018-06-24 10:45:42 +02:00
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Property Operators */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* Copy Property Value
|
|
|
|
|
* Paste Property Value */
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_reset_default_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset All to Default Values"),
|
|
|
|
|
ICON_LOOP_BACK);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op(
|
|
|
|
|
"UI_OT_reset_default_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset Single to Default Value"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_reset_default_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Reset to Default Value"),
|
|
|
|
|
ICON_LOOP_BACK);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-14 19:17:52 +03:00
|
|
|
if (is_idprop && !is_array && ELEM(type, PROP_INT, PROP_FLOAT)) {
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("UI_OT_assign_default_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Value as Default"),
|
|
|
|
|
ICON_NONE);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2018-12-15 22:37:12 +03:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (is_array_component) {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_copy_to_selected_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy All to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
|
|
|
|
op_ptr = layout->op("UI_OT_copy_to_selected_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Single to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", false);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_copy_to_selected_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy to Selected"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "all", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("UI_OT_copy_data_path_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Data Path"),
|
|
|
|
|
ICON_NONE);
|
2025-05-23 01:07:12 +02:00
|
|
|
PointerRNA op_ptr = layout->op(
|
|
|
|
|
"UI_OT_copy_data_path_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy Full Data Path"),
|
|
|
|
|
ICON_NONE);
|
|
|
|
|
RNA_boolean_set(&op_ptr, "full_path", true);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-08-23 09:52:12 +02:00
|
|
|
if (ptr->owner_id && !is_whole_array &&
|
2024-01-02 18:12:54 +01:00
|
|
|
ELEM(type, PROP_BOOLEAN, PROP_INT, PROP_FLOAT, PROP_ENUM))
|
|
|
|
|
{
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("UI_OT_copy_as_driver_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Copy as New Driver"),
|
|
|
|
|
ICON_NONE);
|
2019-07-31 18:42:03 +03:00
|
|
|
}
|
|
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (type == PROP_STRING && ELEM(subtype, PROP_FILEPATH, PROP_DIRPATH)) {
|
2023-10-03 18:44:56 +02:00
|
|
|
if (ui_but_menu_add_path_operators(layout, ptr, prop)) {
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2023-10-03 18:44:56 +02:00
|
|
|
}
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2024-03-28 04:37:08 +01:00
|
|
|
else if (but->optype && but->opptr && RNA_struct_property_is_set(but->opptr, "filepath")) {
|
|
|
|
|
/* Operator with "filepath" string property of PROP_FILEPATH subtype. */
|
|
|
|
|
PropertyRNA *prop = RNA_struct_find_property(but->opptr, "filepath");
|
|
|
|
|
const PropertySubType subtype = RNA_property_subtype(prop);
|
|
|
|
|
|
|
|
|
|
if (prop && RNA_property_type(prop) == PROP_STRING &&
|
|
|
|
|
subtype == PropertySubType::PROP_FILEPATH)
|
|
|
|
|
{
|
|
|
|
|
char filepath[FILE_MAX] = {0};
|
|
|
|
|
RNA_property_string_get(but->opptr, prop, filepath);
|
|
|
|
|
if (filepath[0] && BLI_exists(filepath)) {
|
|
|
|
|
wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true);
|
|
|
|
|
PointerRNA props_ptr;
|
|
|
|
|
char dir[FILE_MAXDIR];
|
|
|
|
|
BLI_path_split_dir_part(filepath, dir, sizeof(dir));
|
2025-05-21 02:55:04 +02:00
|
|
|
props_ptr = layout->op(ot,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Open File Location"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_INVOKE_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2024-03-28 04:37:08 +01:00
|
|
|
RNA_string_set(&props_ptr, "filepath", dir);
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2024-03-28 04:37:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2021-10-08 19:56:24 +02:00
|
|
|
{
|
2024-05-10 11:25:44 +10:00
|
|
|
const ARegion *region = CTX_wm_region_popup(C) ? CTX_wm_region_popup(C) : CTX_wm_region(C);
|
2024-06-28 17:01:28 +02:00
|
|
|
uiButViewItem *view_item_but = (but->type == UI_BTYPE_VIEW_ITEM) ?
|
|
|
|
|
static_cast<uiButViewItem *>(but) :
|
|
|
|
|
static_cast<uiButViewItem *>(
|
|
|
|
|
ui_view_item_find_mouse_over(region, event->xy));
|
UI: Port view item features to base class, merge view item button types
No user visible changes expected.
Merges the tree row and grid tile button types, which were mostly doing
the same things. The idea is that there is a button type for
highlighting, as well as supporting general view item features (e.g.
renaming, drag/drop, etc.). So instead there is a view item button type
now. Also ports view item features like renaming, custom context menus,
drag controllers and drop controllers to `ui::AbstractViewItem` (the new
base class for all view items).
This should be quite an improvement because:
- Merges code that was duplicated over view items.
- Mentioned features (renaming, drag & drop, ...) are much easier to
implement in new view types now. Most of it comes "for free".
- Further features will immediately become availalbe to all views (e.g.
selection).
- Simplifies APIs, there don't have to be functions for individual view
item types anymore.
- View item classes are split and thus less overwhelming visually.
- View item buttons now share all code (drawing, handling, etc.)
- We're soon running out of available button types, this commit merges
two into one.
I was hoping I could do this in multiple smaller commits, but things
were quite intertwined so that would've taken quite some effort.
2022-07-19 16:14:42 +02:00
|
|
|
if (view_item_but) {
|
2023-02-03 16:12:14 +01:00
|
|
|
BLI_assert(view_item_but->type == UI_BTYPE_VIEW_ITEM);
|
2023-07-03 15:29:08 +02:00
|
|
|
|
2023-08-31 11:59:58 -04:00
|
|
|
const bContextStore *prev_ctx = CTX_store_get(C);
|
2023-07-03 15:29:08 +02:00
|
|
|
/* Sub-layout for context override. */
|
2025-04-26 21:07:34 +02:00
|
|
|
uiLayout *sub = &layout->column(false);
|
2023-07-03 15:29:08 +02:00
|
|
|
set_layout_context_from_button(C, sub, view_item_but);
|
2024-03-08 09:16:00 -05:00
|
|
|
view_item_but->view_item->build_context_menu(*C, *sub);
|
2023-07-03 15:29:08 +02:00
|
|
|
|
|
|
|
|
/* Reset context. */
|
|
|
|
|
CTX_store_set(C, prev_ctx);
|
|
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2021-10-08 19:56:24 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 23:29:54 +02:00
|
|
|
/* Expose id specific operators in context menu when button has no operator associated. Otherwise
|
|
|
|
|
* they would appear in nested context menus, see: #126006. */
|
2024-09-25 21:43:41 +02:00
|
|
|
if ((but->optype == nullptr) && (but->apply_func == nullptr) &&
|
|
|
|
|
(but->menu_create_func == nullptr))
|
|
|
|
|
{
|
2024-09-23 23:29:54 +02:00
|
|
|
/* If the button represents an id, it can set the "id" context pointer. */
|
|
|
|
|
if (asset::can_mark_single_from_context(C)) {
|
|
|
|
|
const ID *id = static_cast<const ID *>(CTX_data_pointer_get_type(C, "id", &RNA_ID).data);
|
|
|
|
|
|
|
|
|
|
/* Gray out items depending on if data-block is an asset. Preferably this could be done via
|
|
|
|
|
* operator poll, but that doesn't work since the operator also works with "selected_ids",
|
|
|
|
|
* which isn't cheap to check. */
|
2025-04-26 21:07:34 +02:00
|
|
|
uiLayout *sub = &layout->column(true);
|
2024-09-23 23:29:54 +02:00
|
|
|
uiLayoutSetEnabled(sub, !id->asset_data);
|
2025-05-12 22:14:38 +02:00
|
|
|
sub->op("ASSET_OT_mark_single",
|
2024-09-23 23:29:54 +02:00
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Mark as Asset"),
|
2025-05-12 22:14:38 +02:00
|
|
|
ICON_ASSET_MANAGER);
|
2025-04-26 21:07:34 +02:00
|
|
|
sub = &layout->column(true);
|
2024-09-23 23:29:54 +02:00
|
|
|
uiLayoutSetEnabled(sub, id->asset_data);
|
2025-05-12 22:14:38 +02:00
|
|
|
sub->op("ASSET_OT_clear_single",
|
2024-09-23 23:29:54 +02:00
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Clear Asset"),
|
2025-05-12 22:14:38 +02:00
|
|
|
ICON_NONE);
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2024-09-23 23:29:54 +02:00
|
|
|
}
|
2020-12-11 23:16:29 +01:00
|
|
|
|
2024-09-23 23:29:54 +02:00
|
|
|
MenuType *mt_idtemplate_liboverride = WM_menutype_find("UI_MT_idtemplate_liboverride", true);
|
|
|
|
|
if (mt_idtemplate_liboverride && mt_idtemplate_liboverride->poll(C, mt_idtemplate_liboverride))
|
|
|
|
|
{
|
2025-05-14 23:22:56 +02:00
|
|
|
layout->menu(mt_idtemplate_liboverride, IFACE_("Library Override"), ICON_NONE);
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2024-09-23 23:29:54 +02:00
|
|
|
}
|
2022-08-17 12:10:31 +02:00
|
|
|
}
|
|
|
|
|
|
2019-01-15 23:24:20 +11:00
|
|
|
/* Pointer properties and string properties with
|
|
|
|
|
* prop_search support jumping to target object/bone. */
|
2018-11-20 17:34:56 +11:00
|
|
|
if (but->rnapoin.data && but->rnaprop) {
|
2019-03-15 10:49:26 -03:00
|
|
|
const PropertyType prop_type = RNA_property_type(but->rnaprop);
|
|
|
|
|
if (((prop_type == PROP_POINTER) ||
|
|
|
|
|
(prop_type == PROP_STRING && but->type == UI_BTYPE_SEARCH_MENU &&
|
2020-08-07 14:34:11 +02:00
|
|
|
((uiButSearch *)but)->items_update_fn == ui_rna_collection_search_update_fn)) &&
|
2019-03-15 10:49:26 -03:00
|
|
|
ui_jump_to_target_button_poll(C))
|
|
|
|
|
{
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("UI_OT_jump_to_target_button",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Jump to Target"),
|
|
|
|
|
ICON_NONE);
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2018-11-20 17:34:56 +11:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
2018-08-24 01:37:55 +02:00
|
|
|
/* Favorites Menu */
|
|
|
|
|
if (ui_but_is_user_menu_compatible(C, but)) {
|
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
|
|
|
|
const int w = uiLayoutGetWidth(layout);
|
2018-12-28 14:57:34 +01:00
|
|
|
bool item_found = false;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-12-20 13:00:32 +01:00
|
|
|
uint um_array_len;
|
|
|
|
|
bUserMenu **um_array = ED_screen_user_menus_find(C, &um_array_len);
|
|
|
|
|
for (int um_index = 0; um_index < um_array_len; um_index++) {
|
|
|
|
|
bUserMenu *um = um_array[um_index];
|
2022-11-25 17:09:47 -06:00
|
|
|
if (um == nullptr) {
|
2018-12-20 13:00:32 +01:00
|
|
|
continue;
|
|
|
|
|
}
|
2018-08-24 01:37:55 +02:00
|
|
|
bUserMenuItem *umi = ui_but_user_menu_find(C, but, um);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (umi != nullptr) {
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(
|
2018-12-28 14:57:34 +01:00
|
|
|
block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_MENU_PANEL,
|
2018-08-24 01:37:55 +02:00
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove from Quick Favorites"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2018-08-24 01:37:55 +02:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
"");
|
2023-07-27 13:34:52 +02:00
|
|
|
item_found = true;
|
2023-06-26 16:56:05 +02:00
|
|
|
UI_but_func_set(but2, [um, umi](bContext &) {
|
|
|
|
|
U.runtime.is_dirty = true;
|
|
|
|
|
ED_screen_user_menu_item_remove(&um->items, umi);
|
|
|
|
|
});
|
2018-08-24 01:37:55 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2019-02-13 11:02:47 +01:00
|
|
|
if (um_array) {
|
|
|
|
|
MEM_freeN(um_array);
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-12-28 14:57:34 +01:00
|
|
|
if (!item_found) {
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(
|
2018-12-28 14:57:34 +01:00
|
|
|
block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_MENU_PANEL,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Add to Quick Favorites"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2018-12-28 14:57:34 +01:00
|
|
|
0,
|
|
|
|
|
0,
|
I18n: extract and disambiguate a few messages
Extract
- Add to Quick Favorites tooltip.
- "Mask", the name of a newly created mask (DATA_).
- "New" in the context of the new mask ID button.
- A few strings using BLI_STR_UTF8_ defines were not extracted.
Take the special characters out of the translation macros.
- "External" menu items from the filebrowser's Files context
menu (right-click on a file). These items were already extracted,
but not translated.
Improve
- Separate formatted error message "%s is not compatible with
["the specified", "any"] 'refresh' options" into two messages.
Disambiguate
- Use Action context for new F-modifiers' names. This is already used
for the "type" operator prop.
- Translate ImportHelper's default confirmation text using the
Operator context, as it uses the operator name which is extracted
with this context.
- "Scale" can be a noun, the scale of something, or a verb, to scale
something. The latter mostly uses the Operator context, so apply
this context to verbs, and the default contexts to nouns.
- "Scale Influence" can mean "Influence on Scale" (tracking
stabilization) and "to Scale the Influence" (dynamic paint canvas).
- "Object Line Art" as type of Line Art to add, as opposed to the
active object's Line Art settings.
- Float to Integer node: use NodeTree context for the node label, as
this is already extracted and used for the enum.
Do not translate
- Sequencer labels containing only a string formatting field.
Some issues reported by Gabriel Gazzán and Ye Gui.
Pull Request: https://projects.blender.org/blender/blender/pulls/122283
2024-05-27 19:33:35 +02:00
|
|
|
TIP_("Add to a user defined context menu (stored in the user preferences)"));
|
2023-06-26 16:56:05 +02:00
|
|
|
UI_but_func_set(but2, [but](bContext &C) {
|
|
|
|
|
bUserMenu *um = ED_screen_user_menu_ensure(&C);
|
|
|
|
|
U.runtime.is_dirty = true;
|
|
|
|
|
ui_but_user_menu_add(&C, but, um);
|
|
|
|
|
});
|
2018-12-28 14:57:34 +01:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2018-08-24 01:37:55 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
/* Shortcut menu */
|
|
|
|
|
IDProperty *prop;
|
|
|
|
|
const char *idname = shortcut_get_operator_property(C, but, &prop);
|
2022-11-25 17:09:47 -06:00
|
|
|
if (idname != nullptr) {
|
2018-06-24 10:01:13 +02:00
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
2020-08-26 10:11:13 +10:00
|
|
|
const int w = uiLayoutGetWidth(layout);
|
2019-05-10 11:55:52 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* We want to know if this op has a shortcut, be it hotkey or not. */
|
2020-12-04 08:03:14 -06:00
|
|
|
wmKeyMap *km;
|
2018-12-12 21:39:55 +11:00
|
|
|
wmKeyMapItem *kmi = WM_key_event_operator(
|
2019-05-10 11:55:52 +02:00
|
|
|
C, idname, but->opcontext, prop, EVT_TYPE_MASK_ALL, 0, &km);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-08-22 18:54:06 +02:00
|
|
|
/* We do have a shortcut, but only keyboard ones are editable that way... */
|
2018-06-24 10:01:13 +02:00
|
|
|
if (kmi) {
|
2022-03-08 13:54:47 +11:00
|
|
|
if (ISKEYBOARD(kmi->type) || ISNDOF_BUTTON(kmi->type)) {
|
2018-06-24 10:01:13 +02:00
|
|
|
#if 0 /* would rather use a block but, but gets weirdly positioned... */
|
2019-04-17 08:24:14 +02:00
|
|
|
uiDefBlockBut(block,
|
|
|
|
|
menu_change_shortcut,
|
|
|
|
|
but,
|
|
|
|
|
"Change Shortcut",
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
uiLayoutGetWidth(layout),
|
|
|
|
|
UI_UNIT_Y,
|
|
|
|
|
"");
|
2018-06-24 10:01:13 +02:00
|
|
|
#endif
|
|
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(
|
|
|
|
|
block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_HAND,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Change Shortcut"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2020-12-04 08:03:14 -06:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
"");
|
2023-06-26 16:56:05 +02:00
|
|
|
UI_but_func_set(but2, [but](bContext &C) {
|
|
|
|
|
UI_popup_block_invoke(&C, menu_change_shortcut, but, nullptr);
|
|
|
|
|
});
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
else {
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_HAND,
|
|
|
|
|
IFACE_("Non-Keyboard Shortcut"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2020-12-04 08:03:14 -06:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
TIP_("Only keyboard shortcuts can be edited that way, "
|
|
|
|
|
"please use User Preferences otherwise"));
|
2018-06-24 10:01:13 +02:00
|
|
|
UI_but_flag_enable(but2, UI_BUT_DISABLED);
|
|
|
|
|
}
|
2019-07-18 14:13:20 +02:00
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(
|
|
|
|
|
block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_BLANK1,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Remove Shortcut"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2020-12-04 08:03:14 -06:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
"");
|
2023-06-26 16:56:05 +02:00
|
|
|
UI_but_func_set(but2, [but](bContext &C) { remove_shortcut_func(&C, but); });
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-08-22 18:54:06 +02:00
|
|
|
/* only show 'assign' if there's a suitable key map for it to go in */
|
2019-05-10 11:55:52 +02:00
|
|
|
else if (WM_keymap_guess_opname(C, idname)) {
|
2020-12-04 08:03:14 -06:00
|
|
|
uiBut *but2 = uiDefIconTextBut(
|
|
|
|
|
block,
|
|
|
|
|
UI_BTYPE_BUT,
|
|
|
|
|
0,
|
|
|
|
|
ICON_HAND,
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Assign Shortcut"),
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
w,
|
|
|
|
|
UI_UNIT_Y,
|
2022-11-25 17:09:47 -06:00
|
|
|
nullptr,
|
2020-12-04 08:03:14 -06:00
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
"");
|
2023-06-26 16:56:05 +02:00
|
|
|
UI_but_func_set(but2, [but](bContext &C) {
|
|
|
|
|
UI_popup_block_ex(&C, menu_add_shortcut, nullptr, menu_add_shortcut_cancel, but, nullptr);
|
|
|
|
|
});
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2019-05-10 11:55:52 +02:00
|
|
|
shortcut_free_operator_property(prop);
|
|
|
|
|
|
2018-06-30 12:08:08 +02:00
|
|
|
/* Set the operator pointer for python access */
|
|
|
|
|
uiLayoutSetContextFromBut(layout, but);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-05-13 17:54:26 +02:00
|
|
|
layout->separator();
|
2018-06-30 12:08:08 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
{ /* Docs */
|
2024-02-19 12:17:52 +11:00
|
|
|
if (std::optional<std::string> manual_id = UI_but_online_manual_id(but)) {
|
2018-06-24 10:01:13 +02:00
|
|
|
PointerRNA ptr_props;
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("WM_OT_doc_view_manual_ui_context",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Manual"),
|
|
|
|
|
ICON_URL);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-08-22 16:29:41 +02:00
|
|
|
if (U.flag & USER_DEVELOPER_UI) {
|
2025-05-20 15:19:34 +02:00
|
|
|
ptr_props = layout->op(
|
|
|
|
|
"WM_OT_doc_view",
|
|
|
|
|
CTX_IFACE_(BLT_I18NCONTEXT_OPERATOR_DEFAULT, "Online Python Reference"),
|
|
|
|
|
ICON_NONE,
|
|
|
|
|
WM_OP_EXEC_DEFAULT,
|
|
|
|
|
UI_ITEM_NONE);
|
2024-02-19 12:17:52 +11:00
|
|
|
RNA_string_set(&ptr_props, "doc_id", manual_id.value().c_str());
|
2018-08-22 16:29:41 +02:00
|
|
|
}
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-08-22 16:29:41 +02:00
|
|
|
if (but->optype && U.flag & USER_DEVELOPER_UI) {
|
2025-05-12 22:14:38 +02:00
|
|
|
layout->op("UI_OT_copy_python_command_button", std::nullopt, ICON_NONE);
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* perhaps we should move this into (G.debug & G_DEBUG) - campbell */
|
|
|
|
|
if (U.flag & USER_DEVELOPER_UI) {
|
|
|
|
|
if (ui_block_is_menu(but->block) == false) {
|
2025-05-20 15:19:34 +02:00
|
|
|
layout->op("UI_OT_editsource", std::nullopt, ICON_NONE, WM_OP_INVOKE_DEFAULT, UI_ITEM_NONE);
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-08-24 01:37:55 +02:00
|
|
|
/* Show header tools for header buttons. */
|
|
|
|
|
if (ui_block_is_popup_any(but->block) == false) {
|
2020-03-06 16:56:42 +01:00
|
|
|
const ARegion *region = CTX_wm_region(C);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-03-06 16:56:42 +01:00
|
|
|
if (!region) {
|
2018-10-31 00:41:03 +01:00
|
|
|
/* skip */
|
|
|
|
|
}
|
2020-03-06 16:56:42 +01:00
|
|
|
else if (ELEM(region->regiontype, RGN_TYPE_HEADER, RGN_TYPE_TOOL_HEADER)) {
|
2025-05-15 21:26:49 +02:00
|
|
|
layout->menu_fn(IFACE_("Header"), ICON_NONE, ED_screens_header_tools_menu_create, nullptr);
|
2018-08-24 01:37:55 +02:00
|
|
|
}
|
2020-03-06 16:56:42 +01:00
|
|
|
else if (region->regiontype == RGN_TYPE_NAV_BAR) {
|
2025-05-15 21:26:49 +02:00
|
|
|
layout->menu_fn(IFACE_("Navigation Bar"), ICON_NONE, ED_buttons_navbar_menu, nullptr);
|
2025-04-08 18:43:18 +02:00
|
|
|
const ScrArea *area = CTX_wm_area(C);
|
|
|
|
|
if (area && area->spacetype == SPACE_PROPERTIES) {
|
2025-05-15 21:26:49 +02:00
|
|
|
layout->menu_fn(IFACE_("Visible Tabs"), ICON_NONE, ED_buttons_visible_tabs_menu, nullptr);
|
2025-04-08 18:43:18 +02:00
|
|
|
}
|
2018-10-31 00:41:03 +01:00
|
|
|
}
|
2020-03-06 16:56:42 +01:00
|
|
|
else if (region->regiontype == RGN_TYPE_FOOTER) {
|
2025-05-15 21:26:49 +02:00
|
|
|
layout->menu_fn(IFACE_("Footer"), ICON_NONE, ED_screens_footer_tools_menu_create, nullptr);
|
2018-08-24 01:37:55 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
}
|
|
|
|
|
|
UI: Support UI list tooltips, defined via Python scripts
Makes it possible to create tooltips for UI list rows, which can be
filled in .py scripts, similar to how they can extend other menus. This
is used by the (to be committed) Pose Library add-on to display pose
operations (selecting bones of a pose, blending a pose, etc).
It's important that the Python scripts check if the UI list is the
correct one by checking the list ID.
For this to work, a new `bpy.context.ui_list` can be checked. For
example, the Pose Library add-on does the following check:
```
def is_pose_asset_view() -> bool:
# Important: Must check context first, or the menu is added for every kind of list.
list = getattr(context, "ui_list", None)
if not list or list.bl_idname != "UI_UL_asset_view" or list.list_id != "pose_assets":
return False
if not context.asset_handle:
return False
return True
```
2021-07-13 15:01:00 +02:00
|
|
|
/* UI List item context menu. Scripts can add items to it, by default there's nothing shown. */
|
2024-05-10 11:25:44 +10:00
|
|
|
const ARegion *region = CTX_wm_region_popup(C) ? CTX_wm_region_popup(C) : CTX_wm_region(C);
|
2022-11-25 17:09:47 -06:00
|
|
|
const bool is_inside_listbox = ui_list_find_mouse_over(region, event) != nullptr;
|
UI: Support UI list tooltips, defined via Python scripts
Makes it possible to create tooltips for UI list rows, which can be
filled in .py scripts, similar to how they can extend other menus. This
is used by the (to be committed) Pose Library add-on to display pose
operations (selecting bones of a pose, blending a pose, etc).
It's important that the Python scripts check if the UI list is the
correct one by checking the list ID.
For this to work, a new `bpy.context.ui_list` can be checked. For
example, the Pose Library add-on does the following check:
```
def is_pose_asset_view() -> bool:
# Important: Must check context first, or the menu is added for every kind of list.
list = getattr(context, "ui_list", None)
if not list or list.bl_idname != "UI_UL_asset_view" or list.list_id != "pose_assets":
return False
if not context.asset_handle:
return False
return True
```
2021-07-13 15:01:00 +02:00
|
|
|
const bool is_inside_listrow = is_inside_listbox ?
|
2022-11-25 17:09:47 -06:00
|
|
|
ui_list_row_find_mouse_over(region, event->xy) != nullptr :
|
UI: Support UI list tooltips, defined via Python scripts
Makes it possible to create tooltips for UI list rows, which can be
filled in .py scripts, similar to how they can extend other menus. This
is used by the (to be committed) Pose Library add-on to display pose
operations (selecting bones of a pose, blending a pose, etc).
It's important that the Python scripts check if the UI list is the
correct one by checking the list ID.
For this to work, a new `bpy.context.ui_list` can be checked. For
example, the Pose Library add-on does the following check:
```
def is_pose_asset_view() -> bool:
# Important: Must check context first, or the menu is added for every kind of list.
list = getattr(context, "ui_list", None)
if not list or list.bl_idname != "UI_UL_asset_view" or list.list_id != "pose_assets":
return False
if not context.asset_handle:
return False
return True
```
2021-07-13 15:01:00 +02:00
|
|
|
false;
|
|
|
|
|
if (is_inside_listrow) {
|
|
|
|
|
MenuType *mt = WM_menutype_find("UI_MT_list_item_context_menu", true);
|
|
|
|
|
if (mt) {
|
2025-04-26 21:07:34 +02:00
|
|
|
UI_menutype_draw(C, mt, &layout->column(false));
|
UI: Support UI list tooltips, defined via Python scripts
Makes it possible to create tooltips for UI list rows, which can be
filled in .py scripts, similar to how they can extend other menus. This
is used by the (to be committed) Pose Library add-on to display pose
operations (selecting bones of a pose, blending a pose, etc).
It's important that the Python scripts check if the UI list is the
correct one by checking the list ID.
For this to work, a new `bpy.context.ui_list` can be checked. For
example, the Pose Library add-on does the following check:
```
def is_pose_asset_view() -> bool:
# Important: Must check context first, or the menu is added for every kind of list.
list = getattr(context, "ui_list", None)
if not list or list.bl_idname != "UI_UL_asset_view" or list.list_id != "pose_assets":
return False
if not context.asset_handle:
return False
return True
```
2021-07-13 15:01:00 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-18 14:42:48 +02:00
|
|
|
MenuType *mt = WM_menutype_find("UI_MT_button_context_menu", true);
|
2018-06-24 10:01:13 +02:00
|
|
|
if (mt) {
|
2025-04-26 21:07:34 +02:00
|
|
|
UI_menutype_draw(C, mt, &layout->column(false));
|
2018-06-24 10:01:13 +02:00
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-11 22:20:31 +01:00
|
|
|
if (but->context) {
|
|
|
|
|
CTX_store_set(C, previous_ctx);
|
|
|
|
|
}
|
|
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
return UI_popup_menu_end_or_cancel(C, pup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|
|
|
|
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
|
/** \name Panel Context Menu
|
|
|
|
|
* \{ */
|
|
|
|
|
|
2020-04-03 22:20:25 -05:00
|
|
|
void ui_popup_context_menu_for_panel(bContext *C, ARegion *region, Panel *panel)
|
2018-06-24 10:01:13 +02:00
|
|
|
{
|
2020-04-03 14:23:21 +02:00
|
|
|
bScreen *screen = CTX_wm_screen(C);
|
2020-03-06 16:56:42 +01:00
|
|
|
const bool has_panel_category = UI_panel_category_is_visible(region);
|
2018-06-24 10:01:13 +02:00
|
|
|
const bool any_item_visible = has_panel_category;
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (!any_item_visible) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-11-25 17:09:47 -06:00
|
|
|
if (panel->type->parent != nullptr) {
|
2019-05-13 16:53:40 +10:00
|
|
|
return;
|
|
|
|
|
}
|
2021-10-22 13:13:48 +02:00
|
|
|
if (!UI_panel_can_be_pinned(panel)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2025-01-24 16:45:32 +01:00
|
|
|
PointerRNA ptr = RNA_pointer_create_discrete(&screen->id, &RNA_Panel, panel);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2020-12-04 08:03:14 -06:00
|
|
|
uiPopupMenu *pup = UI_popup_menu_begin(C, IFACE_("Panel"), ICON_NONE);
|
|
|
|
|
uiLayout *layout = UI_popup_menu_layout(pup);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
if (has_panel_category) {
|
|
|
|
|
char tmpstr[80];
|
2023-05-09 12:50:37 +10:00
|
|
|
SNPRINTF(tmpstr, "%s" UI_SEP_CHAR_S "%s", IFACE_("Pin"), IFACE_("Shift Left Mouse"));
|
2025-05-08 20:45:37 +02:00
|
|
|
layout->prop(&ptr, "use_pin", UI_ITEM_NONE, tmpstr, ICON_NONE);
|
2019-04-17 06:17:24 +02:00
|
|
|
|
2018-06-24 10:01:13 +02:00
|
|
|
/* evil, force shortcut flag */
|
|
|
|
|
{
|
|
|
|
|
uiBlock *block = uiLayoutGetBlock(layout);
|
2025-02-14 15:29:26 +01:00
|
|
|
uiBut *but = block->buttons.last().get();
|
2018-06-24 10:01:13 +02:00
|
|
|
but->flag |= UI_BUT_HAS_SEP_CHAR;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
UI_popup_menu_end(C, pup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** \} */
|