- Use typed enum for the wrap axis. - Rename `bounds` to `wrap_region`. - Take a `rcti` argument instead of an `int[4]`. - Pair wrap & wrap_region arguments together.
6157 lines
196 KiB
C++
6157 lines
196 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2007 Blender Foundation. All rights reserved. */
|
|
|
|
/** \file
|
|
* \ingroup wm
|
|
*
|
|
* Handle events and notifiers from GHOST input (mouse, keyboard, tablet, NDOF).
|
|
*
|
|
* Also some operator reports utility functions.
|
|
*/
|
|
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include "AS_asset_library.h"
|
|
|
|
#include "DNA_listBase.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
#include "DNA_userdef_types.h"
|
|
#include "DNA_windowmanager_types.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "CLG_log.h"
|
|
|
|
#include "GHOST_C-api.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_dynstr.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_math.h"
|
|
#include "BLI_timer.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_customdata.h"
|
|
#include "BKE_global.h"
|
|
#include "BKE_idprop.h"
|
|
#include "BKE_lib_remap.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_report.h"
|
|
#include "BKE_scene.h"
|
|
#include "BKE_screen.h"
|
|
#include "BKE_undo_system.h"
|
|
#include "BKE_workspace.h"
|
|
|
|
#include "BKE_sound.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "ED_asset.h"
|
|
#include "ED_fileselect.h"
|
|
#include "ED_info.h"
|
|
#include "ED_render.h"
|
|
#include "ED_screen.h"
|
|
#include "ED_undo.h"
|
|
#include "ED_util.h"
|
|
#include "ED_view3d.h"
|
|
|
|
#include "GPU_context.h"
|
|
|
|
#include "RNA_access.h"
|
|
|
|
#include "UI_interface.h"
|
|
|
|
#include "PIL_time.h"
|
|
|
|
#include "WM_api.h"
|
|
#include "WM_message.h"
|
|
#include "WM_toolsystem.h"
|
|
#include "WM_types.h"
|
|
|
|
#include "wm.h"
|
|
#include "wm_event_system.h"
|
|
#include "wm_event_types.h"
|
|
#include "wm_surface.h"
|
|
#include "wm_window.h"
|
|
|
|
#include "DEG_depsgraph.h"
|
|
#include "DEG_depsgraph_query.h"
|
|
|
|
/**
|
|
* When a gizmo is highlighted and uses click/drag events,
|
|
* this prevents mouse button press events from being passed through to other key-maps
|
|
* which would obscure those events.
|
|
*
|
|
* This allows gizmos that only use drag to co-exist with tools that use click.
|
|
*
|
|
* Without tools using press events which would prevent click/drag events getting to the gizmos.
|
|
*
|
|
* This is not a fool proof solution since it's possible the gizmo operators would pass
|
|
* through these events when called, see: T65479.
|
|
*/
|
|
#define USE_GIZMO_MOUSE_PRIORITY_HACK
|
|
|
|
static void wm_notifier_clear(wmNotifier *note);
|
|
static bool wm_notifier_is_clear(const wmNotifier *note);
|
|
|
|
static int wm_operator_call_internal(bContext *C,
|
|
wmOperatorType *ot,
|
|
PointerRNA *properties,
|
|
ReportList *reports,
|
|
const wmOperatorCallContext context,
|
|
const bool poll_only,
|
|
const wmEvent *event);
|
|
|
|
static bool wm_operator_check_locked_interface(bContext *C, wmOperatorType *ot);
|
|
static wmEvent *wm_event_add_mousemove_to_head(wmWindow *win);
|
|
static void wm_operator_free_for_fileselect(wmOperator *file_operator);
|
|
|
|
static void wm_event_state_update_and_click_set_ex(wmEvent *event,
|
|
wmEvent *event_state,
|
|
const bool is_keyboard,
|
|
const bool check_double_click);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Event Management
|
|
* \{ */
|
|
|
|
wmEvent *wm_event_add_ex(wmWindow *win,
|
|
const wmEvent *event_to_add,
|
|
const wmEvent *event_to_add_after)
|
|
{
|
|
wmEvent *event = MEM_new<wmEvent>(__func__);
|
|
|
|
*event = *event_to_add;
|
|
|
|
if (event_to_add_after == nullptr) {
|
|
BLI_addtail(&win->event_queue, event);
|
|
}
|
|
else {
|
|
/* NOTE: strictly speaking this breaks const-correctness,
|
|
* however we're only changing 'next' member. */
|
|
BLI_insertlinkafter(&win->event_queue, (void *)event_to_add_after, event);
|
|
}
|
|
return event;
|
|
}
|
|
|
|
wmEvent *wm_event_add(wmWindow *win, const wmEvent *event_to_add)
|
|
{
|
|
return wm_event_add_ex(win, event_to_add, nullptr);
|
|
}
|
|
|
|
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
|
|
{
|
|
if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) {
|
|
BLI_assert_unreachable();
|
|
return nullptr;
|
|
}
|
|
wmEvent *event = wm_event_add(win, event_to_add);
|
|
|
|
/* Logic for setting previous value is documented on the #wmEvent struct,
|
|
* see #wm_event_add_ghostevent for the implementation of logic this follows. */
|
|
copy_v2_v2_int(win->eventstate->xy, event->xy);
|
|
|
|
if (event->type == MOUSEMOVE) {
|
|
copy_v2_v2_int(win->eventstate->prev_xy, win->eventstate->xy);
|
|
copy_v2_v2_int(event->prev_xy, win->eventstate->xy);
|
|
}
|
|
else if (ISKEYBOARD_OR_BUTTON(event->type)) {
|
|
wm_event_state_update_and_click_set_ex(event, win->eventstate, ISKEYBOARD(event->type), false);
|
|
}
|
|
return event;
|
|
}
|
|
|
|
static void wm_event_custom_free(wmEvent *event)
|
|
{
|
|
if ((event->customdata && event->customdata_free) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* NOTE: pointer to #ListBase struct elsewhere. */
|
|
if (event->custom == EVT_DATA_DRAGDROP) {
|
|
ListBase *lb = static_cast<ListBase *>(event->customdata);
|
|
WM_drag_free_list(lb);
|
|
}
|
|
else {
|
|
MEM_freeN(event->customdata);
|
|
}
|
|
}
|
|
|
|
static void wm_event_custom_clear(wmEvent *event)
|
|
{
|
|
event->custom = 0;
|
|
event->customdata = nullptr;
|
|
event->customdata_free = false;
|
|
}
|
|
|
|
void wm_event_free(wmEvent *event)
|
|
{
|
|
#ifndef NDEBUG
|
|
/* Don't use assert here because it's fairly harmless in most cases,
|
|
* more an issue of correctness, something we should avoid in general. */
|
|
if ((event->flag & WM_EVENT_IS_REPEAT) && !ISKEYBOARD(event->type)) {
|
|
printf("%s: 'is_repeat=true' for non-keyboard event, this should not happen.\n", __func__);
|
|
WM_event_print(event);
|
|
}
|
|
if (ISMOUSE_MOTION(event->type) && (event->val != KM_NOTHING)) {
|
|
printf("%s: 'val != NOTHING' for a cursor motion event, this should not happen.\n", __func__);
|
|
WM_event_print(event);
|
|
}
|
|
#endif
|
|
|
|
wm_event_custom_free(event);
|
|
|
|
MEM_freeN(event);
|
|
}
|
|
|
|
/** A version of #wm_event_free that holds the last handled event. */
|
|
static void wm_event_free_last_handled(wmWindow *win, wmEvent *event)
|
|
{
|
|
/* Don't rely on this pointer being valid,
|
|
* callers should behave as if the memory has been freed.
|
|
* As this function should be interchangeable with #wm_event_free. */
|
|
#ifndef NDEBUG
|
|
{
|
|
wmEvent *event_copy = static_cast<wmEvent *>(MEM_dupallocN(event));
|
|
MEM_freeN(event);
|
|
event = event_copy;
|
|
}
|
|
#endif
|
|
|
|
if (win->event_last_handled) {
|
|
wm_event_free(win->event_last_handled);
|
|
}
|
|
/* Don't store custom data in the last handled event as we don't have control how long this event
|
|
* will be stored and the referenced data may become invalid (also it's not needed currently). */
|
|
wm_event_custom_free(event);
|
|
wm_event_custom_clear(event);
|
|
win->event_last_handled = event;
|
|
}
|
|
|
|
static void wm_event_free_last(wmWindow *win)
|
|
{
|
|
wmEvent *event = static_cast<wmEvent *>(BLI_poptail(&win->event_queue));
|
|
if (event != nullptr) {
|
|
wm_event_free(event);
|
|
}
|
|
}
|
|
|
|
void wm_event_free_all(wmWindow *win)
|
|
{
|
|
wmEvent *event;
|
|
while ((event = static_cast<wmEvent *>(BLI_pophead(&win->event_queue)))) {
|
|
wm_event_free(event);
|
|
}
|
|
}
|
|
|
|
void wm_event_init_from_window(wmWindow *win, wmEvent *event)
|
|
{
|
|
*event = *(win->eventstate);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Notifiers & Listeners
|
|
* \{ */
|
|
|
|
/**
|
|
* Hash for #wmWindowManager.notifier_queue_set, ignores `window`.
|
|
*/
|
|
static uint note_hash_for_queue_fn(const void *ptr)
|
|
{
|
|
const wmNotifier *note = static_cast<const wmNotifier *>(ptr);
|
|
return (BLI_ghashutil_ptrhash(note->reference) ^
|
|
(note->category | note->data | note->subtype | note->action));
|
|
}
|
|
|
|
/**
|
|
* Comparison for #wmWindowManager.notifier_queue_set
|
|
*
|
|
* \note This is not an exact equality function as the `window` is ignored.
|
|
*/
|
|
static bool note_cmp_for_queue_fn(const void *a, const void *b)
|
|
{
|
|
const wmNotifier *note_a = static_cast<const wmNotifier *>(a);
|
|
const wmNotifier *note_b = static_cast<const wmNotifier *>(b);
|
|
return !(((note_a->category | note_a->data | note_a->subtype | note_a->action) ==
|
|
(note_b->category | note_b->data | note_b->subtype | note_b->action)) &&
|
|
(note_a->reference == note_b->reference));
|
|
}
|
|
|
|
void WM_event_add_notifier_ex(wmWindowManager *wm, const wmWindow *win, uint type, void *reference)
|
|
{
|
|
if (wm == nullptr) {
|
|
/* There may be some cases where e.g. `G_MAIN` is not actually the real current main, but some
|
|
* other temporary one (e.g. during liboverride processing over linked data), leading to null
|
|
* window manager.
|
|
*
|
|
* This is fairly bad and weak, but unfortunately RNA does not have any way to operate over
|
|
* another main than G_MAIN currently. */
|
|
return;
|
|
}
|
|
|
|
wmNotifier note_test = {nullptr};
|
|
|
|
note_test.window = win;
|
|
|
|
note_test.category = type & NOTE_CATEGORY;
|
|
note_test.data = type & NOTE_DATA;
|
|
note_test.subtype = type & NOTE_SUBTYPE;
|
|
note_test.action = type & NOTE_ACTION;
|
|
note_test.reference = reference;
|
|
|
|
BLI_assert(!wm_notifier_is_clear(¬e_test));
|
|
|
|
if (wm->notifier_queue_set == nullptr) {
|
|
wm->notifier_queue_set = BLI_gset_new_ex(
|
|
note_hash_for_queue_fn, note_cmp_for_queue_fn, __func__, 1024);
|
|
}
|
|
|
|
void **note_p;
|
|
if (BLI_gset_ensure_p_ex(wm->notifier_queue_set, ¬e_test, ¬e_p)) {
|
|
return;
|
|
}
|
|
wmNotifier *note = MEM_new<wmNotifier>(__func__);
|
|
*note = note_test;
|
|
*note_p = note;
|
|
BLI_addtail(&wm->notifier_queue, note);
|
|
}
|
|
|
|
/* XXX: in future, which notifiers to send to other windows? */
|
|
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
|
|
{
|
|
WM_event_add_notifier_ex(CTX_wm_manager(C), CTX_wm_window(C), type, reference);
|
|
}
|
|
|
|
void WM_main_add_notifier(uint type, void *reference)
|
|
{
|
|
Main *bmain = G_MAIN;
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
|
|
|
WM_event_add_notifier_ex(wm, nullptr, type, reference);
|
|
}
|
|
|
|
void WM_main_remove_notifier_reference(const void *reference)
|
|
{
|
|
Main *bmain = G_MAIN;
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
|
|
|
if (wm) {
|
|
LISTBASE_FOREACH_MUTABLE (wmNotifier *, note, &wm->notifier_queue) {
|
|
if (note->reference == reference) {
|
|
const bool removed = BLI_gset_remove(wm->notifier_queue_set, note, nullptr);
|
|
BLI_assert(removed);
|
|
UNUSED_VARS_NDEBUG(removed);
|
|
/* Don't remove because this causes problems for #wm_event_do_notifiers
|
|
* which may be looping on the data (deleting screens). */
|
|
wm_notifier_clear(note);
|
|
}
|
|
}
|
|
|
|
/* Remap instead. */
|
|
#if 0
|
|
if (wm->message_bus) {
|
|
WM_msg_id_remove(wm->message_bus, reference);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void wm_main_remap_assetlist(ID *old_id, ID *new_id, void * /*user_data*/)
|
|
{
|
|
ED_assetlist_storage_id_remap(old_id, new_id);
|
|
}
|
|
|
|
static void wm_main_remap_msgbus_notify(ID *old_id, ID *new_id, void *user_data)
|
|
{
|
|
wmMsgBus *mbus = static_cast<wmMsgBus *>(user_data);
|
|
if (new_id != nullptr) {
|
|
WM_msg_id_update(mbus, old_id, new_id);
|
|
}
|
|
else {
|
|
WM_msg_id_remove(mbus, old_id);
|
|
}
|
|
}
|
|
|
|
void WM_main_remap_editor_id_reference(const IDRemapper *mappings)
|
|
{
|
|
Main *bmain = G_MAIN;
|
|
|
|
LISTBASE_FOREACH (bScreen *, screen, &bmain->screens) {
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
LISTBASE_FOREACH (SpaceLink *, sl, &area->spacedata) {
|
|
ED_spacedata_id_remap(area, sl, mappings);
|
|
}
|
|
}
|
|
}
|
|
|
|
BKE_id_remapper_iter(mappings, wm_main_remap_assetlist, nullptr);
|
|
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
|
if (wm && wm->message_bus) {
|
|
BKE_id_remapper_iter(mappings, wm_main_remap_msgbus_notify, wm->message_bus);
|
|
}
|
|
|
|
AS_asset_library_remap_ids(mappings);
|
|
}
|
|
|
|
static void wm_notifier_clear(wmNotifier *note)
|
|
{
|
|
/* nullptr the entire notifier, only leaving (`next`, `prev`) members intact. */
|
|
memset(((char *)note) + sizeof(Link), 0, sizeof(*note) - sizeof(Link));
|
|
note->category = NOTE_CATEGORY_TAG_CLEARED;
|
|
}
|
|
|
|
static bool wm_notifier_is_clear(const wmNotifier *note)
|
|
{
|
|
return note->category == NOTE_CATEGORY_TAG_CLEARED;
|
|
}
|
|
|
|
void wm_event_do_depsgraph(bContext *C, bool is_after_open_file)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
/* The whole idea of locked interface is to prevent viewport and whatever thread from
|
|
* modifying the same data. Because of this, we can not perform dependency graph update. */
|
|
if (wm->is_interface_locked) {
|
|
return;
|
|
}
|
|
/* Combine data-masks so one window doesn't disable UV's in another T26448. */
|
|
CustomData_MeshMasks win_combine_v3d_datamask = {0};
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
const Scene *scene = WM_window_get_active_scene(win);
|
|
const bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
ED_view3d_screen_datamask(C, scene, screen, &win_combine_v3d_datamask);
|
|
}
|
|
/* Update all the dependency graphs of visible view layers. */
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
|
|
Main *bmain = CTX_data_main(C);
|
|
/* Copied to set's in scene_update_tagged_recursive() */
|
|
scene->customdata_mask = win_combine_v3d_datamask;
|
|
/* XXX, hack so operators can enforce data-masks T26482, GPU render. */
|
|
CustomData_MeshMasks_update(&scene->customdata_mask, &scene->customdata_mask_modal);
|
|
/* TODO(sergey): For now all dependency graphs which are evaluated from
|
|
* workspace are considered active. This will work all fine with "locked"
|
|
* view layer and time across windows. This is to be granted separately,
|
|
* and for until then we have to accept ambiguities when object is shared
|
|
* across visible view layers and has overrides on it. */
|
|
Depsgraph *depsgraph = BKE_scene_ensure_depsgraph(bmain, scene, view_layer);
|
|
if (is_after_open_file) {
|
|
DEG_graph_tag_on_visible_update(depsgraph, true);
|
|
}
|
|
DEG_make_active(depsgraph);
|
|
BKE_scene_graph_update_tagged(depsgraph, bmain);
|
|
}
|
|
|
|
wm_surfaces_do_depsgraph(C);
|
|
}
|
|
|
|
void wm_event_do_refresh_wm_and_depsgraph(bContext *C)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
/* Cached: editor refresh callbacks now, they get context. */
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
const bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
CTX_wm_window_set(C, win);
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
if (area->do_refresh) {
|
|
CTX_wm_area_set(C, area);
|
|
ED_area_do_refresh(C, area);
|
|
}
|
|
}
|
|
}
|
|
|
|
wm_event_do_depsgraph(C, false);
|
|
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
static void wm_event_execute_timers(bContext *C)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
if (UNLIKELY(wm == nullptr)) {
|
|
return;
|
|
}
|
|
|
|
/* Set the first window as context, so that there is some minimal context. This avoids crashes
|
|
* when calling code that assumes that there is always a window in the context (which many
|
|
* operators do). */
|
|
CTX_wm_window_set(C, static_cast<wmWindow *>(wm->windows.first));
|
|
BLI_timer_execute();
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
void wm_event_do_notifiers(bContext *C)
|
|
{
|
|
/* Ensure inside render boundary. */
|
|
GPU_render_begin();
|
|
|
|
/* Run the timer before assigning `wm` in the unlikely case a timer loads a file, see T80028. */
|
|
wm_event_execute_timers(C);
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
if (wm == nullptr) {
|
|
GPU_render_end();
|
|
return;
|
|
}
|
|
|
|
/* Disable? - Keep for now since its used for window level notifiers. */
|
|
#if 1
|
|
/* Cache & catch WM level notifiers, such as frame change, scene/screen set. */
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
bool do_anim = false;
|
|
bool clear_info_stats = false;
|
|
|
|
CTX_wm_window_set(C, win);
|
|
|
|
LISTBASE_FOREACH_MUTABLE (const wmNotifier *, note, &wm->notifier_queue) {
|
|
if (note->category == NC_WM) {
|
|
if (ELEM(note->data, ND_FILEREAD, ND_FILESAVE)) {
|
|
wm->file_saved = 1;
|
|
wm_window_title(wm, win);
|
|
}
|
|
else if (note->data == ND_DATACHANGED) {
|
|
wm_window_title(wm, win);
|
|
}
|
|
else if (note->data == ND_UNDO) {
|
|
ED_preview_restart_queue_work(C);
|
|
}
|
|
}
|
|
if (note->window == win) {
|
|
if (note->category == NC_SCREEN) {
|
|
if (note->data == ND_WORKSPACE_SET) {
|
|
WorkSpace *ref_ws = static_cast<WorkSpace *>(note->reference);
|
|
|
|
UI_popup_handlers_remove_all(C, &win->modalhandlers);
|
|
|
|
WM_window_set_active_workspace(C, win, ref_ws);
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: Workspace set %p\n", __func__, note->reference);
|
|
}
|
|
}
|
|
else if (note->data == ND_WORKSPACE_DELETE) {
|
|
WorkSpace *workspace = static_cast<WorkSpace *>(note->reference);
|
|
|
|
ED_workspace_delete(
|
|
workspace, CTX_data_main(C), C, wm); /* XXX: hum, think this over! */
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: Workspace delete %p\n", __func__, workspace);
|
|
}
|
|
}
|
|
else if (note->data == ND_LAYOUTBROWSE) {
|
|
bScreen *ref_screen = BKE_workspace_layout_screen_get(
|
|
static_cast<WorkSpaceLayout *>(note->reference));
|
|
|
|
/* Free popup handlers only T35434. */
|
|
UI_popup_handlers_remove_all(C, &win->modalhandlers);
|
|
|
|
ED_screen_change(C, ref_screen); /* XXX: hum, think this over! */
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: screen set %p\n", __func__, note->reference);
|
|
}
|
|
}
|
|
else if (note->data == ND_LAYOUTDELETE) {
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
WorkSpaceLayout *layout = static_cast<WorkSpaceLayout *>(note->reference);
|
|
|
|
ED_workspace_layout_delete(workspace, layout, C); /* XXX: hum, think this over! */
|
|
if (G.debug & G_DEBUG_EVENTS) {
|
|
printf("%s: screen delete %p\n", __func__, note->reference);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (note->window == win ||
|
|
(note->window == nullptr && ELEM(note->reference, nullptr, scene))) {
|
|
if (note->category == NC_SCENE) {
|
|
if (note->data == ND_FRAME) {
|
|
do_anim = true;
|
|
}
|
|
}
|
|
}
|
|
if (ELEM(note->category, NC_SCENE, NC_OBJECT, NC_GEOM, NC_WM)) {
|
|
clear_info_stats = true;
|
|
}
|
|
}
|
|
|
|
if (clear_info_stats) {
|
|
/* Only do once since adding notifiers is slow when there are many. */
|
|
ViewLayer *view_layer = CTX_data_view_layer(C);
|
|
ED_info_stats_clear(wm, view_layer);
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO, nullptr);
|
|
}
|
|
|
|
if (do_anim) {
|
|
|
|
/* XXX: quick frame changes can cause a crash if frame-change and rendering
|
|
* collide (happens on slow scenes), BKE_scene_graph_update_for_newframe can be called
|
|
* twice which can depsgraph update the same object at once. */
|
|
if (G.is_rendering == false) {
|
|
/* Depsgraph gets called, might send more notifiers. */
|
|
Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
|
|
ED_update_for_newframe(CTX_data_main(C), depsgraph);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* The notifiers are sent without context, to keep it clean. */
|
|
const wmNotifier *note;
|
|
while ((note = static_cast<const wmNotifier *>(BLI_pophead(&wm->notifier_queue)))) {
|
|
if (wm_notifier_is_clear(note)) {
|
|
MEM_freeN((void *)note);
|
|
continue;
|
|
}
|
|
const bool removed = BLI_gset_remove(wm->notifier_queue_set, note, nullptr);
|
|
BLI_assert(removed);
|
|
UNUSED_VARS_NDEBUG(removed);
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
|
|
/* Filter out notifiers. */
|
|
if (note->category == NC_SCREEN && note->reference && note->reference != screen &&
|
|
note->reference != workspace && note->reference != WM_window_get_active_layout(win)) {
|
|
/* Pass. */
|
|
}
|
|
else if (note->category == NC_SCENE && note->reference && note->reference != scene) {
|
|
/* Pass. */
|
|
}
|
|
else {
|
|
/* XXX context in notifiers? */
|
|
CTX_wm_window_set(C, win);
|
|
|
|
# if 0
|
|
printf("notifier win %d screen %s cat %x\n",
|
|
win->winid,
|
|
win->screen->id.name + 2,
|
|
note->category);
|
|
# endif
|
|
ED_workspace_do_listen(C, note);
|
|
ED_screen_do_listen(C, note);
|
|
|
|
LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
|
|
wmRegionListenerParams region_params{};
|
|
region_params.window = win;
|
|
region_params.area = nullptr;
|
|
region_params.region = region;
|
|
region_params.scene = scene;
|
|
region_params.notifier = note;
|
|
|
|
ED_region_do_listen(®ion_params);
|
|
}
|
|
|
|
ED_screen_areas_iter (win, screen, area) {
|
|
if ((note->category == NC_SPACE) && note->reference) {
|
|
/* Filter out notifiers sent to other spaces. RNA sets the reference to the owning ID
|
|
* though, the screen, so let notifiers through that reference the entire screen. */
|
|
if (!ELEM(note->reference, area->spacedata.first, screen, scene)) {
|
|
continue;
|
|
}
|
|
}
|
|
wmSpaceTypeListenerParams area_params{};
|
|
area_params.window = win;
|
|
area_params.area = area;
|
|
area_params.notifier = note;
|
|
area_params.scene = scene;
|
|
ED_area_do_listen(&area_params);
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
wmRegionListenerParams region_params{};
|
|
region_params.window = win;
|
|
region_params.area = area;
|
|
region_params.region = region;
|
|
region_params.scene = scene;
|
|
region_params.notifier = note;
|
|
ED_region_do_listen(®ion_params);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MEM_freeN((void *)note);
|
|
}
|
|
#endif /* If 1 (postpone disabling for in favor of message-bus), eventually. */
|
|
|
|
/* Handle message bus. */
|
|
{
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
CTX_wm_window_set(C, win);
|
|
WM_msgbus_handle(wm->message_bus, C);
|
|
}
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
wm_event_do_refresh_wm_and_depsgraph(C);
|
|
|
|
/* Status bar. */
|
|
if (wm->winactive) {
|
|
wmWindow *win = wm->winactive;
|
|
CTX_wm_window_set(C, win);
|
|
WM_window_cursor_keymap_status_refresh(C, win);
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
/* Auto-run warning. */
|
|
wm_test_autorun_warning(C);
|
|
|
|
GPU_render_end();
|
|
}
|
|
|
|
static bool wm_event_always_pass(const wmEvent *event)
|
|
{
|
|
/* Some events we always pass on, to ensure proper communication. */
|
|
return ISTIMER(event->type) || (event->type == WINDEACTIVATE);
|
|
}
|
|
|
|
/**
|
|
* Debug only sanity check for the return value of event handlers. Checks that "always pass" events
|
|
* don't cause non-passing handler return values, and thus actually pass.
|
|
*
|
|
* \param C: Pass in the context to check if it's "window" was cleared.
|
|
* The event check can't be executed if the handler just loaded a file or closed the window.
|
|
* (typically identified by `CTX_wm_window(C)` returning null),
|
|
* because the event will have been freed then.
|
|
* When null, always check the event (assume the caller knows the event was not freed).
|
|
*/
|
|
BLI_INLINE void wm_event_handler_return_value_check(const bContext *C,
|
|
const wmEvent *event,
|
|
const int action)
|
|
{
|
|
#ifndef NDEBUG
|
|
if (C == nullptr || CTX_wm_window(C)) {
|
|
BLI_assert_msg(!wm_event_always_pass(event) || (action != WM_HANDLER_BREAK),
|
|
"Return value for events that should always pass should never be BREAK.");
|
|
}
|
|
#endif
|
|
UNUSED_VARS_NDEBUG(C, event, action);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name UI Handling
|
|
* \{ */
|
|
|
|
static int wm_handler_ui_call(bContext *C,
|
|
wmEventHandler_UI *handler,
|
|
const wmEvent *event,
|
|
int always_pass)
|
|
{
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
ARegion *menu = CTX_wm_menu(C);
|
|
static bool do_wheel_ui = true;
|
|
const bool is_wheel = ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE, MOUSEPAN);
|
|
|
|
/* UI code doesn't handle return values - it just always returns break.
|
|
* to make the #DBL_CLICK conversion work, we just don't send this to UI, except mouse clicks. */
|
|
if (((handler->head.flag & WM_HANDLER_ACCEPT_DBL_CLICK) == 0) && !ISMOUSE_BUTTON(event->type) &&
|
|
(event->val == KM_DBL_CLICK)) {
|
|
return WM_HANDLER_CONTINUE;
|
|
}
|
|
|
|
/* UI is quite aggressive with swallowing events, like scroll-wheel. */
|
|
/* I realize this is not extremely nice code... when UI gets key-maps it can be maybe smarter. */
|
|
if (do_wheel_ui == false) {
|
|
if (is_wheel) {
|
|
return WM_HANDLER_CONTINUE;
|
|
}
|
|
if (wm_event_always_pass(event) == 0) {
|
|
do_wheel_ui = true;
|
|
}
|
|
}
|
|
|
|
/* Don't block file-select events. Those are triggered by a separate file browser window.
|
|
* See T75292. */
|
|
if (event->type == EVT_FILESELECT) {
|
|
return WM_UI_HANDLER_CONTINUE;
|
|
}
|
|
|
|
/* We set context to where UI handler came from. */
|
|
if (handler->context.area) {
|
|
CTX_wm_area_set(C, handler->context.area);
|
|
}
|
|
if (handler->context.region) {
|
|
CTX_wm_region_set(C, handler->context.region);
|
|
}
|
|
if (handler->context.menu) {
|
|
CTX_wm_menu_set(C, handler->context.menu);
|
|
}
|
|
|
|
int retval = handler->handle_fn(C, event, handler->user_data);
|
|
|
|
/* Putting back screen context. */
|
|
if ((retval != WM_UI_HANDLER_BREAK) || always_pass) {
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
CTX_wm_menu_set(C, menu);
|
|
}
|
|
else {
|
|
/* This special cases is for areas and regions that get removed. */
|
|
CTX_wm_area_set(C, nullptr);
|
|
CTX_wm_region_set(C, nullptr);
|
|
CTX_wm_menu_set(C, nullptr);
|
|
}
|
|
|
|
if (retval == WM_UI_HANDLER_BREAK) {
|
|
return WM_HANDLER_BREAK;
|
|
}
|
|
|
|
/* Event not handled in UI, if wheel then we temporarily disable it. */
|
|
if (is_wheel) {
|
|
do_wheel_ui = false;
|
|
}
|
|
|
|
return WM_HANDLER_CONTINUE;
|
|
}
|
|
|
|
void wm_event_handler_ui_cancel_ex(bContext *C,
|
|
wmWindow *win,
|
|
ARegion *region,
|
|
bool reactivate_button)
|
|
{
|
|
if (!region) {
|
|
return;
|
|
}
|
|
|
|
LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, ®ion->handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
BLI_assert(handler->handle_fn != nullptr);
|
|
wmEvent event;
|
|
wm_event_init_from_window(win, &event);
|
|
event.type = EVT_BUT_CANCEL;
|
|
event.val = reactivate_button ? 0 : 1;
|
|
event.flag = (eWM_EventFlag)0;
|
|
handler->handle_fn(C, &event, handler->user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void wm_event_handler_ui_cancel(bContext *C)
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
wm_event_handler_ui_cancel_ex(C, win, region, true);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name WM Reports
|
|
*
|
|
* Access to #wmWindowManager.reports
|
|
* \{ */
|
|
|
|
void WM_report_banner_show(void)
|
|
{
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
|
|
ReportList *wm_reports = &wm->reports;
|
|
|
|
/* After adding reports to the global list, reset the report timer. */
|
|
WM_event_remove_timer(wm, nullptr, wm_reports->reporttimer);
|
|
|
|
/* Records time since last report was added. */
|
|
wm_reports->reporttimer = WM_event_add_timer(wm, wm->winactive, TIMERREPORT, 0.05);
|
|
|
|
ReportTimerInfo *rti = MEM_cnew<ReportTimerInfo>(__func__);
|
|
wm_reports->reporttimer->customdata = rti;
|
|
}
|
|
|
|
void WM_report_banners_cancel(Main *bmain)
|
|
{
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(bmain->wm.first);
|
|
BKE_reports_clear(&wm->reports);
|
|
WM_event_remove_timer(wm, nullptr, wm->reports.reporttimer);
|
|
}
|
|
|
|
#ifdef WITH_INPUT_NDOF
|
|
void WM_ndof_deadzone_set(float deadzone)
|
|
{
|
|
GHOST_setNDOFDeadZone(deadzone);
|
|
}
|
|
#endif
|
|
|
|
static void wm_add_reports(ReportList *reports)
|
|
{
|
|
/* If the caller owns them, handle this. */
|
|
if (reports->list.first && (reports->flag & RPT_OP_HOLD) == 0) {
|
|
wmWindowManager *wm = static_cast<wmWindowManager *>(G_MAIN->wm.first);
|
|
|
|
/* Add reports to the global list, otherwise they are not seen. */
|
|
BLI_movelisttolist(&wm->reports.list, &reports->list);
|
|
|
|
WM_report_banner_show();
|
|
}
|
|
}
|
|
|
|
void WM_report(eReportType type, const char *message)
|
|
{
|
|
ReportList reports;
|
|
BKE_reports_init(&reports, RPT_STORE | RPT_PRINT);
|
|
BKE_report_print_level_set(&reports, RPT_WARNING);
|
|
BKE_report(&reports, type, message);
|
|
|
|
wm_add_reports(&reports);
|
|
|
|
BKE_reports_clear(&reports);
|
|
}
|
|
|
|
void WM_reportf(eReportType type, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
DynStr *ds = BLI_dynstr_new();
|
|
va_start(args, format);
|
|
BLI_dynstr_vappendf(ds, format, args);
|
|
va_end(args);
|
|
|
|
char *str = BLI_dynstr_get_cstring(ds);
|
|
WM_report(type, str);
|
|
MEM_freeN(str);
|
|
|
|
BLI_dynstr_free(ds);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Operator Logic
|
|
* \{ */
|
|
|
|
/**
|
|
* Return the active undo step as an identifier for the purpose of comparison only.
|
|
*/
|
|
static intptr_t wm_operator_undo_active_id(const wmWindowManager *wm)
|
|
{
|
|
if (wm->undo_stack) {
|
|
return intptr_t(wm->undo_stack->step_active);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool WM_operator_poll(bContext *C, wmOperatorType *ot)
|
|
{
|
|
|
|
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
|
|
wmOperatorType *ot_macro = WM_operatortype_find(macro->idname, false);
|
|
|
|
if (!WM_operator_poll(C, ot_macro)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Python needs operator type, so we added exception for it. */
|
|
if (ot->pyop_poll) {
|
|
return ot->pyop_poll(C, ot);
|
|
}
|
|
if (ot->poll) {
|
|
return ot->poll(C);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WM_operator_poll_context(bContext *C, wmOperatorType *ot, short context)
|
|
{
|
|
/* Sets up the new context and calls #wm_operator_invoke() with poll_only. */
|
|
return wm_operator_call_internal(
|
|
C, ot, nullptr, nullptr, static_cast<wmOperatorCallContext>(context), true, nullptr);
|
|
}
|
|
|
|
bool WM_operator_check_ui_empty(wmOperatorType *ot)
|
|
{
|
|
if (ot->macro.first != nullptr) {
|
|
/* For macros, check all have exec() we can call. */
|
|
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
|
|
wmOperatorType *otm = WM_operatortype_find(macro->idname, false);
|
|
if (otm && !WM_operator_check_ui_empty(otm)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Assume a UI callback will draw something. */
|
|
if (ot->ui) {
|
|
return false;
|
|
}
|
|
|
|
PointerRNA ptr;
|
|
WM_operator_properties_create_ptr(&ptr, ot);
|
|
RNA_STRUCT_BEGIN (&ptr, prop) {
|
|
int flag = RNA_property_flag(prop);
|
|
if (flag & PROP_HIDDEN) {
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
RNA_STRUCT_END;
|
|
return true;
|
|
}
|
|
|
|
void WM_operator_region_active_win_set(bContext *C)
|
|
{
|
|
ScrArea *area = CTX_wm_area(C);
|
|
if (area) {
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region && region->regiontype == RGN_TYPE_WINDOW) {
|
|
area->region_active_win = BLI_findindex(&area->regionbase, region);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \param caller_owns_reports: True when called from Python.
|
|
*/
|
|
static void wm_operator_reports(bContext *C, wmOperator *op, int retval, bool caller_owns_reports)
|
|
{
|
|
if (G.background == 0 && caller_owns_reports == false) { /* Popup. */
|
|
if (op->reports->list.first) {
|
|
/* FIXME: temp setting window, see other call to #UI_popup_menu_reports for why. */
|
|
wmWindow *win_prev = CTX_wm_window(C);
|
|
ScrArea *area_prev = CTX_wm_area(C);
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
|
|
if (win_prev == nullptr) {
|
|
CTX_wm_window_set(C, static_cast<wmWindow *>(CTX_wm_manager(C)->windows.first));
|
|
}
|
|
|
|
UI_popup_menu_reports(C, op->reports);
|
|
|
|
CTX_wm_window_set(C, win_prev);
|
|
CTX_wm_area_set(C, area_prev);
|
|
CTX_wm_region_set(C, region_prev);
|
|
}
|
|
}
|
|
|
|
if (retval & OPERATOR_FINISHED) {
|
|
CLOG_STR_INFO_N(WM_LOG_OPERATORS, 1, WM_operator_pystring(C, op, false, true));
|
|
|
|
if (caller_owns_reports == false) {
|
|
BKE_reports_print(op->reports, RPT_DEBUG); /* Print out reports to console. */
|
|
}
|
|
|
|
if (op->type->flag & OPTYPE_REGISTER) {
|
|
if (G.background == 0) { /* Ends up printing these in the terminal, gets annoying. */
|
|
/* Report the python string representation of the operator. */
|
|
char *buf = WM_operator_pystring(C, op, false, true);
|
|
BKE_report(CTX_wm_reports(C), RPT_OPERATOR, buf);
|
|
MEM_freeN(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Refresh Info Editor with reports immediately, even if op returned #OPERATOR_CANCELLED. */
|
|
if ((retval & OPERATOR_CANCELLED) && !BLI_listbase_is_empty(&op->reports->list)) {
|
|
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_INFO_REPORT, nullptr);
|
|
}
|
|
/* If the caller owns them, handle this. */
|
|
wm_add_reports(op->reports);
|
|
}
|
|
|
|
/**
|
|
* This function is mainly to check that the rules for freeing
|
|
* an operator are kept in sync.
|
|
*/
|
|
static bool wm_operator_register_check(wmWindowManager *wm, wmOperatorType *ot)
|
|
{
|
|
/* Check undo flag here since undo operators are also added to the list,
|
|
* to support checking if the same operator is run twice. */
|
|
return wm && (wm->op_undo_depth == 0) && (ot->flag & (OPTYPE_REGISTER | OPTYPE_UNDO));
|
|
}
|
|
|
|
/**
|
|
* \param has_undo_step: True when an undo step was added,
|
|
* needed when the operator doesn't use #OPTYPE_UNDO, #OPTYPE_UNDO_GROUPED but adds an undo step.
|
|
*/
|
|
static void wm_operator_finished(
|
|
bContext *C, wmOperator *op, const bool repeat, const bool store, const bool has_undo_step)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
enum {
|
|
NOP,
|
|
SET,
|
|
CLEAR,
|
|
} hud_status = NOP;
|
|
|
|
op->customdata = nullptr;
|
|
|
|
if (store) {
|
|
WM_operator_last_properties_store(op);
|
|
}
|
|
|
|
/* We don't want to do undo pushes for operators that are being
|
|
* called from operators that already do an undo push. Usually
|
|
* this will happen for python operators that call C operators. */
|
|
if (wm->op_undo_depth == 0) {
|
|
if (op->type->flag & OPTYPE_UNDO) {
|
|
ED_undo_push_op(C, op);
|
|
if (repeat == 0) {
|
|
hud_status = CLEAR;
|
|
}
|
|
}
|
|
else if (op->type->flag & OPTYPE_UNDO_GROUPED) {
|
|
ED_undo_grouped_push_op(C, op);
|
|
if (repeat == 0) {
|
|
hud_status = CLEAR;
|
|
}
|
|
}
|
|
else if (has_undo_step) {
|
|
if (repeat == 0) {
|
|
hud_status = CLEAR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (repeat == 0) {
|
|
if (G.debug & G_DEBUG_WM) {
|
|
char *buf = WM_operator_pystring(C, op, false, true);
|
|
BKE_report(CTX_wm_reports(C), RPT_OPERATOR, buf);
|
|
MEM_freeN(buf);
|
|
}
|
|
|
|
if (wm_operator_register_check(wm, op->type)) {
|
|
/* Take ownership of reports (in case python provided own). */
|
|
op->reports->flag |= RPT_FREE;
|
|
|
|
wm_operator_register(C, op);
|
|
WM_operator_region_active_win_set(C);
|
|
|
|
if (WM_operator_last_redo(C) == op) {
|
|
/* Show the redo panel. */
|
|
hud_status = SET;
|
|
}
|
|
}
|
|
else {
|
|
WM_operator_free(op);
|
|
}
|
|
}
|
|
|
|
if (hud_status != NOP) {
|
|
if (hud_status == SET) {
|
|
ScrArea *area = CTX_wm_area(C);
|
|
if (area && ((area->flag & AREA_FLAG_OFFSCREEN) == 0)) {
|
|
ED_area_type_hud_ensure(C, area);
|
|
}
|
|
}
|
|
else if (hud_status == CLEAR) {
|
|
ED_area_type_hud_clear(wm, nullptr);
|
|
}
|
|
else {
|
|
BLI_assert_unreachable();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \param repeat: When true, it doesn't register again, nor does it free.
|
|
*/
|
|
static int wm_operator_exec(bContext *C, wmOperator *op, const bool repeat, const bool store)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
int retval = OPERATOR_CANCELLED;
|
|
|
|
CTX_wm_operator_poll_msg_clear(C);
|
|
|
|
if (op == nullptr || op->type == nullptr) {
|
|
return retval;
|
|
}
|
|
|
|
if (0 == WM_operator_poll(C, op->type)) {
|
|
return retval;
|
|
}
|
|
|
|
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
|
|
if (op->type->exec) {
|
|
if (op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
}
|
|
|
|
/* XXX(@mont29): Disabled the repeat check to address part 2 of T31840.
|
|
* Carefully checked all calls to wm_operator_exec and WM_operator_repeat, don't see any reason
|
|
* why this was needed, but worth to note it in case something turns bad. */
|
|
if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED) /* && repeat == 0 */) {
|
|
wm_operator_reports(C, op, retval, false);
|
|
}
|
|
|
|
if (retval & OPERATOR_FINISHED) {
|
|
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
|
|
|
|
wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0, has_undo_step);
|
|
}
|
|
else if (repeat == 0) {
|
|
/* WARNING: modal from exec is bad practice, but avoid crashing. */
|
|
if (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED)) {
|
|
WM_operator_free(op);
|
|
}
|
|
}
|
|
|
|
return retval | OPERATOR_HANDLED;
|
|
}
|
|
|
|
/**
|
|
* Simply calls exec with basic checks.
|
|
*/
|
|
static int wm_operator_exec_notest(bContext *C, wmOperator *op)
|
|
{
|
|
int retval = OPERATOR_CANCELLED;
|
|
|
|
if (op == nullptr || op->type == nullptr || op->type->exec == nullptr) {
|
|
return retval;
|
|
}
|
|
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
return retval;
|
|
}
|
|
|
|
int WM_operator_call_ex(bContext *C, wmOperator *op, const bool store)
|
|
{
|
|
return wm_operator_exec(C, op, false, store);
|
|
}
|
|
|
|
int WM_operator_call(bContext *C, wmOperator *op)
|
|
{
|
|
return WM_operator_call_ex(C, op, false);
|
|
}
|
|
|
|
int WM_operator_call_notest(bContext *C, wmOperator *op)
|
|
{
|
|
return wm_operator_exec_notest(C, op);
|
|
}
|
|
|
|
int WM_operator_repeat(bContext *C, wmOperator *op)
|
|
{
|
|
const int op_flag = OP_IS_REPEAT;
|
|
op->flag |= op_flag;
|
|
const int ret = wm_operator_exec(C, op, true, true);
|
|
op->flag &= ~op_flag;
|
|
return ret;
|
|
}
|
|
int WM_operator_repeat_last(bContext *C, wmOperator *op)
|
|
{
|
|
const int op_flag = OP_IS_REPEAT_LAST;
|
|
op->flag |= op_flag;
|
|
const int ret = wm_operator_exec(C, op, true, true);
|
|
op->flag &= ~op_flag;
|
|
return ret;
|
|
}
|
|
bool WM_operator_repeat_check(const bContext * /*C*/, wmOperator *op)
|
|
{
|
|
if (op->type->exec != nullptr) {
|
|
return true;
|
|
}
|
|
if (op->opm) {
|
|
/* For macros, check all have exec() we can call. */
|
|
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &op->opm->type->macro) {
|
|
wmOperatorType *otm = WM_operatortype_find(macro->idname, false);
|
|
if (otm && otm->exec == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool WM_operator_is_repeat(const bContext *C, const wmOperator *op)
|
|
{
|
|
/* May be in the operators list or not. */
|
|
wmOperator *op_prev;
|
|
if (op->prev == nullptr && op->next == nullptr) {
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
op_prev = static_cast<wmOperator *>(wm->operators.last);
|
|
}
|
|
else {
|
|
op_prev = op->prev;
|
|
}
|
|
return (op_prev && (op->type == op_prev->type));
|
|
}
|
|
|
|
static wmOperator *wm_operator_create(wmWindowManager *wm,
|
|
wmOperatorType *ot,
|
|
PointerRNA *properties,
|
|
ReportList *reports)
|
|
{
|
|
/* Operator-type names are static still. pass to allocation name for debugging. */
|
|
wmOperator *op = MEM_cnew<wmOperator>(ot->idname);
|
|
|
|
/* Adding new operator could be function, only happens here now. */
|
|
op->type = ot;
|
|
BLI_strncpy(op->idname, ot->idname, OP_MAX_TYPENAME);
|
|
|
|
/* Initialize properties, either copy or create. */
|
|
op->ptr = MEM_cnew<PointerRNA>("wmOperatorPtrRNA");
|
|
if (properties && properties->data) {
|
|
op->properties = IDP_CopyProperty(static_cast<const IDProperty *>(properties->data));
|
|
}
|
|
else {
|
|
IDPropertyTemplate val = {0};
|
|
op->properties = IDP_New(IDP_GROUP, &val, "wmOperatorProperties");
|
|
}
|
|
RNA_pointer_create(&wm->id, ot->srna, op->properties, op->ptr);
|
|
|
|
/* Initialize error reports. */
|
|
if (reports) {
|
|
op->reports = reports; /* Must be initialized already. */
|
|
}
|
|
else {
|
|
op->reports = MEM_cnew<ReportList>("wmOperatorReportList");
|
|
BKE_reports_init(op->reports, RPT_STORE | RPT_FREE);
|
|
}
|
|
|
|
/* Recursive filling of operator macro list. */
|
|
if (ot->macro.first) {
|
|
static wmOperator *motherop = nullptr;
|
|
int root = 0;
|
|
|
|
/* Ensure all ops are in execution order in 1 list. */
|
|
if (motherop == nullptr) {
|
|
motherop = op;
|
|
root = 1;
|
|
}
|
|
|
|
/* If properties exist, it will contain everything needed. */
|
|
if (properties) {
|
|
wmOperatorTypeMacro *otmacro = static_cast<wmOperatorTypeMacro *>(ot->macro.first);
|
|
|
|
RNA_STRUCT_BEGIN (properties, prop) {
|
|
|
|
if (otmacro == nullptr) {
|
|
break;
|
|
}
|
|
|
|
/* Skip invalid properties. */
|
|
if (STREQ(RNA_property_identifier(prop), otmacro->idname)) {
|
|
wmOperatorType *otm = WM_operatortype_find(otmacro->idname, false);
|
|
PointerRNA someptr = RNA_property_pointer_get(properties, prop);
|
|
wmOperator *opm = wm_operator_create(wm, otm, &someptr, nullptr);
|
|
|
|
IDP_ReplaceGroupInGroup(opm->properties, otmacro->properties);
|
|
|
|
BLI_addtail(&motherop->macro, opm);
|
|
opm->opm = motherop; /* Pointer to mom, for modal(). */
|
|
|
|
otmacro = otmacro->next;
|
|
}
|
|
}
|
|
RNA_STRUCT_END;
|
|
}
|
|
else {
|
|
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
|
|
wmOperatorType *otm = WM_operatortype_find(macro->idname, false);
|
|
wmOperator *opm = wm_operator_create(wm, otm, macro->ptr, nullptr);
|
|
|
|
BLI_addtail(&motherop->macro, opm);
|
|
opm->opm = motherop; /* Pointer to mom, for modal(). */
|
|
}
|
|
}
|
|
|
|
if (root) {
|
|
motherop = nullptr;
|
|
}
|
|
}
|
|
|
|
WM_operator_properties_sanitize(op->ptr, false);
|
|
|
|
return op;
|
|
}
|
|
|
|
/**
|
|
* This isn't very nice but needed to redraw gizmos which are hidden while tweaking,
|
|
* See #WM_GIZMOGROUPTYPE_DELAY_REFRESH_FOR_TWEAK for details.
|
|
*/
|
|
static void wm_region_tag_draw_on_gizmo_delay_refresh_for_tweak(wmWindow *win)
|
|
{
|
|
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
/* Unlikely but not impossible as this runs after events have been handled. */
|
|
if (UNLIKELY(screen == nullptr)) {
|
|
return;
|
|
}
|
|
ED_screen_areas_iter (win, screen, area) {
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
if (region->gizmo_map != nullptr) {
|
|
if (WM_gizmomap_tag_delay_refresh_for_tweak_check(region->gizmo_map)) {
|
|
ED_region_tag_redraw(region);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void wm_region_mouse_co(bContext *C, wmEvent *event)
|
|
{
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region) {
|
|
/* Compatibility convention. */
|
|
event->mval[0] = event->xy[0] - region->winrct.xmin;
|
|
event->mval[1] = event->xy[1] - region->winrct.ymin;
|
|
}
|
|
else {
|
|
/* These values are invalid (avoid odd behavior by relying on old #wmEvent.mval values). */
|
|
event->mval[0] = -1;
|
|
event->mval[1] = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Also used for exec when 'event' is nullptr.
|
|
*/
|
|
static int wm_operator_invoke(bContext *C,
|
|
wmOperatorType *ot,
|
|
const wmEvent *event,
|
|
PointerRNA *properties,
|
|
ReportList *reports,
|
|
const bool poll_only,
|
|
bool use_last_properties)
|
|
{
|
|
int retval = OPERATOR_PASS_THROUGH;
|
|
|
|
/* This is done because complicated setup is done to call this function
|
|
* that is better not duplicated. */
|
|
if (poll_only) {
|
|
return WM_operator_poll(C, ot);
|
|
}
|
|
|
|
if (WM_operator_poll(C, ot)) {
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
|
|
|
|
/* If `reports == nullptr`, they'll be initialized. */
|
|
wmOperator *op = wm_operator_create(wm, ot, properties, reports);
|
|
|
|
const bool is_nested_call = (wm->op_undo_depth != 0);
|
|
|
|
if (event != nullptr) {
|
|
op->flag |= OP_IS_INVOKE;
|
|
}
|
|
|
|
/* Initialize setting from previous run. */
|
|
if (!is_nested_call && use_last_properties) { /* Not called by a Python script. */
|
|
WM_operator_last_properties_init(op);
|
|
}
|
|
|
|
if ((event == nullptr) || (event->type != MOUSEMOVE)) {
|
|
CLOG_INFO(WM_LOG_HANDLERS,
|
|
2,
|
|
"handle evt %d win %p op %s",
|
|
event ? event->type : 0,
|
|
CTX_wm_screen(C)->active_region,
|
|
ot->idname);
|
|
}
|
|
|
|
if (op->type->invoke && event) {
|
|
/* Make a copy of the event as it's `const` and the #wmEvent.mval to be written into. */
|
|
wmEvent event_temp = *event;
|
|
wm_region_mouse_co(C, &event_temp);
|
|
|
|
if (op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
retval = op->type->invoke(C, op, &event_temp);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
}
|
|
else if (op->type->exec) {
|
|
if (op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
retval = op->type->exec(C, op);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
}
|
|
else {
|
|
/* Debug, important to leave a while, should never happen. */
|
|
CLOG_ERROR(WM_LOG_OPERATORS, "invalid operator call '%s'", op->idname);
|
|
}
|
|
|
|
/* NOTE: if the report is given as an argument then assume the caller will deal with displaying
|
|
* them currently Python only uses this. */
|
|
if (!(retval & OPERATOR_HANDLED) && (retval & (OPERATOR_FINISHED | OPERATOR_CANCELLED))) {
|
|
/* Only show the report if the report list was not given in the function. */
|
|
wm_operator_reports(C, op, retval, (reports != nullptr));
|
|
}
|
|
|
|
if (retval & OPERATOR_HANDLED) {
|
|
/* Do nothing, #wm_operator_exec() has been called somewhere. */
|
|
}
|
|
else if (retval & OPERATOR_FINISHED) {
|
|
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
|
|
const bool store = !is_nested_call && use_last_properties;
|
|
wm_operator_finished(C, op, false, store, has_undo_step);
|
|
}
|
|
else if (retval & OPERATOR_RUNNING_MODAL) {
|
|
/* Take ownership of reports (in case python provided own). */
|
|
op->reports->flag |= RPT_FREE;
|
|
|
|
/* Grab cursor during blocking modal operators (X11)
|
|
* Also check for macro. */
|
|
if (ot->flag & OPTYPE_BLOCKING || (op->opm && op->opm->type->flag & OPTYPE_BLOCKING)) {
|
|
eWM_CursorWrapAxis wrap = WM_CURSOR_WRAP_NONE;
|
|
const rcti *wrap_region = nullptr;
|
|
|
|
if (event && (U.uiflag & USER_CONTINUOUS_MOUSE)) {
|
|
const wmOperator *op_test = op->opm ? op->opm : op;
|
|
const wmOperatorType *ot_test = op_test->type;
|
|
if ((ot_test->flag & OPTYPE_GRAB_CURSOR_XY) ||
|
|
(op_test->flag & OP_IS_MODAL_GRAB_CURSOR)) {
|
|
wrap = WM_CURSOR_WRAP_XY;
|
|
}
|
|
else if (ot_test->flag & OPTYPE_GRAB_CURSOR_X) {
|
|
wrap = WM_CURSOR_WRAP_X;
|
|
}
|
|
else if (ot_test->flag & OPTYPE_GRAB_CURSOR_Y) {
|
|
wrap = WM_CURSOR_WRAP_Y;
|
|
}
|
|
}
|
|
|
|
if (wrap) {
|
|
ARegion *region = CTX_wm_region(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
/* Wrap only in X for header. */
|
|
if (region && RGN_TYPE_IS_HEADER_ANY(region->regiontype)) {
|
|
wrap = WM_CURSOR_WRAP_X;
|
|
}
|
|
|
|
if (region && region->regiontype == RGN_TYPE_WINDOW &&
|
|
BLI_rcti_isect_pt_v(®ion->winrct, event->xy)) {
|
|
wrap_region = ®ion->winrct;
|
|
}
|
|
else if (area && BLI_rcti_isect_pt_v(&area->totrct, event->xy)) {
|
|
wrap_region = &area->totrct;
|
|
}
|
|
}
|
|
|
|
WM_cursor_grab_enable(CTX_wm_window(C), wrap, wrap_region, false);
|
|
}
|
|
|
|
/* Cancel UI handlers, typically tool-tips that can hang around
|
|
* while dragging the view or worse, that stay there permanently
|
|
* after the modal operator has swallowed all events and passed
|
|
* none to the UI handler. */
|
|
wm_event_handler_ui_cancel(C);
|
|
}
|
|
else {
|
|
WM_operator_free(op);
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* #WM_operator_name_call is the main accessor function
|
|
* This is for python to access since its done the operator lookup
|
|
* invokes operator in context.
|
|
*/
|
|
static int wm_operator_call_internal(bContext *C,
|
|
wmOperatorType *ot,
|
|
PointerRNA *properties,
|
|
ReportList *reports,
|
|
const wmOperatorCallContext context,
|
|
const bool poll_only,
|
|
const wmEvent *event)
|
|
{
|
|
int retval;
|
|
|
|
CTX_wm_operator_poll_msg_clear(C);
|
|
|
|
/* Dummy test. */
|
|
if (ot) {
|
|
wmWindow *window = CTX_wm_window(C);
|
|
|
|
if (event == nullptr) {
|
|
switch (context) {
|
|
case WM_OP_INVOKE_DEFAULT:
|
|
case WM_OP_INVOKE_REGION_WIN:
|
|
case WM_OP_INVOKE_REGION_PREVIEW:
|
|
case WM_OP_INVOKE_REGION_CHANNELS:
|
|
case WM_OP_INVOKE_AREA:
|
|
case WM_OP_INVOKE_SCREEN:
|
|
/* Window is needed for invoke and cancel operators. */
|
|
if (window == nullptr) {
|
|
if (poll_only) {
|
|
CTX_wm_operator_poll_msg_set(C, "Missing 'window' in context");
|
|
}
|
|
return 0;
|
|
}
|
|
else {
|
|
event = window->eventstate;
|
|
}
|
|
break;
|
|
default:
|
|
event = nullptr;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
switch (context) {
|
|
case WM_OP_EXEC_DEFAULT:
|
|
case WM_OP_EXEC_REGION_WIN:
|
|
case WM_OP_EXEC_REGION_PREVIEW:
|
|
case WM_OP_EXEC_REGION_CHANNELS:
|
|
case WM_OP_EXEC_AREA:
|
|
case WM_OP_EXEC_SCREEN:
|
|
event = nullptr;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (context) {
|
|
case WM_OP_EXEC_REGION_WIN:
|
|
case WM_OP_INVOKE_REGION_WIN:
|
|
case WM_OP_EXEC_REGION_CHANNELS:
|
|
case WM_OP_INVOKE_REGION_CHANNELS:
|
|
case WM_OP_EXEC_REGION_PREVIEW:
|
|
case WM_OP_INVOKE_REGION_PREVIEW: {
|
|
/* Forces operator to go to the region window/channels/preview, for header menus,
|
|
* but we stay in the same region if we are already in one. */
|
|
ARegion *region = CTX_wm_region(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
int type = RGN_TYPE_WINDOW;
|
|
|
|
switch (context) {
|
|
case WM_OP_EXEC_REGION_CHANNELS:
|
|
case WM_OP_INVOKE_REGION_CHANNELS:
|
|
type = RGN_TYPE_CHANNELS;
|
|
break;
|
|
|
|
case WM_OP_EXEC_REGION_PREVIEW:
|
|
case WM_OP_INVOKE_REGION_PREVIEW:
|
|
type = RGN_TYPE_PREVIEW;
|
|
break;
|
|
|
|
case WM_OP_EXEC_REGION_WIN:
|
|
case WM_OP_INVOKE_REGION_WIN:
|
|
default:
|
|
type = RGN_TYPE_WINDOW;
|
|
break;
|
|
}
|
|
|
|
if (!(region && region->regiontype == type) && area) {
|
|
ARegion *region_other = (type == RGN_TYPE_WINDOW) ?
|
|
BKE_area_find_region_active_win(area) :
|
|
BKE_area_find_region_type(area, type);
|
|
if (region_other) {
|
|
CTX_wm_region_set(C, region_other);
|
|
}
|
|
}
|
|
|
|
retval = wm_operator_invoke(C, ot, event, properties, reports, poll_only, true);
|
|
|
|
/* Set region back. */
|
|
CTX_wm_region_set(C, region);
|
|
|
|
return retval;
|
|
}
|
|
case WM_OP_EXEC_AREA:
|
|
case WM_OP_INVOKE_AREA: {
|
|
/* Remove region from context. */
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
CTX_wm_region_set(C, nullptr);
|
|
retval = wm_operator_invoke(C, ot, event, properties, reports, poll_only, true);
|
|
CTX_wm_region_set(C, region);
|
|
|
|
return retval;
|
|
}
|
|
case WM_OP_EXEC_SCREEN:
|
|
case WM_OP_INVOKE_SCREEN: {
|
|
/* Remove region + area from context. */
|
|
ARegion *region = CTX_wm_region(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
CTX_wm_region_set(C, nullptr);
|
|
CTX_wm_area_set(C, nullptr);
|
|
retval = wm_operator_invoke(C, ot, event, properties, reports, poll_only, true);
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
|
|
return retval;
|
|
}
|
|
case WM_OP_EXEC_DEFAULT:
|
|
case WM_OP_INVOKE_DEFAULT:
|
|
return wm_operator_invoke(C, ot, event, properties, reports, poll_only, true);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int WM_operator_name_call_ptr(bContext *C,
|
|
wmOperatorType *ot,
|
|
wmOperatorCallContext context,
|
|
PointerRNA *properties,
|
|
const wmEvent *event)
|
|
{
|
|
BLI_assert(ot == WM_operatortype_find(ot->idname, true));
|
|
return wm_operator_call_internal(C, ot, properties, nullptr, context, false, event);
|
|
}
|
|
int WM_operator_name_call(bContext *C,
|
|
const char *opstring,
|
|
wmOperatorCallContext context,
|
|
PointerRNA *properties,
|
|
const wmEvent *event)
|
|
{
|
|
wmOperatorType *ot = WM_operatortype_find(opstring, false);
|
|
if (ot) {
|
|
return WM_operator_name_call_ptr(C, ot, context, properties, event);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool WM_operator_name_poll(bContext *C, const char *opstring)
|
|
{
|
|
wmOperatorType *ot = WM_operatortype_find(opstring, false);
|
|
if (!ot) {
|
|
return false;
|
|
}
|
|
|
|
return WM_operator_poll(C, ot);
|
|
}
|
|
|
|
int WM_operator_name_call_with_properties(bContext *C,
|
|
const char *opstring,
|
|
wmOperatorCallContext context,
|
|
IDProperty *properties,
|
|
const wmEvent *event)
|
|
{
|
|
PointerRNA props_ptr;
|
|
wmOperatorType *ot = WM_operatortype_find(opstring, false);
|
|
RNA_pointer_create(
|
|
&static_cast<wmWindowManager *>(G_MAIN->wm.first)->id, ot->srna, properties, &props_ptr);
|
|
return WM_operator_name_call_ptr(C, ot, context, &props_ptr, event);
|
|
}
|
|
|
|
void WM_menu_name_call(bContext *C, const char *menu_name, short context)
|
|
{
|
|
wmOperatorType *ot = WM_operatortype_find("WM_OT_call_menu", false);
|
|
PointerRNA ptr;
|
|
WM_operator_properties_create_ptr(&ptr, ot);
|
|
RNA_string_set(&ptr, "name", menu_name);
|
|
WM_operator_name_call_ptr(C, ot, static_cast<wmOperatorCallContext>(context), &ptr, nullptr);
|
|
WM_operator_properties_free(&ptr);
|
|
}
|
|
|
|
int WM_operator_call_py(bContext *C,
|
|
wmOperatorType *ot,
|
|
wmOperatorCallContext context,
|
|
PointerRNA *properties,
|
|
ReportList *reports,
|
|
const bool is_undo)
|
|
{
|
|
int retval = OPERATOR_CANCELLED;
|
|
/* Not especially nice using undo depth here. It's used so Python never
|
|
* triggers undo or stores an operator's last used state. */
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
if (!is_undo && wm) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
retval = wm_operator_call_internal(C, ot, properties, reports, context, false, nullptr);
|
|
|
|
if (!is_undo && wm && (wm == CTX_wm_manager(C))) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Operator Wait For Input
|
|
*
|
|
* Delay executing operators that depend on cursor location.
|
|
*
|
|
* See: #OPTYPE_DEPENDS_ON_CURSOR doc-string for more information.
|
|
* \{ */
|
|
|
|
struct uiOperatorWaitForInput {
|
|
ScrArea *area;
|
|
wmOperatorCallParams optype_params;
|
|
bContextStore *context;
|
|
};
|
|
|
|
static void ui_handler_wait_for_input_remove(bContext *C, void *userdata)
|
|
{
|
|
uiOperatorWaitForInput *opwait = static_cast<uiOperatorWaitForInput *>(userdata);
|
|
if (opwait->optype_params.opptr) {
|
|
if (opwait->optype_params.opptr->data) {
|
|
IDP_FreeProperty(static_cast<IDProperty *>(opwait->optype_params.opptr->data));
|
|
}
|
|
MEM_freeN(opwait->optype_params.opptr);
|
|
}
|
|
if (opwait->context) {
|
|
CTX_store_free(opwait->context);
|
|
}
|
|
|
|
if (opwait->area != nullptr) {
|
|
ED_area_status_text(opwait->area, nullptr);
|
|
}
|
|
else {
|
|
ED_workspace_status_text(C, nullptr);
|
|
}
|
|
|
|
MEM_freeN(opwait);
|
|
}
|
|
|
|
static int ui_handler_wait_for_input(bContext *C, const wmEvent *event, void *userdata)
|
|
{
|
|
uiOperatorWaitForInput *opwait = static_cast<uiOperatorWaitForInput *>(userdata);
|
|
enum { CONTINUE = 0, EXECUTE, CANCEL } state = CONTINUE;
|
|
state = CONTINUE;
|
|
|
|
switch (event->type) {
|
|
case LEFTMOUSE: {
|
|
if (event->val == KM_PRESS) {
|
|
state = EXECUTE;
|
|
}
|
|
break;
|
|
}
|
|
/* Useful if the operator isn't convenient to access while the mouse button is held.
|
|
* If it takes numeric input for example. */
|
|
case EVT_SPACEKEY:
|
|
case EVT_RETKEY: {
|
|
if (event->val == KM_PRESS) {
|
|
state = EXECUTE;
|
|
}
|
|
break;
|
|
}
|
|
case RIGHTMOUSE: {
|
|
if (event->val == KM_PRESS) {
|
|
state = CANCEL;
|
|
}
|
|
break;
|
|
}
|
|
case EVT_ESCKEY: {
|
|
if (event->val == KM_PRESS) {
|
|
state = CANCEL;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (state != CONTINUE) {
|
|
wmWindow *win = CTX_wm_window(C);
|
|
WM_cursor_modal_restore(win);
|
|
|
|
if (state == EXECUTE) {
|
|
CTX_store_set(C, opwait->context);
|
|
WM_operator_name_call_ptr(C,
|
|
opwait->optype_params.optype,
|
|
opwait->optype_params.opcontext,
|
|
opwait->optype_params.opptr,
|
|
event);
|
|
CTX_store_set(C, nullptr);
|
|
}
|
|
|
|
WM_event_remove_ui_handler(&win->modalhandlers,
|
|
ui_handler_wait_for_input,
|
|
ui_handler_wait_for_input_remove,
|
|
opwait,
|
|
false);
|
|
|
|
ui_handler_wait_for_input_remove(C, opwait);
|
|
|
|
return WM_UI_HANDLER_BREAK;
|
|
}
|
|
|
|
return WM_UI_HANDLER_CONTINUE;
|
|
}
|
|
|
|
void WM_operator_name_call_ptr_with_depends_on_cursor(bContext *C,
|
|
wmOperatorType *ot,
|
|
wmOperatorCallContext opcontext,
|
|
PointerRNA *properties,
|
|
const wmEvent *event,
|
|
const char *drawstr)
|
|
{
|
|
int flag = ot->flag;
|
|
|
|
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
|
|
wmOperatorType *otm = WM_operatortype_find(macro->idname, false);
|
|
if (otm != nullptr) {
|
|
flag |= otm->flag;
|
|
}
|
|
}
|
|
|
|
if ((flag & OPTYPE_DEPENDS_ON_CURSOR) == 0) {
|
|
WM_operator_name_call_ptr(C, ot, opcontext, properties, event);
|
|
return;
|
|
}
|
|
|
|
wmWindow *win = CTX_wm_window(C);
|
|
/* The operator context is applied when the operator is called,
|
|
* the check for the area needs to be explicitly limited here.
|
|
* Useful so it's possible to screen-shot an area without drawing into it's header. */
|
|
ScrArea *area = WM_OP_CONTEXT_HAS_AREA(opcontext) ? CTX_wm_area(C) : nullptr;
|
|
|
|
{
|
|
char header_text[UI_MAX_DRAW_STR];
|
|
SNPRINTF(header_text,
|
|
"%s %s",
|
|
IFACE_("Input pending "),
|
|
(drawstr && drawstr[0]) ? drawstr : CTX_IFACE_(ot->translation_context, ot->name));
|
|
if (area != nullptr) {
|
|
ED_area_status_text(area, header_text);
|
|
}
|
|
else {
|
|
ED_workspace_status_text(C, header_text);
|
|
}
|
|
}
|
|
|
|
WM_cursor_modal_set(win, ot->cursor_pending);
|
|
|
|
uiOperatorWaitForInput *opwait = MEM_cnew<uiOperatorWaitForInput>(__func__);
|
|
opwait->optype_params.optype = ot;
|
|
opwait->optype_params.opcontext = opcontext;
|
|
opwait->optype_params.opptr = properties;
|
|
|
|
opwait->area = area;
|
|
|
|
if (properties) {
|
|
opwait->optype_params.opptr = MEM_cnew<PointerRNA>(__func__);
|
|
*opwait->optype_params.opptr = *properties;
|
|
if (properties->data != nullptr) {
|
|
opwait->optype_params.opptr->data = IDP_CopyProperty(
|
|
static_cast<IDProperty *>(properties->data));
|
|
}
|
|
}
|
|
|
|
bContextStore *store = CTX_store_get(C);
|
|
if (store) {
|
|
opwait->context = CTX_store_copy(store);
|
|
}
|
|
|
|
WM_event_add_ui_handler(C,
|
|
&win->modalhandlers,
|
|
ui_handler_wait_for_input,
|
|
ui_handler_wait_for_input_remove,
|
|
opwait,
|
|
WM_HANDLER_BLOCKING);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Handler Types
|
|
*
|
|
* General API for different handler types.
|
|
* \{ */
|
|
|
|
void wm_event_free_handler(wmEventHandler *handler)
|
|
{
|
|
/* Future extra custom-data free? */
|
|
MEM_freeN(handler);
|
|
}
|
|
|
|
/**
|
|
* Check if the handler's area and/or region are actually part of the screen, and return them if
|
|
* so.
|
|
*/
|
|
static void wm_handler_op_context_get_if_valid(bContext *C,
|
|
wmEventHandler_Op *handler,
|
|
const wmEvent *event,
|
|
ScrArea **r_area,
|
|
ARegion **r_region)
|
|
{
|
|
wmWindow *win = handler->context.win ? handler->context.win : CTX_wm_window(C);
|
|
/* It's probably fine to always use #WM_window_get_active_screen() to get the screen. But this
|
|
* code has been getting it through context since forever, so play safe and stick to that when
|
|
* possible. */
|
|
bScreen *screen = handler->context.win ? WM_window_get_active_screen(win) : CTX_wm_screen(C);
|
|
|
|
*r_area = nullptr;
|
|
*r_region = nullptr;
|
|
|
|
if (screen == nullptr || handler->op == nullptr) {
|
|
return;
|
|
}
|
|
|
|
if (handler->context.area == nullptr) {
|
|
/* Pass */
|
|
}
|
|
else {
|
|
ScrArea *area = nullptr;
|
|
|
|
ED_screen_areas_iter (win, screen, area_iter) {
|
|
if (area_iter == handler->context.area) {
|
|
area = area_iter;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (area == nullptr) {
|
|
/* When changing screen layouts with running modal handlers (like render display), this
|
|
* is not an error to print. */
|
|
if (handler->op == nullptr) {
|
|
CLOG_ERROR(WM_LOG_HANDLERS,
|
|
"internal error: handler (%s) has invalid area",
|
|
handler->op->type->idname);
|
|
}
|
|
}
|
|
else {
|
|
ARegion *region;
|
|
wmOperator *op = handler->op ? (handler->op->opm ? handler->op->opm : handler->op) : nullptr;
|
|
*r_area = area;
|
|
|
|
if (op && (op->flag & OP_IS_MODAL_CURSOR_REGION)) {
|
|
region = BKE_area_find_region_xy(area, handler->context.region_type, event->xy);
|
|
if (region) {
|
|
handler->context.region = region;
|
|
}
|
|
}
|
|
else {
|
|
region = nullptr;
|
|
}
|
|
|
|
if ((region == nullptr) && handler->context.region) {
|
|
if (BLI_findindex(&area->regionbase, handler->context.region) != -1) {
|
|
region = handler->context.region;
|
|
}
|
|
}
|
|
|
|
/* No warning print here, after full-area and back regions are remade. */
|
|
if (region) {
|
|
*r_region = region;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void wm_handler_op_context(bContext *C, wmEventHandler_Op *handler, const wmEvent *event)
|
|
{
|
|
ScrArea *area = nullptr;
|
|
ARegion *region = nullptr;
|
|
wm_handler_op_context_get_if_valid(C, handler, event, &area, ®ion);
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
}
|
|
|
|
void WM_event_remove_handlers(bContext *C, ListBase *handlers)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
/* C is zero on freeing database, modal handlers then already were freed. */
|
|
wmEventHandler *handler_base;
|
|
while ((handler_base = static_cast<wmEventHandler *>(BLI_pophead(handlers)))) {
|
|
BLI_assert(handler_base->type != 0);
|
|
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
|
|
if (handler->op) {
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
if (handler->is_fileselect) {
|
|
/* Exit File Browsers referring to this handler/operator. */
|
|
LISTBASE_FOREACH (wmWindow *, temp_win, &wm->windows) {
|
|
ScrArea *file_area = ED_fileselect_handler_area_find(temp_win, handler->op);
|
|
if (!file_area) {
|
|
continue;
|
|
}
|
|
ED_area_exit(C, file_area);
|
|
}
|
|
}
|
|
|
|
if (handler->op->type->cancel) {
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
wm_handler_op_context(C, handler, win->eventstate);
|
|
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
handler->op->type->cancel(C, handler->op);
|
|
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
}
|
|
|
|
WM_cursor_grab_disable(win, nullptr);
|
|
|
|
if (handler->is_fileselect) {
|
|
wm_operator_free_for_fileselect(handler->op);
|
|
}
|
|
else {
|
|
WM_operator_free(handler->op);
|
|
}
|
|
}
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
|
|
if (handler->remove_fn) {
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
ARegion *menu = CTX_wm_menu(C);
|
|
|
|
if (handler->context.area) {
|
|
CTX_wm_area_set(C, handler->context.area);
|
|
}
|
|
if (handler->context.region) {
|
|
CTX_wm_region_set(C, handler->context.region);
|
|
}
|
|
if (handler->context.menu) {
|
|
CTX_wm_menu_set(C, handler->context.menu);
|
|
}
|
|
|
|
handler->remove_fn(C, handler->user_data);
|
|
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
CTX_wm_menu_set(C, menu);
|
|
}
|
|
}
|
|
|
|
wm_event_free_handler(handler_base);
|
|
}
|
|
}
|
|
|
|
static bool wm_eventmatch(const wmEvent *winevent, const wmKeyMapItem *kmi)
|
|
{
|
|
if (kmi->flag & KMI_INACTIVE) {
|
|
return false;
|
|
}
|
|
|
|
if (winevent->flag & WM_EVENT_IS_REPEAT) {
|
|
if (kmi->flag & KMI_REPEAT_IGNORE) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const int kmitype = WM_userdef_event_map(kmi->type);
|
|
|
|
/* The matching rules. */
|
|
if (kmitype == KM_TEXTINPUT) {
|
|
if (winevent->val == KM_PRESS) { /* Prevent double clicks. */
|
|
if (ISKEYBOARD(winevent->type) && winevent->utf8_buf[0]) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (kmitype != KM_ANY) {
|
|
if (ELEM(kmitype, TABLET_STYLUS, TABLET_ERASER)) {
|
|
const wmTabletData *wmtab = &winevent->tablet;
|
|
|
|
if (winevent->type != LEFTMOUSE) {
|
|
/* Tablet events can occur on hover + key-press. */
|
|
return false;
|
|
}
|
|
if ((kmitype == TABLET_STYLUS) && (wmtab->active != EVT_TABLET_STYLUS)) {
|
|
return false;
|
|
}
|
|
if ((kmitype == TABLET_ERASER) && (wmtab->active != EVT_TABLET_ERASER)) {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if (winevent->type != kmitype) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (kmi->val != KM_ANY) {
|
|
if (winevent->val != kmi->val) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (kmi->val == KM_CLICK_DRAG) {
|
|
if (kmi->direction != KM_ANY) {
|
|
if (kmi->direction != winevent->direction) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Account for rare case of when these keys are used as the 'type' not as modifiers. */
|
|
if (kmi->shift != KM_ANY) {
|
|
const bool shift = (winevent->modifier & KM_SHIFT) != 0;
|
|
if ((shift != bool(kmi->shift)) &&
|
|
!ELEM(winevent->type, EVT_LEFTSHIFTKEY, EVT_RIGHTSHIFTKEY)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (kmi->ctrl != KM_ANY) {
|
|
const bool ctrl = (winevent->modifier & KM_CTRL) != 0;
|
|
if (ctrl != bool(kmi->ctrl) && !ELEM(winevent->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (kmi->alt != KM_ANY) {
|
|
const bool alt = (winevent->modifier & KM_ALT) != 0;
|
|
if (alt != bool(kmi->alt) && !ELEM(winevent->type, EVT_LEFTALTKEY, EVT_RIGHTALTKEY)) {
|
|
return false;
|
|
}
|
|
}
|
|
if (kmi->oskey != KM_ANY) {
|
|
const bool oskey = (winevent->modifier & KM_OSKEY) != 0;
|
|
if ((oskey != bool(kmi->oskey)) && (winevent->type != EVT_OSKEY)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Only key-map entry with key-modifier is checked,
|
|
* means all keys without modifier get handled too. */
|
|
/* That is currently needed to make overlapping events work (when you press A - G fast or so). */
|
|
if (kmi->keymodifier) {
|
|
if (winevent->keymodifier != kmi->keymodifier) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static wmKeyMapItem *wm_eventmatch_modal_keymap_items(const wmKeyMap *keymap,
|
|
wmOperator *op,
|
|
const wmEvent *event)
|
|
{
|
|
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
|
/* Should already be handled by #wm_user_modal_keymap_set_items. */
|
|
BLI_assert(kmi->propvalue_str[0] == '\0');
|
|
if (wm_eventmatch(event, kmi)) {
|
|
if ((keymap->poll_modal_item == nullptr) || keymap->poll_modal_item(op, kmi->propvalue)) {
|
|
return kmi;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
struct wmEvent_ModalMapStore {
|
|
short prev_type;
|
|
short prev_val;
|
|
|
|
bool dbl_click_disabled;
|
|
};
|
|
|
|
/**
|
|
* This function prepares events for use with #wmOperatorType.modal by:
|
|
*
|
|
* - Matching key-map items with the operators modal key-map.
|
|
* - Converting double click events into press events,
|
|
* allowing them to be restored when the events aren't handled.
|
|
*
|
|
* This is done since we only want to use double click events to match key-map items,
|
|
* allowing modal functions to check for press/release events without having to interpret them.
|
|
*/
|
|
static void wm_event_modalkeymap_begin(const bContext *C,
|
|
wmOperator *op,
|
|
wmEvent *event,
|
|
wmEvent_ModalMapStore *event_backup)
|
|
{
|
|
BLI_assert(event->type != EVT_MODAL_MAP);
|
|
|
|
/* Support for modal key-map in macros. */
|
|
if (op->opm) {
|
|
op = op->opm;
|
|
}
|
|
|
|
event_backup->dbl_click_disabled = false;
|
|
|
|
if (op->type->modalkeymap) {
|
|
wmKeyMap *keymap = WM_keymap_active(CTX_wm_manager(C), op->type->modalkeymap);
|
|
wmKeyMapItem *kmi = nullptr;
|
|
|
|
const wmEvent *event_match = nullptr;
|
|
wmEvent event_no_dbl_click;
|
|
|
|
if ((kmi = wm_eventmatch_modal_keymap_items(keymap, op, event))) {
|
|
event_match = event;
|
|
}
|
|
else if (event->val == KM_DBL_CLICK) {
|
|
event_no_dbl_click = *event;
|
|
event_no_dbl_click.val = KM_PRESS;
|
|
if ((kmi = wm_eventmatch_modal_keymap_items(keymap, op, &event_no_dbl_click))) {
|
|
event_match = &event_no_dbl_click;
|
|
}
|
|
}
|
|
|
|
if (event_match != nullptr) {
|
|
event_backup->prev_type = event->prev_type;
|
|
event_backup->prev_val = event->prev_val;
|
|
|
|
event->prev_type = event_match->type;
|
|
event->prev_val = event_match->val;
|
|
event->type = EVT_MODAL_MAP;
|
|
event->val = kmi->propvalue;
|
|
|
|
/* Avoid double-click events even in the case of #EVT_MODAL_MAP,
|
|
* since it's possible users configure double-click key-map items
|
|
* which would break when modal functions expect press/release. */
|
|
if (event->prev_type == KM_DBL_CLICK) {
|
|
event->prev_type = KM_PRESS;
|
|
event_backup->dbl_click_disabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event->type != EVT_MODAL_MAP) {
|
|
/* This bypass just disables support for double-click in modal handlers. */
|
|
if (event->val == KM_DBL_CLICK) {
|
|
event->val = KM_PRESS;
|
|
event_backup->dbl_click_disabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restore changes from #wm_event_modalkeymap_begin
|
|
*
|
|
* \warning bad hacking event system...
|
|
* better restore event type for checking of #KM_CLICK for example.
|
|
* Modal maps could use different method (ton).
|
|
*/
|
|
static void wm_event_modalkeymap_end(wmEvent *event, const wmEvent_ModalMapStore *event_backup)
|
|
{
|
|
if (event->type == EVT_MODAL_MAP) {
|
|
event->type = event->prev_type;
|
|
event->val = event->prev_val;
|
|
|
|
event->prev_type = event_backup->prev_type;
|
|
event->prev_val = event_backup->prev_val;
|
|
}
|
|
|
|
if (event_backup->dbl_click_disabled) {
|
|
event->val = KM_DBL_CLICK;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \warning this function removes a modal handler, when finished.
|
|
*/
|
|
static int wm_handler_operator_call(bContext *C,
|
|
ListBase *handlers,
|
|
wmEventHandler *handler_base,
|
|
wmEvent *event,
|
|
PointerRNA *properties,
|
|
const char *kmi_idname)
|
|
{
|
|
int retval = OPERATOR_PASS_THROUGH;
|
|
|
|
/* Derived, modal or blocking operator. */
|
|
if ((handler_base->type == WM_HANDLER_TYPE_OP) &&
|
|
(((wmEventHandler_Op *)handler_base)->op != nullptr)) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
wmOperator *op = handler->op;
|
|
wmOperatorType *ot = op->type;
|
|
|
|
if (!wm_operator_check_locked_interface(C, ot)) {
|
|
/* Interface is locked and operator is not allowed to run,
|
|
* nothing to do in this case. */
|
|
}
|
|
else if (ot->modal) {
|
|
/* We set context to where modal handler came from. */
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
wm_handler_op_context(C, handler, event);
|
|
wm_region_mouse_co(C, event);
|
|
|
|
wmEvent_ModalMapStore event_backup;
|
|
wm_event_modalkeymap_begin(C, op, event, &event_backup);
|
|
|
|
const intptr_t undo_id_prev = wm_operator_undo_active_id(wm);
|
|
if (ot->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
/* Warning, after this call all context data and 'event' may be freed. see check below. */
|
|
retval = ot->modal(C, op, event);
|
|
OPERATOR_RETVAL_CHECK(retval);
|
|
|
|
if (ot->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
/* When the window changes the modal modifier may have loaded a new blend file
|
|
* (the `system_demo_mode` add-on does this), so we have to assume the event,
|
|
* operator, area, region etc have all been freed. */
|
|
if (CTX_wm_window(C) == win) {
|
|
|
|
wm_event_modalkeymap_end(event, &event_backup);
|
|
|
|
if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {
|
|
wm_operator_reports(C, op, retval, false);
|
|
|
|
if (op->type->modalkeymap) {
|
|
WM_window_status_area_tag_redraw(win);
|
|
}
|
|
}
|
|
else {
|
|
/* Not very common, but modal operators may report before finishing. */
|
|
if (!BLI_listbase_is_empty(&op->reports->list)) {
|
|
wm_add_reports(op->reports);
|
|
}
|
|
}
|
|
|
|
/* Important to run 'wm_operator_finished' before setting the context members to null. */
|
|
if (retval & OPERATOR_FINISHED) {
|
|
const bool has_undo_step = (undo_id_prev != wm_operator_undo_active_id(wm));
|
|
|
|
wm_operator_finished(C, op, false, true, has_undo_step);
|
|
handler->op = nullptr;
|
|
}
|
|
else if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {
|
|
WM_operator_free(op);
|
|
handler->op = nullptr;
|
|
}
|
|
|
|
/* Putting back screen context, `reval` can pass through after modal failures! */
|
|
if ((retval & OPERATOR_PASS_THROUGH) || wm_event_always_pass(event)) {
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
}
|
|
else {
|
|
/* This special cases is for areas and regions that get removed. */
|
|
CTX_wm_area_set(C, nullptr);
|
|
CTX_wm_region_set(C, nullptr);
|
|
}
|
|
|
|
/* Update gizmos during modal handlers. */
|
|
wm_gizmomaps_handled_modal_update(C, event, handler);
|
|
|
|
/* Remove modal handler, operator itself should have been canceled and freed. */
|
|
if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {
|
|
WM_cursor_grab_disable(CTX_wm_window(C), nullptr);
|
|
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(&handler->head);
|
|
|
|
/* Prevent silly errors from operator users. */
|
|
// retval &= ~OPERATOR_PASS_THROUGH;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
CLOG_ERROR(WM_LOG_HANDLERS, "missing modal '%s'", op->idname);
|
|
}
|
|
}
|
|
else {
|
|
wmOperatorType *ot = WM_operatortype_find(kmi_idname, false);
|
|
|
|
if (ot && wm_operator_check_locked_interface(C, ot)) {
|
|
bool use_last_properties = true;
|
|
PointerRNA tool_properties = {nullptr};
|
|
|
|
bToolRef *keymap_tool = nullptr;
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
keymap_tool = ((wmEventHandler_Keymap *)handler_base)->keymap_tool;
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_GIZMO) {
|
|
wmGizmoMap *gizmo_map = ((wmEventHandler_Gizmo *)handler_base)->gizmo_map;
|
|
wmGizmo *gz = wm_gizmomap_highlight_get(gizmo_map);
|
|
if (gz && (gz->flag & WM_GIZMO_OPERATOR_TOOL_INIT)) {
|
|
keymap_tool = WM_toolsystem_ref_from_context(C);
|
|
}
|
|
}
|
|
|
|
const bool is_tool = (keymap_tool != nullptr);
|
|
const bool use_tool_properties = is_tool;
|
|
|
|
if (use_tool_properties) {
|
|
WM_toolsystem_ref_properties_init_for_keymap(
|
|
keymap_tool, &tool_properties, properties, ot);
|
|
properties = &tool_properties;
|
|
use_last_properties = false;
|
|
}
|
|
|
|
retval = wm_operator_invoke(C, ot, event, properties, nullptr, false, use_last_properties);
|
|
|
|
if (use_tool_properties) {
|
|
WM_operator_properties_free(&tool_properties);
|
|
}
|
|
|
|
/* Link gizmo if #WM_GIZMOGROUPTYPE_TOOL_INIT is set. */
|
|
if (retval & OPERATOR_FINISHED) {
|
|
if (is_tool) {
|
|
bToolRef_Runtime *tref_rt = keymap_tool->runtime;
|
|
if (tref_rt->gizmo_group[0]) {
|
|
const char *idname = tref_rt->gizmo_group;
|
|
wmGizmoGroupType *gzgt = WM_gizmogrouptype_find(idname, false);
|
|
if (gzgt != nullptr) {
|
|
if ((gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_INIT) != 0) {
|
|
ARegion *region = CTX_wm_region(C);
|
|
if (region != nullptr) {
|
|
wmGizmoMapType *gzmap_type = WM_gizmomaptype_ensure(&gzgt->gzmap_params);
|
|
WM_gizmo_group_type_ensure_ptr_ex(gzgt, gzmap_type);
|
|
wmGizmoGroup *gzgroup = WM_gizmomaptype_group_init_runtime_with_region(
|
|
gzmap_type, gzgt, region);
|
|
/* We can't rely on drawing to initialize gizmo's since disabling
|
|
* overlays/gizmos will prevent pre-drawing setup calls. (see T60905) */
|
|
WM_gizmogroup_ensure_init(C, gzgroup);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* Done linking gizmo. */
|
|
}
|
|
}
|
|
|
|
/* Finished and pass through flag as handled. */
|
|
if (retval == (OPERATOR_FINISHED | OPERATOR_PASS_THROUGH)) {
|
|
return WM_HANDLER_HANDLED;
|
|
}
|
|
|
|
/* Modal unhandled, break. */
|
|
if (retval == (OPERATOR_PASS_THROUGH | OPERATOR_RUNNING_MODAL)) {
|
|
return (WM_HANDLER_BREAK | WM_HANDLER_MODAL);
|
|
}
|
|
|
|
if (retval & OPERATOR_PASS_THROUGH) {
|
|
return WM_HANDLER_CONTINUE;
|
|
}
|
|
|
|
return WM_HANDLER_BREAK;
|
|
}
|
|
|
|
static void wm_operator_free_for_fileselect(wmOperator *file_operator)
|
|
{
|
|
LISTBASE_FOREACH (bScreen *, screen, &G_MAIN->screens) {
|
|
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
|
|
if (area->spacetype == SPACE_FILE) {
|
|
SpaceFile *sfile = static_cast<SpaceFile *>(area->spacedata.first);
|
|
if (sfile->op == file_operator) {
|
|
sfile->op = nullptr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WM_operator_free(file_operator);
|
|
}
|
|
|
|
/**
|
|
* File-select handlers are only in the window queue,
|
|
* so it's safe to switch screens or area types.
|
|
*/
|
|
static int wm_handler_fileselect_do(bContext *C,
|
|
ListBase *handlers,
|
|
wmEventHandler_Op *handler,
|
|
int val)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
switch (val) {
|
|
case EVT_FILESELECT_FULL_OPEN: {
|
|
wmWindow *win = CTX_wm_window(C);
|
|
ScrArea *area;
|
|
|
|
if ((area = ED_screen_temp_space_open(C,
|
|
IFACE_("Blender File View"),
|
|
WM_window_pixels_x(win) / 2,
|
|
WM_window_pixels_y(win) / 2,
|
|
U.file_space_data.temp_win_sizex * UI_DPI_FAC,
|
|
U.file_space_data.temp_win_sizey * UI_DPI_FAC,
|
|
SPACE_FILE,
|
|
U.filebrowser_display_type,
|
|
true))) {
|
|
ARegion *region_header = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
|
|
|
|
BLI_assert(area->spacetype == SPACE_FILE);
|
|
|
|
region_header->flag |= RGN_FLAG_HIDDEN;
|
|
/* Header on bottom, #AZone triangle to toggle header looks misplaced at the top. */
|
|
region_header->alignment = RGN_ALIGN_BOTTOM;
|
|
|
|
/* Settings for file-browser, #sfile is not operator owner but sends events. */
|
|
SpaceFile *sfile = (SpaceFile *)area->spacedata.first;
|
|
sfile->op = handler->op;
|
|
|
|
ED_fileselect_set_params_from_userdef(sfile);
|
|
}
|
|
else {
|
|
BKE_report(&wm->reports, RPT_ERROR, "Failed to open window!");
|
|
return OPERATOR_CANCELLED;
|
|
}
|
|
|
|
action = WM_HANDLER_BREAK;
|
|
break;
|
|
}
|
|
|
|
case EVT_FILESELECT_EXEC:
|
|
case EVT_FILESELECT_CANCEL:
|
|
case EVT_FILESELECT_EXTERNAL_CANCEL: {
|
|
wmWindow *ctx_win = CTX_wm_window(C);
|
|
wmEvent *eventstate = ctx_win->eventstate;
|
|
/* The root window of the operation as determined in #WM_event_add_fileselect(). */
|
|
wmWindow *root_win = handler->context.win;
|
|
|
|
/* Remove link now, for load file case before removing. */
|
|
BLI_remlink(handlers, handler);
|
|
|
|
if (val == EVT_FILESELECT_EXTERNAL_CANCEL) {
|
|
/* The window might have been freed already. */
|
|
if (BLI_findindex(&wm->windows, handler->context.win) == -1) {
|
|
handler->context.win = nullptr;
|
|
}
|
|
}
|
|
else {
|
|
ScrArea *ctx_area = CTX_wm_area(C);
|
|
|
|
wmWindow *temp_win = nullptr;
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
ScrArea *file_area = static_cast<ScrArea *>(screen->areabase.first);
|
|
|
|
if ((file_area->spacetype != SPACE_FILE) || !WM_window_is_temp_screen(win)) {
|
|
continue;
|
|
}
|
|
|
|
if (file_area->full) {
|
|
/* Users should not be able to maximize/full-screen an area in a temporary screen.
|
|
* So if there's a maximized file browser in a temporary screen,
|
|
* it was likely opened by #EVT_FILESELECT_FULL_OPEN. */
|
|
continue;
|
|
}
|
|
|
|
int win_size[2];
|
|
bool is_maximized;
|
|
ED_fileselect_window_params_get(win, win_size, &is_maximized);
|
|
ED_fileselect_params_to_userdef(
|
|
static_cast<SpaceFile *>(file_area->spacedata.first), win_size, is_maximized);
|
|
|
|
if (BLI_listbase_is_single(&file_area->spacedata)) {
|
|
BLI_assert(root_win != win);
|
|
|
|
wm_window_close(C, wm, win);
|
|
|
|
CTX_wm_window_set(C, root_win); /* #wm_window_close() nullptrs. */
|
|
/* Some operators expect a drawable context (for #EVT_FILESELECT_EXEC). */
|
|
wm_window_make_drawable(wm, root_win);
|
|
/* Ensure correct cursor position, otherwise, popups may close immediately after
|
|
* opening (#UI_BLOCK_MOVEMOUSE_QUIT). */
|
|
wm_cursor_position_get(root_win, &eventstate->xy[0], &eventstate->xy[1]);
|
|
wm->winactive = root_win; /* Reports use this... */
|
|
}
|
|
else if (file_area->full) {
|
|
ED_screen_full_prevspace(C, file_area);
|
|
}
|
|
else {
|
|
ED_area_prevspace(C, file_area);
|
|
}
|
|
|
|
temp_win = win;
|
|
break;
|
|
}
|
|
|
|
if (!temp_win && ctx_area->full) {
|
|
ED_fileselect_params_to_userdef(
|
|
static_cast<SpaceFile *>(ctx_area->spacedata.first), nullptr, false);
|
|
ED_screen_full_prevspace(C, ctx_area);
|
|
}
|
|
}
|
|
|
|
CTX_wm_window_set(C, root_win);
|
|
wm_handler_op_context(C, handler, eventstate);
|
|
/* At this point context is supposed to match the root context determined by
|
|
* #WM_event_add_fileselect(). */
|
|
BLI_assert(!CTX_wm_area(C) || (CTX_wm_area(C) == handler->context.area));
|
|
BLI_assert(!CTX_wm_region(C) || (CTX_wm_region(C) == handler->context.region));
|
|
|
|
ScrArea *handler_area = CTX_wm_area(C);
|
|
/* Make sure new context area is ready, the operator callback may operate on it. */
|
|
if (handler_area) {
|
|
ED_area_do_refresh(C, handler_area);
|
|
}
|
|
|
|
/* Needed for #UI_popup_menu_reports. */
|
|
|
|
if (val == EVT_FILESELECT_EXEC) {
|
|
int retval;
|
|
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
retval = handler->op->type->exec(C, handler->op);
|
|
|
|
/* XXX check this carefully, `CTX_wm_manager(C) == wm` is a bit hackish. */
|
|
if (handler->op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
|
|
/* XXX check this carefully, `CTX_wm_manager(C) == wm` is a bit hackish. */
|
|
if (CTX_wm_manager(C) == wm && wm->op_undo_depth == 0) {
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
ED_undo_push_op(C, handler->op);
|
|
}
|
|
else if (handler->op->type->flag & OPTYPE_UNDO_GROUPED) {
|
|
ED_undo_grouped_push_op(C, handler->op);
|
|
}
|
|
}
|
|
|
|
if (handler->op->reports->list.first) {
|
|
|
|
/* FIXME(@campbellbarton): temp setting window, this is really bad!
|
|
* only have because lib linking errors need to be seen by users :(
|
|
* it can be removed without breaking anything but then no linking errors. */
|
|
wmWindow *win_prev = CTX_wm_window(C);
|
|
ScrArea *area_prev = CTX_wm_area(C);
|
|
ARegion *region_prev = CTX_wm_region(C);
|
|
|
|
if (win_prev == nullptr) {
|
|
CTX_wm_window_set(C, static_cast<wmWindow *>(CTX_wm_manager(C)->windows.first));
|
|
}
|
|
|
|
BKE_report_print_level_set(handler->op->reports, RPT_WARNING);
|
|
UI_popup_menu_reports(C, handler->op->reports);
|
|
|
|
/* XXX: copied from #wm_operator_finished(). */
|
|
/* Add reports to the global list, otherwise they are not seen. */
|
|
BLI_movelisttolist(&CTX_wm_reports(C)->list, &handler->op->reports->list);
|
|
|
|
/* More hacks, since we meddle with reports, banner display doesn't happen automatic. */
|
|
WM_report_banner_show();
|
|
|
|
CTX_wm_window_set(C, win_prev);
|
|
CTX_wm_area_set(C, area_prev);
|
|
CTX_wm_region_set(C, region_prev);
|
|
}
|
|
|
|
/* For #WM_operator_pystring only, custom report handling is done above. */
|
|
wm_operator_reports(C, handler->op, retval, true);
|
|
|
|
if (retval & OPERATOR_FINISHED) {
|
|
WM_operator_last_properties_store(handler->op);
|
|
}
|
|
|
|
if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {
|
|
wm_operator_free_for_fileselect(handler->op);
|
|
}
|
|
}
|
|
else {
|
|
if (handler->op->type->cancel) {
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth++;
|
|
}
|
|
|
|
handler->op->type->cancel(C, handler->op);
|
|
|
|
if (handler->op->type->flag & OPTYPE_UNDO) {
|
|
wm->op_undo_depth--;
|
|
}
|
|
}
|
|
wm_operator_free_for_fileselect(handler->op);
|
|
}
|
|
|
|
CTX_wm_area_set(C, nullptr);
|
|
|
|
wm_event_free_handler(&handler->head);
|
|
|
|
action = WM_HANDLER_BREAK;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static int wm_handler_fileselect_call(bContext *C,
|
|
ListBase *handlers,
|
|
wmEventHandler_Op *handler,
|
|
const wmEvent *event)
|
|
{
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
if (event->type != EVT_FILESELECT) {
|
|
return action;
|
|
}
|
|
if (handler->op != (wmOperator *)event->customdata) {
|
|
return action;
|
|
}
|
|
|
|
return wm_handler_fileselect_do(C, handlers, handler, event->val);
|
|
}
|
|
|
|
static bool wm_action_not_handled(int action)
|
|
{
|
|
return action == WM_HANDLER_CONTINUE || action == (WM_HANDLER_BREAK | WM_HANDLER_MODAL);
|
|
}
|
|
|
|
static const char *keymap_handler_log_action_str(const int action)
|
|
{
|
|
if (action & WM_HANDLER_BREAK) {
|
|
return "handled";
|
|
}
|
|
if (action & WM_HANDLER_HANDLED) {
|
|
return "handled (and pass on)";
|
|
}
|
|
return "un-handled";
|
|
}
|
|
|
|
static const char *keymap_handler_log_kmi_event_str(const wmKeyMapItem *kmi,
|
|
char *buf,
|
|
size_t buf_maxlen)
|
|
{
|
|
/* Short representation of the key that was pressed,
|
|
* include this since it may differ from the event in minor details
|
|
* which can help looking up the key-map definition. */
|
|
WM_keymap_item_to_string(kmi, false, buf, buf_maxlen);
|
|
return buf;
|
|
}
|
|
|
|
static const char *keymap_handler_log_kmi_op_str(bContext *C,
|
|
const wmKeyMapItem *kmi,
|
|
char *buf,
|
|
size_t buf_maxlen)
|
|
{
|
|
/* The key-map item properties can further help distinguish this item from others. */
|
|
char *kmi_props = nullptr;
|
|
if (kmi->properties != nullptr) {
|
|
wmOperatorType *ot = WM_operatortype_find(kmi->idname, false);
|
|
if (ot) {
|
|
kmi_props = RNA_pointer_as_string_keywords(C, kmi->ptr, false, false, true, 512);
|
|
}
|
|
else { /* Fallback. */
|
|
kmi_props = IDP_reprN(kmi->properties, nullptr);
|
|
}
|
|
}
|
|
BLI_snprintf(buf, buf_maxlen, "%s(%s)", kmi->idname, kmi_props ? kmi_props : "");
|
|
if (kmi_props != nullptr) {
|
|
MEM_freeN(kmi_props);
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
#define PRINT \
|
|
if (do_debug_handler) \
|
|
printf
|
|
|
|
static int wm_handlers_do_keymap_with_keymap_handler(
|
|
/* From 'wm_handlers_do_intern'. */
|
|
bContext *C,
|
|
wmEvent *event,
|
|
ListBase *handlers,
|
|
wmEventHandler_Keymap *handler,
|
|
/* Additional. */
|
|
wmKeyMap *keymap,
|
|
const bool do_debug_handler)
|
|
{
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
if (keymap == nullptr) {
|
|
/* Only callback is allowed to have nullptr key-maps. */
|
|
BLI_assert(handler->dynamic.keymap_fn);
|
|
}
|
|
else {
|
|
PRINT("%s: checking '%s' ...", __func__, keymap->idname);
|
|
|
|
if (WM_keymap_poll(C, keymap)) {
|
|
|
|
PRINT("pass\n");
|
|
|
|
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
|
if (wm_eventmatch(event, kmi)) {
|
|
wmEventHandler_KeymapPost keymap_post = handler->post;
|
|
|
|
action |= wm_handler_operator_call(
|
|
C, handlers, &handler->head, event, kmi->ptr, kmi->idname);
|
|
|
|
char op_buf[512];
|
|
char kmi_buf[128];
|
|
CLOG_INFO(WM_LOG_HANDLERS,
|
|
2,
|
|
"keymap '%s', %s, %s, event: %s",
|
|
keymap->idname,
|
|
keymap_handler_log_kmi_op_str(C, kmi, op_buf, sizeof(op_buf)),
|
|
keymap_handler_log_action_str(action),
|
|
keymap_handler_log_kmi_event_str(kmi, kmi_buf, sizeof(kmi_buf)));
|
|
|
|
if (action & WM_HANDLER_BREAK) {
|
|
/* Not always_pass here, it denotes removed handler_base. */
|
|
if (keymap_post.post_fn != nullptr) {
|
|
keymap_post.post_fn(keymap, kmi, keymap_post.user_data);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
PRINT("fail\n");
|
|
}
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static int wm_handlers_do_keymap_with_gizmo_handler(
|
|
/* From 'wm_handlers_do_intern' */
|
|
bContext *C,
|
|
wmEvent *event,
|
|
ListBase *handlers,
|
|
wmEventHandler_Gizmo *handler,
|
|
/* Additional. */
|
|
wmGizmoGroup *gzgroup,
|
|
wmKeyMap *keymap,
|
|
const bool do_debug_handler,
|
|
bool *r_keymap_poll)
|
|
{
|
|
int action = WM_HANDLER_CONTINUE;
|
|
bool keymap_poll = false;
|
|
|
|
PRINT("%s: checking '%s' ...", __func__, keymap->idname);
|
|
|
|
if (WM_keymap_poll(C, keymap)) {
|
|
keymap_poll = true;
|
|
PRINT("pass\n");
|
|
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
|
if (wm_eventmatch(event, kmi)) {
|
|
PRINT("%s: item matched '%s'\n", __func__, kmi->idname);
|
|
|
|
CTX_wm_gizmo_group_set(C, gzgroup);
|
|
|
|
/* `handler->op` is called later, we want key-map op to be triggered here. */
|
|
action |= wm_handler_operator_call(
|
|
C, handlers, &handler->head, event, kmi->ptr, kmi->idname);
|
|
|
|
CTX_wm_gizmo_group_set(C, nullptr);
|
|
|
|
if (action & WM_HANDLER_BREAK) {
|
|
if (G.debug & (G_DEBUG_EVENTS | G_DEBUG_HANDLERS)) {
|
|
printf("%s: handled - and pass on! '%s'\n", __func__, kmi->idname);
|
|
}
|
|
break;
|
|
}
|
|
if (action & WM_HANDLER_HANDLED) {
|
|
if (G.debug & (G_DEBUG_EVENTS | G_DEBUG_HANDLERS)) {
|
|
printf("%s: handled - and pass on! '%s'\n", __func__, kmi->idname);
|
|
}
|
|
}
|
|
else {
|
|
PRINT("%s: un-handled '%s'\n", __func__, kmi->idname);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
PRINT("fail\n");
|
|
}
|
|
|
|
if (r_keymap_poll) {
|
|
*r_keymap_poll = keymap_poll;
|
|
}
|
|
|
|
return action;
|
|
}
|
|
|
|
static int wm_handlers_do_gizmo_handler(bContext *C,
|
|
wmWindowManager *wm,
|
|
wmEventHandler_Gizmo *handler,
|
|
wmEvent *event,
|
|
ListBase *handlers,
|
|
const bool do_debug_handler)
|
|
{
|
|
/* Drag events use the previous click location to highlight the gizmos,
|
|
* Get the highlight again in case the user dragged off the gizmo. */
|
|
const bool is_event_drag = (event->val == KM_CLICK_DRAG);
|
|
const bool is_event_modifier = ISKEYMODIFIER(event->type);
|
|
/* Only keep the highlight if the gizmo becomes modal as result of event handling.
|
|
* Without this check, even un-handled drag events will set the highlight if the drag
|
|
* was initiated over a gizmo. */
|
|
const bool restore_highlight_unless_activated = is_event_drag;
|
|
|
|
int action = WM_HANDLER_CONTINUE;
|
|
ScrArea *area = CTX_wm_area(C);
|
|
ARegion *region = CTX_wm_region(C);
|
|
wmGizmoMap *gzmap = handler->gizmo_map;
|
|
BLI_assert(gzmap != nullptr);
|
|
wmGizmo *gz = wm_gizmomap_highlight_get(gzmap);
|
|
|
|
/* Needed so UI blocks over gizmos don't let events fall through to the gizmos,
|
|
* noticeable for the node editor - where dragging on a node should move it, see: T73212.
|
|
* note we still allow for starting the gizmo drag outside, then travel 'inside' the node. */
|
|
if (region->type->clip_gizmo_events_by_ui) {
|
|
if (UI_region_block_find_mouse_over(region, event->xy, true)) {
|
|
if (gz != nullptr && event->type != EVT_GIZMO_UPDATE) {
|
|
if (restore_highlight_unless_activated == false) {
|
|
WM_tooltip_clear(C, CTX_wm_window(C));
|
|
wm_gizmomap_highlight_set(gzmap, C, nullptr, 0);
|
|
}
|
|
}
|
|
return action;
|
|
}
|
|
}
|
|
|
|
struct PrevGizmoData {
|
|
wmGizmo *gz_modal;
|
|
wmGizmo *gz;
|
|
int part;
|
|
};
|
|
PrevGizmoData prev{};
|
|
prev.gz_modal = wm_gizmomap_modal_get(gzmap);
|
|
prev.gz = gz;
|
|
prev.part = gz ? gz->highlight_part : 0;
|
|
|
|
if (region->gizmo_map != handler->gizmo_map) {
|
|
WM_gizmomap_tag_refresh(handler->gizmo_map);
|
|
}
|
|
|
|
wm_gizmomap_handler_context_gizmo(C, handler);
|
|
wm_region_mouse_co(C, event);
|
|
|
|
bool handle_highlight = false;
|
|
bool handle_keymap = false;
|
|
|
|
/* Handle gizmo highlighting. */
|
|
if ((prev.gz_modal == nullptr) &&
|
|
((event->type == MOUSEMOVE) || is_event_modifier || is_event_drag)) {
|
|
handle_highlight = true;
|
|
if (is_event_modifier || is_event_drag) {
|
|
handle_keymap = true;
|
|
}
|
|
}
|
|
else {
|
|
handle_keymap = true;
|
|
}
|
|
|
|
/* There is no need to handle this event when the key-map isn't being applied
|
|
* since any change to the highlight will be restored to the previous value. */
|
|
if (restore_highlight_unless_activated) {
|
|
if ((handle_highlight == true) && (handle_keymap == false)) {
|
|
return action;
|
|
}
|
|
}
|
|
|
|
if (handle_highlight) {
|
|
int part = -1;
|
|
gz = wm_gizmomap_highlight_find(gzmap, C, event, &part);
|
|
|
|
/* If no gizmos are/were active, don't clear tool-tips. */
|
|
if (gz || prev.gz) {
|
|
if ((prev.gz != gz) || (prev.part != part)) {
|
|
WM_tooltip_clear(C, CTX_wm_window(C));
|
|
}
|
|
}
|
|
|
|
if (wm_gizmomap_highlight_set(gzmap, C, gz, part)) {
|
|
if (gz != nullptr) {
|
|
if ((U.flag & USER_TOOLTIPS) && (gz->flag & WM_GIZMO_NO_TOOLTIP) == 0) {
|
|
WM_tooltip_timer_init(C, CTX_wm_window(C), area, region, WM_gizmomap_tooltip_init);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Don't use from now on. */
|
|
bool is_event_handle_all = gz && (gz->flag & WM_GIZMO_EVENT_HANDLE_ALL);
|
|
|
|
if (handle_keymap) {
|
|
/* Handle highlight gizmo. */
|
|
if ((gz != nullptr) && (gz->flag & WM_GIZMO_HIDDEN_KEYMAP) == 0) {
|
|
bool keymap_poll = false;
|
|
wmGizmoGroup *gzgroup = gz->parent_gzgroup;
|
|
wmKeyMap *keymap = WM_keymap_active(wm, gz->keymap ? gz->keymap : gzgroup->type->keymap);
|
|
action |= wm_handlers_do_keymap_with_gizmo_handler(
|
|
C, event, handlers, handler, gzgroup, keymap, do_debug_handler, &keymap_poll);
|
|
|
|
#ifdef USE_GIZMO_MOUSE_PRIORITY_HACK
|
|
if (((action & WM_HANDLER_BREAK) == 0) && !is_event_handle_all && keymap_poll) {
|
|
if ((event->val == KM_PRESS) && ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
|
|
|
|
wmEvent event_test_click = *event;
|
|
event_test_click.val = KM_CLICK;
|
|
|
|
wmEvent event_test_click_drag = *event;
|
|
event_test_click_drag.val = KM_CLICK_DRAG;
|
|
|
|
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
|
if ((kmi->flag & KMI_INACTIVE) == 0) {
|
|
if (wm_eventmatch(&event_test_click, kmi) ||
|
|
wm_eventmatch(&event_test_click_drag, kmi)) {
|
|
wmOperatorType *ot = WM_operatortype_find(kmi->idname, false);
|
|
if (WM_operator_poll_context(C, ot, WM_OP_INVOKE_DEFAULT)) {
|
|
is_event_handle_all = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif /* `USE_GIZMO_MOUSE_PRIORITY_HACK` */
|
|
}
|
|
|
|
/* Don't use from now on. */
|
|
gz = nullptr;
|
|
|
|
/* Fallback to selected gizmo (when un-handled). */
|
|
if ((action & WM_HANDLER_BREAK) == 0) {
|
|
if (WM_gizmomap_is_any_selected(gzmap)) {
|
|
const ListBase *groups = WM_gizmomap_group_list(gzmap);
|
|
LISTBASE_FOREACH (wmGizmoGroup *, gzgroup, groups) {
|
|
if (wm_gizmogroup_is_any_selected(gzgroup)) {
|
|
wmKeyMap *keymap = WM_keymap_active(wm, gzgroup->type->keymap);
|
|
action |= wm_handlers_do_keymap_with_gizmo_handler(
|
|
C, event, handlers, handler, gzgroup, keymap, do_debug_handler, nullptr);
|
|
if (action & WM_HANDLER_BREAK) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (handle_highlight) {
|
|
if (restore_highlight_unless_activated) {
|
|
/* Check handling the key-map didn't activate a gizmo. */
|
|
wmGizmo *gz_modal = wm_gizmomap_modal_get(gzmap);
|
|
if (!(gz_modal && (gz_modal != prev.gz_modal))) {
|
|
wm_gizmomap_highlight_set(gzmap, C, prev.gz, prev.part);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_event_handle_all) {
|
|
if (action == WM_HANDLER_CONTINUE) {
|
|
action |= WM_HANDLER_BREAK | WM_HANDLER_MODAL;
|
|
}
|
|
}
|
|
|
|
/* Restore the area. */
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
|
|
return action;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Handle Single Event (All Handler Types)
|
|
* \{ */
|
|
|
|
static int wm_handlers_do_intern(bContext *C, wmWindow *win, wmEvent *event, ListBase *handlers)
|
|
{
|
|
const bool do_debug_handler =
|
|
(G.debug & G_DEBUG_HANDLERS) &&
|
|
/* Comment this out to flood the console! (if you really want to test). */
|
|
!ISMOUSE_MOTION(event->type);
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
if (handlers == nullptr) {
|
|
wm_event_handler_return_value_check(C, event, action);
|
|
return action;
|
|
}
|
|
|
|
/* Modal handlers can get removed in this loop, we keep the loop this way.
|
|
*
|
|
* NOTE: check 'handlers->first' because in rare cases the handlers can be cleared
|
|
* by the event that's called, for eg:
|
|
*
|
|
* Calling a python script which changes the area.type, see T32232. */
|
|
for (wmEventHandler *handler_base = static_cast<wmEventHandler *>(handlers->first),
|
|
*handler_base_next;
|
|
handler_base && handlers->first;
|
|
handler_base = handler_base_next) {
|
|
handler_base_next = handler_base->next;
|
|
|
|
/* During this loop, UI handlers for nested menus can tag multiple handlers free. */
|
|
if (handler_base->flag & WM_HANDLER_DO_FREE) {
|
|
/* Pass. */
|
|
}
|
|
else if (handler_base->poll == nullptr || handler_base->poll(CTX_wm_region(C), event)) {
|
|
/* In advance to avoid access to freed event on window close. */
|
|
const int always_pass = wm_event_always_pass(event);
|
|
|
|
/* Modal+blocking handler_base. */
|
|
if (handler_base->flag & WM_HANDLER_BLOCKING) {
|
|
action |= WM_HANDLER_BREAK;
|
|
}
|
|
|
|
/* Handle all types here. */
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
|
wmEventHandler_KeymapResult km_result;
|
|
WM_event_get_keymaps_from_handler(wm, win, handler, &km_result);
|
|
int action_iter = WM_HANDLER_CONTINUE;
|
|
for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
|
|
wmKeyMap *keymap = km_result.keymaps[km_index];
|
|
action_iter |= wm_handlers_do_keymap_with_keymap_handler(
|
|
C, event, handlers, handler, keymap, do_debug_handler);
|
|
if (action_iter & WM_HANDLER_BREAK) {
|
|
break;
|
|
}
|
|
}
|
|
action |= action_iter;
|
|
|
|
/* Clear the tool-tip whenever a key binding is handled, without this tool-tips
|
|
* are kept when a modal operators starts (annoying but otherwise harmless). */
|
|
if (action & WM_HANDLER_BREAK) {
|
|
/* Window may be gone after file read. */
|
|
if (CTX_wm_window(C) != nullptr) {
|
|
WM_tooltip_clear(C, CTX_wm_window(C));
|
|
}
|
|
}
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
BLI_assert(handler->handle_fn != nullptr);
|
|
if (!wm->is_interface_locked) {
|
|
action |= wm_handler_ui_call(C, handler, event, always_pass);
|
|
}
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_DROPBOX) {
|
|
wmEventHandler_Dropbox *handler = (wmEventHandler_Dropbox *)handler_base;
|
|
if (!wm->is_interface_locked && event->type == EVT_DROP) {
|
|
LISTBASE_FOREACH (wmDropBox *, drop, handler->dropboxes) {
|
|
/* Other drop custom types allowed. */
|
|
if (event->custom == EVT_DATA_DRAGDROP) {
|
|
ListBase *lb = (ListBase *)event->customdata;
|
|
LISTBASE_FOREACH_MUTABLE (wmDrag *, drag, lb) {
|
|
if (drop->poll(C, drag, event)) {
|
|
wm_drop_prepare(C, drag, drop);
|
|
|
|
/* Pass single matched #wmDrag onto the operator. */
|
|
BLI_remlink(lb, drag);
|
|
ListBase single_lb = {nullptr};
|
|
BLI_addtail(&single_lb, drag);
|
|
event->customdata = &single_lb;
|
|
|
|
const wmOperatorCallContext opcontext = wm_drop_operator_context_get(drop);
|
|
int op_retval = wm_operator_call_internal(
|
|
C, drop->ot, drop->ptr, nullptr, opcontext, false, event);
|
|
OPERATOR_RETVAL_CHECK(op_retval);
|
|
|
|
if ((op_retval & OPERATOR_CANCELLED) && drop->cancel) {
|
|
drop->cancel(CTX_data_main(C), drag, drop);
|
|
}
|
|
|
|
action |= WM_HANDLER_BREAK;
|
|
|
|
/* Free the drags. */
|
|
WM_drag_free_list(lb);
|
|
WM_drag_free_list(&single_lb);
|
|
|
|
wm_event_custom_clear(event);
|
|
|
|
wm_drop_end(C, drag, drop);
|
|
|
|
/* XXX file-read case. */
|
|
if (CTX_wm_window(C) == nullptr) {
|
|
return action;
|
|
}
|
|
|
|
/* Escape from drag loop, got freed. */
|
|
break;
|
|
}
|
|
}
|
|
/* Always exit all drags on a drop event, even if poll didn't succeed. */
|
|
wm_drags_exit(wm, win);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_GIZMO) {
|
|
wmEventHandler_Gizmo *handler = (wmEventHandler_Gizmo *)handler_base;
|
|
action |= wm_handlers_do_gizmo_handler(C, wm, handler, event, handlers, do_debug_handler);
|
|
}
|
|
else if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
if (handler->is_fileselect) {
|
|
if (!wm->is_interface_locked) {
|
|
/* Screen context changes here. */
|
|
action |= wm_handler_fileselect_call(C, handlers, handler, event);
|
|
}
|
|
}
|
|
else {
|
|
action |= wm_handler_operator_call(C, handlers, handler_base, event, nullptr, nullptr);
|
|
}
|
|
}
|
|
else {
|
|
/* Unreachable (handle all types above). */
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
if (action & WM_HANDLER_BREAK) {
|
|
if (always_pass) {
|
|
action &= ~WM_HANDLER_BREAK;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* File-read case, if the wm is freed then the handler's
|
|
* will have been too so the code below need not run. */
|
|
if (CTX_wm_window(C) == nullptr) {
|
|
return action;
|
|
}
|
|
|
|
/* Code this for all modal ops, and ensure free only happens here. */
|
|
|
|
/* The handler Could be freed already by regular modal ops. */
|
|
if (BLI_findindex(handlers, handler_base) != -1) {
|
|
/* Modal UI handler can be tagged to be freed. */
|
|
if (handler_base->flag & WM_HANDLER_DO_FREE) {
|
|
BLI_remlink(handlers, handler_base);
|
|
wm_event_free_handler(handler_base);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (action == (WM_HANDLER_BREAK | WM_HANDLER_MODAL)) {
|
|
wm_cursor_arrow_move(CTX_wm_window(C), event);
|
|
}
|
|
|
|
/* Do some extra sanity checking before returning the action. */
|
|
wm_event_handler_return_value_check(C, event, action);
|
|
return action;
|
|
}
|
|
|
|
#undef PRINT
|
|
|
|
/* This calls handlers twice - to solve (double-)click events. */
|
|
static int wm_handlers_do(bContext *C, wmEvent *event, ListBase *handlers)
|
|
{
|
|
int action = wm_handlers_do_intern(C, CTX_wm_window(C), event, handlers);
|
|
|
|
/* Will be nullptr in the file read case. */
|
|
wmWindow *win = CTX_wm_window(C);
|
|
if (win == nullptr) {
|
|
return action;
|
|
}
|
|
|
|
if (ISMOUSE_MOTION(event->type)) {
|
|
/* Test for #KM_CLICK_DRAG events. */
|
|
|
|
/* NOTE(@campbellbarton): Needed so drag can be used for editors that support both click
|
|
* selection and passing through the drag action to box select. See #WM_generic_select_modal.
|
|
* Unlike click, accept `action` when break isn't set.
|
|
* Operators can return `OPERATOR_FINISHED | OPERATOR_PASS_THROUGH` which results
|
|
* in `action` setting #WM_HANDLER_HANDLED, but not #WM_HANDLER_BREAK. */
|
|
if ((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action)) {
|
|
if (win->event_queue_check_drag) {
|
|
if ((event->flag & WM_EVENT_FORCE_DRAG_THRESHOLD) ||
|
|
WM_event_drag_test(event, event->prev_press_xy)) {
|
|
win->event_queue_check_drag_handled = true;
|
|
const int direction = WM_event_drag_direction(event);
|
|
|
|
/* Intentionally leave `event->xy` as-is, event users are expected to use
|
|
* `event->prev_press_xy` if they need to access the drag start location. */
|
|
const short prev_val = event->val;
|
|
const short prev_type = event->type;
|
|
const uint8_t prev_modifier = event->modifier;
|
|
const short prev_keymodifier = event->keymodifier;
|
|
|
|
event->val = KM_CLICK_DRAG;
|
|
event->type = event->prev_press_type;
|
|
event->modifier = event->prev_press_modifier;
|
|
event->keymodifier = event->prev_press_keymodifier;
|
|
event->direction = direction;
|
|
|
|
CLOG_INFO(WM_LOG_HANDLERS, 1, "handling CLICK_DRAG");
|
|
|
|
action |= wm_handlers_do_intern(C, win, event, handlers);
|
|
|
|
event->direction = 0;
|
|
event->keymodifier = prev_keymodifier;
|
|
event->modifier = prev_modifier;
|
|
event->val = prev_val;
|
|
event->type = prev_type;
|
|
|
|
win->event_queue_check_click = false;
|
|
if (!((action & WM_HANDLER_BREAK) == 0 || wm_action_not_handled(action))) {
|
|
/* Only disable when handled as other handlers may use this drag event. */
|
|
CLOG_INFO(WM_LOG_HANDLERS, 3, "canceling CLICK_DRAG: drag was generated & handled");
|
|
win->event_queue_check_drag = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (win->event_queue_check_drag) {
|
|
CLOG_INFO(WM_LOG_HANDLERS, 3, "canceling CLICK_DRAG: motion event was handled");
|
|
win->event_queue_check_drag = false;
|
|
}
|
|
}
|
|
}
|
|
else if (ISKEYBOARD_OR_BUTTON(event->type)) {
|
|
/* All events that don't set #wmEvent.prev_type must be ignored. */
|
|
|
|
/* Test for CLICK events. */
|
|
if (wm_action_not_handled(action)) {
|
|
/* #wmWindow.eventstate stores if previous event was a #KM_PRESS, in case that
|
|
* wasn't handled, the #KM_RELEASE will become a #KM_CLICK. */
|
|
|
|
if (event->val == KM_PRESS) {
|
|
if ((event->flag & WM_EVENT_IS_REPEAT) == 0) {
|
|
win->event_queue_check_click = true;
|
|
|
|
CLOG_INFO(WM_LOG_HANDLERS, 3, "detecting CLICK_DRAG: press event detected");
|
|
win->event_queue_check_drag = true;
|
|
|
|
win->event_queue_check_drag_handled = false;
|
|
}
|
|
}
|
|
else if (event->val == KM_RELEASE) {
|
|
if (win->event_queue_check_drag) {
|
|
if ((event->prev_press_type != event->type) &&
|
|
(ISKEYMODIFIER(event->type) || (event->type == event->prev_press_keymodifier))) {
|
|
/* Support releasing modifier keys without canceling the drag event, see T89989. */
|
|
}
|
|
else {
|
|
CLOG_INFO(
|
|
WM_LOG_HANDLERS, 3, "CLICK_DRAG: canceling (release event didn't match press)");
|
|
win->event_queue_check_drag = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (event->val == KM_RELEASE) {
|
|
if (event->prev_press_type == event->type) {
|
|
if (event->prev_val == KM_PRESS) {
|
|
if (win->event_queue_check_click) {
|
|
if (WM_event_drag_test(event, event->prev_press_xy)) {
|
|
win->event_queue_check_click = false;
|
|
if (win->event_queue_check_drag) {
|
|
CLOG_INFO(WM_LOG_HANDLERS,
|
|
3,
|
|
"CLICK_DRAG: canceling (key-release exceeds drag threshold)");
|
|
win->event_queue_check_drag = false;
|
|
}
|
|
}
|
|
else {
|
|
/* Position is where the actual click happens, for more
|
|
* accurate selecting in case the mouse drifts a little. */
|
|
const int xy[2] = {UNPACK2(event->xy)};
|
|
|
|
copy_v2_v2_int(event->xy, event->prev_press_xy);
|
|
event->val = KM_CLICK;
|
|
|
|
CLOG_INFO(WM_LOG_HANDLERS, 1, "CLICK: handling");
|
|
|
|
action |= wm_handlers_do_intern(C, win, event, handlers);
|
|
|
|
event->val = KM_RELEASE;
|
|
copy_v2_v2_int(event->xy, xy);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (event->val == KM_DBL_CLICK) {
|
|
/* The underlying event is a press, so try and handle this. */
|
|
event->val = KM_PRESS;
|
|
action |= wm_handlers_do_intern(C, win, event, handlers);
|
|
|
|
/* Revert value if not handled. */
|
|
if (wm_action_not_handled(action)) {
|
|
event->val = KM_DBL_CLICK;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
win->event_queue_check_click = false;
|
|
|
|
if (win->event_queue_check_drag) {
|
|
CLOG_INFO(WM_LOG_HANDLERS,
|
|
3,
|
|
"CLICK_DRAG: canceling (button event was handled: value=%d)",
|
|
event->val);
|
|
win->event_queue_check_drag = false;
|
|
}
|
|
}
|
|
}
|
|
else if (ISMOUSE_WHEEL(event->type) || ISMOUSE_GESTURE(event->type)) {
|
|
/* Modifiers which can trigger click event's,
|
|
* however we don't want this if the mouse wheel has been used, see T74607. */
|
|
if (wm_action_not_handled(action)) {
|
|
/* Pass. */
|
|
}
|
|
else {
|
|
if (ISKEYMODIFIER(event->prev_type)) {
|
|
win->event_queue_check_click = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
wm_event_handler_return_value_check(C, event, action);
|
|
return action;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Event Queue Utilities
|
|
*
|
|
* Utilities used by #wm_event_do_handlers.
|
|
* \{ */
|
|
|
|
static bool wm_event_inside_rect(const wmEvent *event, const rcti *rect)
|
|
{
|
|
if (wm_event_always_pass(event)) {
|
|
return true;
|
|
}
|
|
if (BLI_rcti_isect_pt_v(rect, event->xy)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static ScrArea *area_event_inside(bContext *C, const int xy[2])
|
|
{
|
|
wmWindow *win = CTX_wm_window(C);
|
|
bScreen *screen = CTX_wm_screen(C);
|
|
|
|
if (screen) {
|
|
ED_screen_areas_iter (win, screen, area) {
|
|
if (BLI_rcti_isect_pt_v(&area->totrct, xy)) {
|
|
return area;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static ARegion *region_event_inside(bContext *C, const int xy[2])
|
|
{
|
|
bScreen *screen = CTX_wm_screen(C);
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
if (screen && area) {
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
if (BLI_rcti_isect_pt_v(®ion->winrct, xy)) {
|
|
return region;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static void wm_paintcursor_tag(bContext *C, wmPaintCursor *pc, ARegion *region)
|
|
{
|
|
if (region) {
|
|
for (; pc; pc = pc->next) {
|
|
if (pc->poll == nullptr || pc->poll(C)) {
|
|
wmWindow *win = CTX_wm_window(C);
|
|
WM_paint_cursor_tag_redraw(win, region);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on mouse-move, check updates for `wm->paintcursors`.
|
|
*
|
|
* \note Context was set on active area and region.
|
|
*/
|
|
static void wm_paintcursor_test(bContext *C, const wmEvent *event)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
if (wm->paintcursors.first) {
|
|
ARegion *region = CTX_wm_region(C);
|
|
|
|
if (region) {
|
|
wm_paintcursor_tag(C, static_cast<wmPaintCursor *>(wm->paintcursors.first), region);
|
|
}
|
|
|
|
/* If previous position was not in current region, we have to set a temp new context. */
|
|
if (region == nullptr || !BLI_rcti_isect_pt_v(®ion->winrct, event->prev_xy)) {
|
|
ScrArea *area = CTX_wm_area(C);
|
|
|
|
CTX_wm_area_set(C, area_event_inside(C, event->prev_xy));
|
|
CTX_wm_region_set(C, region_event_inside(C, event->prev_xy));
|
|
|
|
wm_paintcursor_tag(
|
|
C, static_cast<wmPaintCursor *>(wm->paintcursors.first), CTX_wm_region(C));
|
|
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void wm_event_drag_and_drop_test(wmWindowManager *wm, wmWindow *win, wmEvent *event)
|
|
{
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
if (BLI_listbase_is_empty(&wm->drags)) {
|
|
return;
|
|
}
|
|
|
|
if (event->type == MOUSEMOVE || ISKEYMODIFIER(event->type)) {
|
|
screen->do_draw_drag = true;
|
|
}
|
|
else if (event->type == EVT_ESCKEY) {
|
|
wm_drags_exit(wm, win);
|
|
WM_drag_free_list(&wm->drags);
|
|
|
|
screen->do_draw_drag = true;
|
|
}
|
|
else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
|
|
event->type = EVT_DROP;
|
|
|
|
/* Create custom-data, first free existing. */
|
|
wm_event_custom_free(event);
|
|
wm_event_custom_clear(event);
|
|
|
|
event->custom = EVT_DATA_DRAGDROP;
|
|
event->customdata = &wm->drags;
|
|
event->customdata_free = true;
|
|
|
|
/* Clear drop icon. */
|
|
screen->do_draw_drag = true;
|
|
|
|
/* Restore cursor (disabled, see `wm_dragdrop.cc`) */
|
|
// WM_cursor_modal_restore(win);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Filter out all events of the pie that spawned the last pie unless it's a release event.
|
|
*/
|
|
static bool wm_event_pie_filter(wmWindow *win, const wmEvent *event)
|
|
{
|
|
if (win->pie_event_type_lock && win->pie_event_type_lock == event->type) {
|
|
if (event->val == KM_RELEASE) {
|
|
win->pie_event_type_lock = EVENT_NONE;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Account for the special case when events are being handled and a file is loaded.
|
|
* In this case event handling exits early, however when "Load UI" is disabled
|
|
* the even will still be in #wmWindow.event_queue.
|
|
*
|
|
* Without this it's possible to continuously handle the same event, see: T76484.
|
|
*/
|
|
static void wm_event_free_and_remove_from_queue_if_valid(wmEvent *event)
|
|
{
|
|
LISTBASE_FOREACH (wmWindowManager *, wm, &G_MAIN->wm) {
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
if (BLI_remlink_safe(&win->event_queue, event)) {
|
|
wm_event_free(event);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Main Event Queue (Every Window)
|
|
*
|
|
* Handle events for all windows, run from the #WM_main event loop.
|
|
* \{ */
|
|
|
|
#ifdef WITH_XR_OPENXR
|
|
/**
|
|
* Special handling for XR events.
|
|
*
|
|
* Although XR events are added to regular window queues, they are handled in an "off-screen area"
|
|
* context that is owned entirely by XR runtime data and not tied to a window.
|
|
*/
|
|
static void wm_event_handle_xrevent(bContext *C,
|
|
wmWindowManager *wm,
|
|
wmWindow *win,
|
|
wmEvent *event)
|
|
{
|
|
ScrArea *area = WM_xr_session_area_get(&wm->xr);
|
|
if (!area) {
|
|
return;
|
|
}
|
|
BLI_assert(area->spacetype == SPACE_VIEW3D && area->spacedata.first);
|
|
|
|
/* Find a valid region for XR operator execution and modal handling. */
|
|
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
|
|
if (!region) {
|
|
return;
|
|
}
|
|
BLI_assert(WM_region_use_viewport(area, region)); /* For operators using GPU-based selection. */
|
|
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
|
|
int action = wm_handlers_do(C, event, &win->modalhandlers);
|
|
|
|
if ((action & WM_HANDLER_BREAK) == 0) {
|
|
wmXrActionData *actiondata = static_cast<wmXrActionData *>(event->customdata);
|
|
if (actiondata->ot->modal && event->val == KM_RELEASE) {
|
|
/* Don't execute modal operators on release. */
|
|
}
|
|
else {
|
|
PointerRNA properties{};
|
|
properties.type = actiondata->ot->srna;
|
|
properties.data = actiondata->op_properties;
|
|
if (actiondata->ot->invoke) {
|
|
/* Invoke operator, either executing operator or transferring responsibility to window
|
|
* modal handlers. */
|
|
wm_operator_invoke(C,
|
|
actiondata->ot,
|
|
event,
|
|
actiondata->op_properties ? &properties : nullptr,
|
|
nullptr,
|
|
false,
|
|
false);
|
|
}
|
|
else {
|
|
/* Execute operator. */
|
|
wmOperator *op = wm_operator_create(
|
|
wm, actiondata->ot, actiondata->op_properties ? &properties : nullptr, nullptr);
|
|
if ((WM_operator_call(C, op) & OPERATOR_HANDLED) == 0) {
|
|
WM_operator_free(op);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CTX_wm_region_set(C, nullptr);
|
|
CTX_wm_area_set(C, nullptr);
|
|
}
|
|
#endif /* WITH_XR_OPENXR */
|
|
|
|
static int wm_event_do_region_handlers(bContext *C, wmEvent *event, ARegion *region)
|
|
{
|
|
CTX_wm_region_set(C, region);
|
|
|
|
/* Call even on non mouse events, since the. */
|
|
wm_region_mouse_co(C, event);
|
|
|
|
const wmWindowManager *wm = CTX_wm_manager(C);
|
|
if (!BLI_listbase_is_empty(&wm->drags)) {
|
|
/* Does polls for drop regions and checks #uiButs. */
|
|
/* Need to be here to make sure region context is true. */
|
|
if (ELEM(event->type, MOUSEMOVE, EVT_DROP) || ISKEYMODIFIER(event->type)) {
|
|
wm_drags_check_ops(C, event);
|
|
}
|
|
}
|
|
|
|
return wm_handlers_do(C, event, ®ion->handlers);
|
|
}
|
|
|
|
/**
|
|
* Send event to region handlers in \a area.
|
|
*
|
|
* Two cases:
|
|
* 1) Always pass events (#wm_event_always_pass()) are sent to all regions.
|
|
* 2) Event is passed to the region visually under the cursor (#ED_area_find_region_xy_visual()).
|
|
*/
|
|
static int wm_event_do_handlers_area_regions(bContext *C, wmEvent *event, ScrArea *area)
|
|
{
|
|
/* Case 1. */
|
|
if (wm_event_always_pass(event)) {
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
action |= wm_event_do_region_handlers(C, event, region);
|
|
}
|
|
|
|
wm_event_handler_return_value_check(C, event, action);
|
|
return action;
|
|
}
|
|
|
|
/* Case 2. */
|
|
ARegion *region_hovered = ED_area_find_region_xy_visual(area, RGN_TYPE_ANY, event->xy);
|
|
if (!region_hovered) {
|
|
return WM_HANDLER_CONTINUE;
|
|
}
|
|
|
|
return wm_event_do_region_handlers(C, event, region_hovered);
|
|
}
|
|
|
|
void wm_event_do_handlers(bContext *C)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
BLI_assert(ED_undo_is_state_valid(C));
|
|
|
|
/* Begin GPU render boundary - Certain event handlers require GPU usage. */
|
|
GPU_render_begin();
|
|
|
|
/* Update key configuration before handling events. */
|
|
WM_keyconfig_update(wm);
|
|
WM_gizmoconfig_update(CTX_data_main(C));
|
|
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
|
|
/* Some safety checks - these should always be set! */
|
|
BLI_assert(WM_window_get_active_scene(win));
|
|
BLI_assert(WM_window_get_active_screen(win));
|
|
BLI_assert(WM_window_get_active_workspace(win));
|
|
|
|
if (screen == nullptr) {
|
|
wm_event_free_all(win);
|
|
}
|
|
else {
|
|
Scene *scene = WM_window_get_active_scene(win);
|
|
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
|
|
Depsgraph *depsgraph = BKE_scene_get_depsgraph(scene, view_layer);
|
|
Scene *scene_eval = (depsgraph != nullptr) ? DEG_get_evaluated_scene(depsgraph) : nullptr;
|
|
|
|
if (scene_eval != nullptr) {
|
|
const int is_playing_sound = BKE_sound_scene_playing(scene_eval);
|
|
|
|
if (scene_eval->id.recalc & ID_RECALC_FRAME_CHANGE) {
|
|
/* Ignore seek here, the audio will be updated to the scene frame after jump during next
|
|
* dependency graph update. */
|
|
}
|
|
else if (is_playing_sound != -1) {
|
|
bool is_playing_screen;
|
|
|
|
is_playing_screen = (ED_screen_animation_playing(wm) != nullptr);
|
|
|
|
if (((is_playing_sound == 1) && (is_playing_screen == 0)) ||
|
|
((is_playing_sound == 0) && (is_playing_screen == 1))) {
|
|
wmWindow *win_ctx = CTX_wm_window(C);
|
|
bScreen *screen_stx = CTX_wm_screen(C);
|
|
Scene *scene_ctx = CTX_data_scene(C);
|
|
|
|
CTX_wm_window_set(C, win);
|
|
CTX_wm_screen_set(C, screen);
|
|
CTX_data_scene_set(C, scene);
|
|
|
|
ED_screen_animation_play(C, -1, 1);
|
|
|
|
CTX_data_scene_set(C, scene_ctx);
|
|
CTX_wm_screen_set(C, screen_stx);
|
|
CTX_wm_window_set(C, win_ctx);
|
|
}
|
|
|
|
if (is_playing_sound == 0) {
|
|
const double time = BKE_sound_sync_scene(scene_eval);
|
|
if (isfinite(time)) {
|
|
int ncfra = round(time * FPS);
|
|
if (ncfra != scene->r.cfra) {
|
|
scene->r.cfra = ncfra;
|
|
ED_update_for_newframe(CTX_data_main(C), depsgraph);
|
|
WM_event_add_notifier(C, NC_WINDOW, nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEvent *event;
|
|
while ((event = static_cast<wmEvent *>(win->event_queue.first))) {
|
|
int action = WM_HANDLER_CONTINUE;
|
|
|
|
/* Force handling drag if a key is pressed even if the drag threshold has not been met.
|
|
* Needed so tablet actions (which typically use a larger threshold) can click-drag
|
|
* then press keys - activating the drag action early.
|
|
* Limit to mouse-buttons drag actions interrupted by pressing any non-mouse button.
|
|
* Otherwise pressing two keys on the keyboard will interpret this as a drag action. */
|
|
if (win->event_queue_check_drag) {
|
|
if ((event->val == KM_PRESS) && ((event->flag & WM_EVENT_IS_REPEAT) == 0) &&
|
|
ISKEYBOARD_OR_BUTTON(event->type) && ISMOUSE_BUTTON(event->prev_press_type)) {
|
|
event = wm_event_add_mousemove_to_head(win);
|
|
event->flag |= WM_EVENT_FORCE_DRAG_THRESHOLD;
|
|
}
|
|
}
|
|
const bool event_queue_check_drag_prev = win->event_queue_check_drag;
|
|
|
|
/* Active screen might change during handlers, update pointer. */
|
|
screen = WM_window_get_active_screen(win);
|
|
|
|
if (G.debug & (G_DEBUG_HANDLERS | G_DEBUG_EVENTS) && !ISMOUSE_MOTION(event->type)) {
|
|
printf("\n%s: Handling event\n", __func__);
|
|
WM_event_print(event);
|
|
}
|
|
|
|
/* Take care of pie event filter. */
|
|
if (wm_event_pie_filter(win, event)) {
|
|
if (!ISMOUSE_MOTION(event->type)) {
|
|
CLOG_INFO(WM_LOG_HANDLERS, 1, "event filtered due to pie button pressed");
|
|
}
|
|
BLI_remlink(&win->event_queue, event);
|
|
wm_event_free_last_handled(win, event);
|
|
continue;
|
|
}
|
|
|
|
CTX_wm_window_set(C, win);
|
|
|
|
#ifdef WITH_XR_OPENXR
|
|
if (event->type == EVT_XR_ACTION) {
|
|
wm_event_handle_xrevent(C, wm, win, event);
|
|
BLI_remlink(&win->event_queue, event);
|
|
wm_event_free_last_handled(win, event);
|
|
/* Skip mouse event handling below, which is unnecessary for XR events. */
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
/* Clear tool-tip on mouse move. */
|
|
if (screen->tool_tip && screen->tool_tip->exit_on_event) {
|
|
if (ISMOUSE_MOTION(event->type)) {
|
|
if (len_manhattan_v2v2_int(screen->tool_tip->event_xy, event->xy) >
|
|
WM_EVENT_CURSOR_MOTION_THRESHOLD) {
|
|
WM_tooltip_clear(C, win);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We let modal handlers get active area/region, also wm_paintcursor_test needs it. */
|
|
CTX_wm_area_set(C, area_event_inside(C, event->xy));
|
|
CTX_wm_region_set(C, region_event_inside(C, event->xy));
|
|
|
|
/* MVC demands to not draw in event handlers...
|
|
* but we need to leave it for GPU selecting etc. */
|
|
wm_window_make_drawable(wm, win);
|
|
|
|
wm_region_mouse_co(C, event);
|
|
|
|
/* First we do priority handlers, modal + some limited key-maps. */
|
|
action |= wm_handlers_do(C, event, &win->modalhandlers);
|
|
|
|
/* File-read case. */
|
|
if (CTX_wm_window(C) == nullptr) {
|
|
wm_event_free_and_remove_from_queue_if_valid(event);
|
|
GPU_render_end();
|
|
return;
|
|
}
|
|
|
|
/* Check for a tool-tip. */
|
|
if (screen == WM_window_get_active_screen(win)) {
|
|
if (screen->tool_tip && screen->tool_tip->timer) {
|
|
if ((event->type == TIMER) && (event->customdata == screen->tool_tip->timer)) {
|
|
WM_tooltip_init(C, win);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check dragging, creates new event or frees, adds draw tag. */
|
|
wm_event_drag_and_drop_test(wm, win, event);
|
|
|
|
if ((action & WM_HANDLER_BREAK) == 0) {
|
|
/* NOTE: setting sub-window active should be done here,
|
|
* after modal handlers have been done. */
|
|
if (event->type == MOUSEMOVE) {
|
|
/* State variables in screen, cursors.
|
|
* Also used in `wm_draw.c`, fails for modal handlers though. */
|
|
ED_screen_set_active_region(C, win, event->xy);
|
|
/* For regions having custom cursors. */
|
|
wm_paintcursor_test(C, event);
|
|
}
|
|
#ifdef WITH_INPUT_NDOF
|
|
else if (event->type == NDOF_MOTION) {
|
|
win->addmousemove = true;
|
|
}
|
|
#endif
|
|
|
|
ED_screen_areas_iter (win, screen, area) {
|
|
/* After restoring a screen from SCREENMAXIMIZED we have to wait
|
|
* with the screen handling till the region coordinates are updated. */
|
|
if (screen->skip_handling) {
|
|
/* Restore for the next iteration of wm_event_do_handlers. */
|
|
screen->skip_handling = false;
|
|
break;
|
|
}
|
|
|
|
/* Update action-zones if needed,
|
|
* done here because it needs to be independent from redraws. */
|
|
if (area->flag & AREA_FLAG_ACTIONZONES_UPDATE) {
|
|
ED_area_azones_update(area, event->xy);
|
|
}
|
|
|
|
if (wm_event_inside_rect(event, &area->totrct)) {
|
|
CTX_wm_area_set(C, area);
|
|
|
|
action |= wm_event_do_handlers_area_regions(C, event, area);
|
|
|
|
/* File-read case (Python), T29489. */
|
|
if (CTX_wm_window(C) == nullptr) {
|
|
wm_event_free_and_remove_from_queue_if_valid(event);
|
|
GPU_render_end();
|
|
return;
|
|
}
|
|
|
|
CTX_wm_region_set(C, nullptr);
|
|
|
|
if ((action & WM_HANDLER_BREAK) == 0) {
|
|
wm_region_mouse_co(C, event); /* Only invalidates `event->mval` in this case. */
|
|
action |= wm_handlers_do(C, event, &area->handlers);
|
|
}
|
|
CTX_wm_area_set(C, nullptr);
|
|
|
|
/* NOTE: do not escape on #WM_HANDLER_BREAK,
|
|
* mouse-move needs handled for previous area. */
|
|
}
|
|
}
|
|
|
|
if ((action & WM_HANDLER_BREAK) == 0) {
|
|
/* Also some non-modal handlers need active area/region. */
|
|
CTX_wm_area_set(C, area_event_inside(C, event->xy));
|
|
CTX_wm_region_set(C, region_event_inside(C, event->xy));
|
|
|
|
wm_region_mouse_co(C, event);
|
|
|
|
action |= wm_handlers_do(C, event, &win->handlers);
|
|
|
|
/* File-read case. */
|
|
if (CTX_wm_window(C) == nullptr) {
|
|
wm_event_free_and_remove_from_queue_if_valid(event);
|
|
GPU_render_end();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If press was handled, we don't want to do click. This way
|
|
* press in tool key-map can override click in editor key-map. */
|
|
if (ISMOUSE_BUTTON(event->type) && event->val == KM_PRESS &&
|
|
!wm_action_not_handled(action)) {
|
|
win->event_queue_check_click = false;
|
|
}
|
|
|
|
/* If the drag even was handled, don't attempt to keep re-handing the same
|
|
* drag event on every cursor motion, see: T87511. */
|
|
if (win->event_queue_check_drag_handled) {
|
|
win->event_queue_check_drag = false;
|
|
win->event_queue_check_drag_handled = false;
|
|
}
|
|
|
|
if (event_queue_check_drag_prev && (win->event_queue_check_drag == false)) {
|
|
wm_region_tag_draw_on_gizmo_delay_refresh_for_tweak(win);
|
|
}
|
|
|
|
/* Update previous mouse position for following events to use. */
|
|
copy_v2_v2_int(win->eventstate->prev_xy, event->xy);
|
|
|
|
/* Un-link and free here, Blender-quit then frees all. */
|
|
BLI_remlink(&win->event_queue, event);
|
|
wm_event_free_last_handled(win, event);
|
|
}
|
|
|
|
/* Only add mouse-move when the event queue was read entirely. */
|
|
if (win->addmousemove && win->eventstate) {
|
|
wmEvent tevent = *(win->eventstate);
|
|
// printf("adding MOUSEMOVE %d %d\n", tevent.xy[0], tevent.xy[1]);
|
|
tevent.type = MOUSEMOVE;
|
|
tevent.val = KM_NOTHING;
|
|
tevent.prev_xy[0] = tevent.xy[0];
|
|
tevent.prev_xy[1] = tevent.xy[1];
|
|
tevent.flag = (eWM_EventFlag)0;
|
|
wm_event_add(win, &tevent);
|
|
win->addmousemove = 0;
|
|
}
|
|
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
/* Update key configuration after handling events. */
|
|
WM_keyconfig_update(wm);
|
|
WM_gizmoconfig_update(CTX_data_main(C));
|
|
|
|
/* End GPU render boundary. Certain event handlers require GPU usage. */
|
|
GPU_render_end();
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name File Selector Handling
|
|
* \{ */
|
|
|
|
void WM_event_fileselect_event(wmWindowManager *wm, void *ophandle, int eventval)
|
|
{
|
|
/* Add to all windows! */
|
|
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
|
|
wmEvent event = *win->eventstate;
|
|
|
|
event.type = EVT_FILESELECT;
|
|
event.val = eventval;
|
|
event.flag = (eWM_EventFlag)0;
|
|
event.customdata = ophandle; /* Only as void pointer type check. */
|
|
|
|
wm_event_add(win, &event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* From the context window, try to find a window that is appropriate for use as root window of a
|
|
* modal File Browser (modal means: there is a #SpaceFile.op to execute). The root window will
|
|
* become the parent of the File Browser and provides a context to execute the file operator in,
|
|
* even after closing the File Browser.
|
|
*
|
|
* An appropriate window is either of the following:
|
|
* * A parent window that does not yet contain a modal File Browser. This is determined using
|
|
* #ED_fileselect_handler_area_find_any_with_op().
|
|
* * A parent window containing a modal File Browser, but in a maximized/full-screen state. Users
|
|
* shouldn't be able to put a temporary screen like the modal File Browser into
|
|
* maximized/full-screen state themselves. So this setup indicates that the File Browser was
|
|
* opened using #USER_TEMP_SPACE_DISPLAY_FULLSCREEN.
|
|
*
|
|
* If no appropriate parent window can be found from the context window, return the first
|
|
* registered window (which can be assumed to be a regular window, e.g. no modal File Browser; this
|
|
* is asserted).
|
|
*/
|
|
static wmWindow *wm_event_find_fileselect_root_window_from_context(const bContext *C)
|
|
{
|
|
wmWindow *ctx_win = CTX_wm_window(C);
|
|
|
|
for (wmWindow *ctx_win_or_parent = ctx_win; ctx_win_or_parent;
|
|
ctx_win_or_parent = ctx_win_or_parent->parent) {
|
|
ScrArea *file_area = ED_fileselect_handler_area_find_any_with_op(ctx_win_or_parent);
|
|
|
|
if (!file_area) {
|
|
return ctx_win_or_parent;
|
|
}
|
|
|
|
if (file_area->full) {
|
|
return ctx_win_or_parent;
|
|
}
|
|
}
|
|
|
|
/* Fallback to the first window. */
|
|
const wmWindowManager *wm = CTX_wm_manager(C);
|
|
BLI_assert(!ED_fileselect_handler_area_find_any_with_op(
|
|
static_cast<const wmWindow *>(wm->windows.first)));
|
|
return static_cast<wmWindow *>(wm->windows.first);
|
|
}
|
|
|
|
/* Operator is supposed to have a filled "path" property. */
|
|
/* Optional property: file-type (XXX enum?) */
|
|
|
|
void WM_event_add_fileselect(bContext *C, wmOperator *op)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmWindow *ctx_win = CTX_wm_window(C);
|
|
|
|
/* The following vars define the root context. That is essentially the "parent" context of the
|
|
* File Browser operation, to be restored for eventually executing the file operation. */
|
|
wmWindow *root_win = wm_event_find_fileselect_root_window_from_context(C);
|
|
/* Determined later. */
|
|
ScrArea *root_area = nullptr;
|
|
ARegion *root_region = nullptr;
|
|
|
|
/* Close any popups, like when opening a file browser from the splash. */
|
|
UI_popup_handlers_remove_all(C, &root_win->modalhandlers);
|
|
|
|
/* Setting the context window unsets the context area & screen. Avoid doing that, so operators
|
|
* calling the file browser can operate in the context the browser was opened in. */
|
|
if (ctx_win != root_win) {
|
|
CTX_wm_window_set(C, root_win);
|
|
}
|
|
|
|
/* The root window may already have a File Browser open. Cancel it if so, only 1 should be open
|
|
* per window. The root context of this operation is also used for the new operation. */
|
|
LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, &root_win->modalhandlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
if (handler->is_fileselect == false) {
|
|
continue;
|
|
}
|
|
|
|
wm_handler_op_context_get_if_valid(
|
|
C, handler, ctx_win->eventstate, &root_area, &root_region);
|
|
|
|
ScrArea *file_area = ED_fileselect_handler_area_find(root_win, handler->op);
|
|
|
|
if (file_area) {
|
|
CTX_wm_area_set(C, file_area);
|
|
wm_handler_fileselect_do(C, &root_win->modalhandlers, handler, EVT_FILESELECT_CANCEL);
|
|
}
|
|
/* If not found we stop the handler without changing the screen. */
|
|
else {
|
|
wm_handler_fileselect_do(
|
|
C, &root_win->modalhandlers, handler, EVT_FILESELECT_EXTERNAL_CANCEL);
|
|
}
|
|
}
|
|
}
|
|
|
|
BLI_assert(root_win != nullptr);
|
|
/* When not reusing the root context from a previous file browsing operation, use the current
|
|
* area & region, if they are inside the root window. */
|
|
if (!root_area && ctx_win == root_win) {
|
|
root_area = CTX_wm_area(C);
|
|
root_region = CTX_wm_region(C);
|
|
}
|
|
|
|
wmEventHandler_Op *handler = MEM_cnew<wmEventHandler_Op>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_OP;
|
|
|
|
handler->is_fileselect = true;
|
|
handler->op = op;
|
|
handler->context.win = root_win;
|
|
handler->context.area = root_area;
|
|
handler->context.region = root_region;
|
|
|
|
BLI_addhead(&root_win->modalhandlers, handler);
|
|
|
|
/* Check props once before invoking if check is available
|
|
* ensures initial properties are valid. */
|
|
if (op->type->check) {
|
|
op->type->check(C, op); /* Ignore return value. */
|
|
}
|
|
|
|
WM_event_fileselect_event(wm, op, EVT_FILESELECT_FULL_OPEN);
|
|
|
|
if (ctx_win != root_win) {
|
|
CTX_wm_window_set(C, ctx_win);
|
|
}
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Modal Operator Handling
|
|
* \{ */
|
|
|
|
#if 0
|
|
/* Lets not expose struct outside wm? */
|
|
static void WM_event_set_handler_flag(wmEventHandler *handler, int flag)
|
|
{
|
|
handler->flag = flag;
|
|
}
|
|
#endif
|
|
|
|
wmEventHandler_Op *WM_event_add_modal_handler(bContext *C, wmOperator *op)
|
|
{
|
|
wmEventHandler_Op *handler = MEM_cnew<wmEventHandler_Op>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_OP;
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
/* Operator was part of macro. */
|
|
if (op->opm) {
|
|
/* Give the mother macro to the handler. */
|
|
handler->op = op->opm;
|
|
/* Mother macro `opm` becomes the macro element. */
|
|
handler->op->opm = op;
|
|
}
|
|
else {
|
|
handler->op = op;
|
|
}
|
|
|
|
handler->context.area = CTX_wm_area(C); /* Means frozen screen context for modal handlers! */
|
|
handler->context.region = CTX_wm_region(C);
|
|
handler->context.region_type = handler->context.region ? handler->context.region->regiontype :
|
|
-1;
|
|
|
|
BLI_addhead(&win->modalhandlers, handler);
|
|
|
|
if (op->type->modalkeymap) {
|
|
WM_window_status_area_tag_redraw(win);
|
|
}
|
|
|
|
return handler;
|
|
}
|
|
|
|
void WM_event_modal_handler_area_replace(wmWindow *win, const ScrArea *old_area, ScrArea *new_area)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
/* File-select handler is quite special.
|
|
* it needs to keep old area stored in handler, so don't change it. */
|
|
if ((handler->context.area == old_area) && (handler->is_fileselect == false)) {
|
|
handler->context.area = new_area;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WM_event_modal_handler_region_replace(wmWindow *win,
|
|
const ARegion *old_region,
|
|
ARegion *new_region)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
/* File-select handler is quite special.
|
|
* it needs to keep old region stored in handler, so don't change it. */
|
|
if ((handler->context.region == old_region) && (handler->is_fileselect == false)) {
|
|
handler->context.region = new_region;
|
|
handler->context.region_type = new_region ? new_region->regiontype : int(RGN_TYPE_WINDOW);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEventHandler_Keymap *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap *keymap)
|
|
{
|
|
if (!keymap) {
|
|
CLOG_WARN(WM_LOG_HANDLERS, "called with nullptr key-map");
|
|
return nullptr;
|
|
}
|
|
|
|
/* Only allow same key-map once. */
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
|
if (handler->keymap == keymap) {
|
|
return handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEventHandler_Keymap *handler = MEM_cnew<wmEventHandler_Keymap>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_KEYMAP;
|
|
BLI_addtail(handlers, handler);
|
|
handler->keymap = keymap;
|
|
|
|
return handler;
|
|
}
|
|
|
|
/**
|
|
* Implements fallback tool when enabled by:
|
|
* #SCE_WORKSPACE_TOOL_FALLBACK, #WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP.
|
|
*
|
|
* This runs before #WM_event_get_keymap_from_toolsystem,
|
|
* allowing both the fallback-tool and active-tool to be activated
|
|
* providing the key-map is configured so the keys don't conflict.
|
|
* For example one mouse button can run the active-tool, another button for the fallback-tool.
|
|
* See T72567.
|
|
*
|
|
* Follow #wmEventHandler_KeymapDynamicFn signature.
|
|
*/
|
|
static void wm_event_get_keymap_from_toolsystem_ex(wmWindowManager *wm,
|
|
wmWindow *win,
|
|
wmEventHandler_Keymap *handler,
|
|
wmEventHandler_KeymapResult *km_result,
|
|
/* Extra arguments. */
|
|
const bool with_gizmos)
|
|
{
|
|
memset(km_result, 0x0, sizeof(*km_result));
|
|
|
|
const char *keymap_id_list[ARRAY_SIZE(km_result->keymaps)];
|
|
int keymap_id_list_len = 0;
|
|
|
|
/* NOTE(@campbellbarton): If `win` is nullptr, this function may not behave as expected.
|
|
* Assert since this should not happen in practice.
|
|
* If it does, the window could be looked up in `wm` using the `area`.
|
|
* Keep nullptr checks in run-time code since any crashes here are difficult to redo. */
|
|
BLI_assert_msg(win != nullptr, "The window should always be set for tool interactions!");
|
|
const Scene *scene = win ? win->scene : nullptr;
|
|
|
|
ScrArea *area = static_cast<ScrArea *>(handler->dynamic.user_data);
|
|
handler->keymap_tool = nullptr;
|
|
bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : nullptr;
|
|
|
|
if (tref_rt && tref_rt->keymap[0]) {
|
|
keymap_id_list[keymap_id_list_len++] = tref_rt->keymap;
|
|
}
|
|
|
|
bool is_gizmo_visible = false;
|
|
bool is_gizmo_highlight = false;
|
|
|
|
if ((tref_rt && tref_rt->keymap_fallback[0]) &&
|
|
(scene && (scene->toolsettings->workspace_tool_type == SCE_WORKSPACE_TOOL_FALLBACK))) {
|
|
bool add_keymap = false;
|
|
/* Support for the gizmo owning the tool key-map. */
|
|
|
|
if (tref_rt->flag & TOOLREF_FLAG_FALLBACK_KEYMAP) {
|
|
add_keymap = true;
|
|
}
|
|
|
|
if (with_gizmos && (tref_rt->gizmo_group[0] != '\0')) {
|
|
wmGizmoMap *gzmap = nullptr;
|
|
wmGizmoGroup *gzgroup = nullptr;
|
|
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
|
|
if (region->gizmo_map != nullptr) {
|
|
gzmap = region->gizmo_map;
|
|
gzgroup = WM_gizmomap_group_find(gzmap, tref_rt->gizmo_group);
|
|
if (gzgroup != nullptr) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (gzgroup != nullptr) {
|
|
if (gzgroup->type->flag & WM_GIZMOGROUPTYPE_TOOL_FALLBACK_KEYMAP) {
|
|
/* If all are hidden, don't override. */
|
|
is_gizmo_visible = true;
|
|
wmGizmo *highlight = wm_gizmomap_highlight_get(gzmap);
|
|
if (highlight) {
|
|
is_gizmo_highlight = true;
|
|
}
|
|
add_keymap = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (add_keymap) {
|
|
keymap_id_list[keymap_id_list_len++] = tref_rt->keymap_fallback;
|
|
}
|
|
}
|
|
|
|
if (is_gizmo_visible && !is_gizmo_highlight) {
|
|
if (keymap_id_list_len == 2) {
|
|
SWAP(const char *, keymap_id_list[0], keymap_id_list[1]);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < keymap_id_list_len; i++) {
|
|
const char *keymap_id = keymap_id_list[i];
|
|
BLI_assert(keymap_id && keymap_id[0]);
|
|
|
|
wmKeyMap *km = WM_keymap_list_find_spaceid_or_empty(
|
|
&wm->userconf->keymaps, keymap_id, area->spacetype, RGN_TYPE_WINDOW);
|
|
/* We shouldn't use key-maps from unrelated spaces. */
|
|
if (km == nullptr) {
|
|
printf("Key-map: '%s' not found for tool '%s'\n", keymap_id, area->runtime.tool->idname);
|
|
continue;
|
|
}
|
|
handler->keymap_tool = area->runtime.tool;
|
|
km_result->keymaps[km_result->keymaps_len++] = km;
|
|
}
|
|
}
|
|
|
|
void WM_event_get_keymap_from_toolsystem_with_gizmos(wmWindowManager *wm,
|
|
wmWindow *win,
|
|
wmEventHandler_Keymap *handler,
|
|
wmEventHandler_KeymapResult *km_result)
|
|
{
|
|
wm_event_get_keymap_from_toolsystem_ex(wm, win, handler, km_result, true);
|
|
}
|
|
|
|
void WM_event_get_keymap_from_toolsystem(wmWindowManager *wm,
|
|
wmWindow *win,
|
|
wmEventHandler_Keymap *handler,
|
|
wmEventHandler_KeymapResult *km_result)
|
|
{
|
|
wm_event_get_keymap_from_toolsystem_ex(wm, win, handler, km_result, false);
|
|
}
|
|
|
|
wmEventHandler_Keymap *WM_event_add_keymap_handler_dynamic(
|
|
ListBase *handlers, wmEventHandler_KeymapDynamicFn *keymap_fn, void *user_data)
|
|
{
|
|
if (!keymap_fn) {
|
|
CLOG_WARN(WM_LOG_HANDLERS, "called with nullptr keymap_fn");
|
|
return nullptr;
|
|
}
|
|
|
|
/* Only allow same key-map once. */
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
|
if (handler->dynamic.keymap_fn == keymap_fn) {
|
|
/* Maximizing the view needs to update the area. */
|
|
handler->dynamic.user_data = user_data;
|
|
return handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEventHandler_Keymap *handler = MEM_cnew<wmEventHandler_Keymap>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_KEYMAP;
|
|
BLI_addtail(handlers, handler);
|
|
handler->dynamic.keymap_fn = keymap_fn;
|
|
handler->dynamic.user_data = user_data;
|
|
|
|
return handler;
|
|
}
|
|
|
|
wmEventHandler_Keymap *WM_event_add_keymap_handler_priority(ListBase *handlers,
|
|
wmKeyMap *keymap,
|
|
int /*priority*/)
|
|
{
|
|
WM_event_remove_keymap_handler(handlers, keymap);
|
|
|
|
wmEventHandler_Keymap *handler = MEM_cnew<wmEventHandler_Keymap>("event key-map handler");
|
|
handler->head.type = WM_HANDLER_TYPE_KEYMAP;
|
|
|
|
BLI_addhead(handlers, handler);
|
|
handler->keymap = keymap;
|
|
|
|
return handler;
|
|
}
|
|
|
|
static bool event_or_prev_in_rect(const wmEvent *event, const rcti *rect)
|
|
{
|
|
if (BLI_rcti_isect_pt_v(rect, event->xy)) {
|
|
return true;
|
|
}
|
|
if (event->type == MOUSEMOVE && BLI_rcti_isect_pt_v(rect, event->prev_xy)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool handler_region_v2d_mask_test(const ARegion *region, const wmEvent *event)
|
|
{
|
|
rcti rect = region->v2d.mask;
|
|
BLI_rcti_translate(&rect, region->winrct.xmin, region->winrct.ymin);
|
|
return event_or_prev_in_rect(event, &rect);
|
|
}
|
|
|
|
wmEventHandler_Keymap *WM_event_add_keymap_handler_poll(ListBase *handlers,
|
|
wmKeyMap *keymap,
|
|
EventHandlerPoll poll)
|
|
{
|
|
wmEventHandler_Keymap *handler = WM_event_add_keymap_handler(handlers, keymap);
|
|
if (handler == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
handler->head.poll = poll;
|
|
return handler;
|
|
}
|
|
|
|
wmEventHandler_Keymap *WM_event_add_keymap_handler_v2d_mask(ListBase *handlers, wmKeyMap *keymap)
|
|
{
|
|
return WM_event_add_keymap_handler_poll(handlers, keymap, handler_region_v2d_mask_test);
|
|
}
|
|
|
|
void WM_event_remove_keymap_handler(ListBase *handlers, wmKeyMap *keymap)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
|
if (handler->keymap == keymap) {
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(&handler->head);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WM_event_set_keymap_handler_post_callback(wmEventHandler_Keymap *handler,
|
|
void(keymap_tag)(wmKeyMap *keymap,
|
|
wmKeyMapItem *kmi,
|
|
void *user_data),
|
|
void *user_data)
|
|
{
|
|
handler->post.post_fn = keymap_tag;
|
|
handler->post.user_data = user_data;
|
|
}
|
|
|
|
wmEventHandler_UI *WM_event_add_ui_handler(const bContext *C,
|
|
ListBase *handlers,
|
|
wmUIHandlerFunc handle_fn,
|
|
wmUIHandlerRemoveFunc remove_fn,
|
|
void *user_data,
|
|
const eWM_EventHandlerFlag flag)
|
|
{
|
|
wmEventHandler_UI *handler = MEM_cnew<wmEventHandler_UI>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_UI;
|
|
handler->handle_fn = handle_fn;
|
|
handler->remove_fn = remove_fn;
|
|
handler->user_data = user_data;
|
|
if (C) {
|
|
handler->context.area = CTX_wm_area(C);
|
|
handler->context.region = CTX_wm_region(C);
|
|
handler->context.menu = CTX_wm_menu(C);
|
|
}
|
|
else {
|
|
handler->context.area = nullptr;
|
|
handler->context.region = nullptr;
|
|
handler->context.menu = nullptr;
|
|
}
|
|
|
|
BLI_assert((flag & WM_HANDLER_DO_FREE) == 0);
|
|
handler->head.flag = flag;
|
|
|
|
BLI_addhead(handlers, handler);
|
|
|
|
return handler;
|
|
}
|
|
|
|
void WM_event_remove_ui_handler(ListBase *handlers,
|
|
wmUIHandlerFunc handle_fn,
|
|
wmUIHandlerRemoveFunc remove_fn,
|
|
void *user_data,
|
|
const bool postpone)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
if ((handler->handle_fn == handle_fn) && (handler->remove_fn == remove_fn) &&
|
|
(handler->user_data == user_data)) {
|
|
/* Handlers will be freed in #wm_handlers_do(). */
|
|
if (postpone) {
|
|
handler->head.flag |= WM_HANDLER_DO_FREE;
|
|
}
|
|
else {
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(&handler->head);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void WM_event_free_ui_handler_all(bContext *C,
|
|
ListBase *handlers,
|
|
wmUIHandlerFunc handle_fn,
|
|
wmUIHandlerRemoveFunc remove_fn)
|
|
{
|
|
LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
if ((handler->handle_fn == handle_fn) && (handler->remove_fn == remove_fn)) {
|
|
remove_fn(C, handler->user_data);
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(&handler->head);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEventHandler_Dropbox *WM_event_add_dropbox_handler(ListBase *handlers, ListBase *dropboxes)
|
|
{
|
|
/* Only allow same dropbox once. */
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_DROPBOX) {
|
|
wmEventHandler_Dropbox *handler = (wmEventHandler_Dropbox *)handler_base;
|
|
if (handler->dropboxes == dropboxes) {
|
|
return handler;
|
|
}
|
|
}
|
|
}
|
|
|
|
wmEventHandler_Dropbox *handler = MEM_cnew<wmEventHandler_Dropbox>(__func__);
|
|
handler->head.type = WM_HANDLER_TYPE_DROPBOX;
|
|
|
|
/* Dropbox stored static, no free or copy. */
|
|
handler->dropboxes = dropboxes;
|
|
BLI_addhead(handlers, handler);
|
|
|
|
return handler;
|
|
}
|
|
|
|
void WM_event_remove_area_handler(ListBase *handlers, void *area)
|
|
{
|
|
/* XXX(@ton): solution works, still better check the real cause. */
|
|
|
|
LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, handlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_UI) {
|
|
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
|
|
if (handler->context.area == area) {
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(handler_base);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
wmOperator *WM_operator_find_modal_by_type(wmWindow *win, const wmOperatorType *ot)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
|
|
if (handler_base->type != WM_HANDLER_TYPE_OP) {
|
|
continue;
|
|
}
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
if (handler->op && handler->op->type == ot) {
|
|
return handler->op;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
#if 0
|
|
static void WM_event_remove_handler(ListBase *handlers, wmEventHandler *handler)
|
|
{
|
|
BLI_remlink(handlers, handler);
|
|
wm_event_free_handler(handler);
|
|
}
|
|
#endif
|
|
|
|
void WM_event_add_mousemove(wmWindow *win)
|
|
{
|
|
win->addmousemove = 1;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Ghost Event Conversion
|
|
* \{ */
|
|
|
|
/**
|
|
* \return The WM enum for key or #EVENT_NONE (which should be ignored).
|
|
*/
|
|
static int convert_key(GHOST_TKey key)
|
|
{
|
|
if (key >= GHOST_kKeyA && key <= GHOST_kKeyZ) {
|
|
return (EVT_AKEY + (int(key) - GHOST_kKeyA));
|
|
}
|
|
if (key >= GHOST_kKey0 && key <= GHOST_kKey9) {
|
|
return (EVT_ZEROKEY + (int(key) - GHOST_kKey0));
|
|
}
|
|
if (key >= GHOST_kKeyNumpad0 && key <= GHOST_kKeyNumpad9) {
|
|
return (EVT_PAD0 + (int(key) - GHOST_kKeyNumpad0));
|
|
}
|
|
if (key >= GHOST_kKeyF1 && key <= GHOST_kKeyF24) {
|
|
return (EVT_F1KEY + (int(key) - GHOST_kKeyF1));
|
|
}
|
|
|
|
switch (key) {
|
|
case GHOST_kKeyBackSpace:
|
|
return EVT_BACKSPACEKEY;
|
|
case GHOST_kKeyTab:
|
|
return EVT_TABKEY;
|
|
case GHOST_kKeyLinefeed:
|
|
return EVT_LINEFEEDKEY;
|
|
case GHOST_kKeyClear:
|
|
return EVENT_NONE;
|
|
case GHOST_kKeyEnter:
|
|
return EVT_RETKEY;
|
|
|
|
case GHOST_kKeyEsc:
|
|
return EVT_ESCKEY;
|
|
case GHOST_kKeySpace:
|
|
return EVT_SPACEKEY;
|
|
case GHOST_kKeyQuote:
|
|
return EVT_QUOTEKEY;
|
|
case GHOST_kKeyComma:
|
|
return EVT_COMMAKEY;
|
|
case GHOST_kKeyMinus:
|
|
return EVT_MINUSKEY;
|
|
case GHOST_kKeyPlus:
|
|
return EVT_PLUSKEY;
|
|
case GHOST_kKeyPeriod:
|
|
return EVT_PERIODKEY;
|
|
case GHOST_kKeySlash:
|
|
return EVT_SLASHKEY;
|
|
|
|
case GHOST_kKeySemicolon:
|
|
return EVT_SEMICOLONKEY;
|
|
case GHOST_kKeyEqual:
|
|
return EVT_EQUALKEY;
|
|
|
|
case GHOST_kKeyLeftBracket:
|
|
return EVT_LEFTBRACKETKEY;
|
|
case GHOST_kKeyRightBracket:
|
|
return EVT_RIGHTBRACKETKEY;
|
|
case GHOST_kKeyBackslash:
|
|
return EVT_BACKSLASHKEY;
|
|
case GHOST_kKeyAccentGrave:
|
|
return EVT_ACCENTGRAVEKEY;
|
|
|
|
case GHOST_kKeyLeftShift:
|
|
return EVT_LEFTSHIFTKEY;
|
|
case GHOST_kKeyRightShift:
|
|
return EVT_RIGHTSHIFTKEY;
|
|
case GHOST_kKeyLeftControl:
|
|
return EVT_LEFTCTRLKEY;
|
|
case GHOST_kKeyRightControl:
|
|
return EVT_RIGHTCTRLKEY;
|
|
case GHOST_kKeyLeftOS:
|
|
case GHOST_kKeyRightOS:
|
|
return EVT_OSKEY;
|
|
case GHOST_kKeyLeftAlt:
|
|
return EVT_LEFTALTKEY;
|
|
case GHOST_kKeyRightAlt:
|
|
return EVT_RIGHTALTKEY;
|
|
case GHOST_kKeyApp:
|
|
return EVT_APPKEY;
|
|
|
|
case GHOST_kKeyCapsLock:
|
|
return EVT_CAPSLOCKKEY;
|
|
case GHOST_kKeyNumLock:
|
|
return EVENT_NONE;
|
|
case GHOST_kKeyScrollLock:
|
|
return EVENT_NONE;
|
|
|
|
case GHOST_kKeyLeftArrow:
|
|
return EVT_LEFTARROWKEY;
|
|
case GHOST_kKeyRightArrow:
|
|
return EVT_RIGHTARROWKEY;
|
|
case GHOST_kKeyUpArrow:
|
|
return EVT_UPARROWKEY;
|
|
case GHOST_kKeyDownArrow:
|
|
return EVT_DOWNARROWKEY;
|
|
|
|
case GHOST_kKeyPrintScreen:
|
|
return EVENT_NONE;
|
|
case GHOST_kKeyPause:
|
|
return EVT_PAUSEKEY;
|
|
|
|
case GHOST_kKeyInsert:
|
|
return EVT_INSERTKEY;
|
|
case GHOST_kKeyDelete:
|
|
return EVT_DELKEY;
|
|
case GHOST_kKeyHome:
|
|
return EVT_HOMEKEY;
|
|
case GHOST_kKeyEnd:
|
|
return EVT_ENDKEY;
|
|
case GHOST_kKeyUpPage:
|
|
return EVT_PAGEUPKEY;
|
|
case GHOST_kKeyDownPage:
|
|
return EVT_PAGEDOWNKEY;
|
|
|
|
case GHOST_kKeyNumpadPeriod:
|
|
return EVT_PADPERIOD;
|
|
case GHOST_kKeyNumpadEnter:
|
|
return EVT_PADENTER;
|
|
case GHOST_kKeyNumpadPlus:
|
|
return EVT_PADPLUSKEY;
|
|
case GHOST_kKeyNumpadMinus:
|
|
return EVT_PADMINUS;
|
|
case GHOST_kKeyNumpadAsterisk:
|
|
return EVT_PADASTERKEY;
|
|
case GHOST_kKeyNumpadSlash:
|
|
return EVT_PADSLASHKEY;
|
|
|
|
case GHOST_kKeyGrLess:
|
|
return EVT_GRLESSKEY;
|
|
|
|
case GHOST_kKeyMediaPlay:
|
|
return EVT_MEDIAPLAY;
|
|
case GHOST_kKeyMediaStop:
|
|
return EVT_MEDIASTOP;
|
|
case GHOST_kKeyMediaFirst:
|
|
return EVT_MEDIAFIRST;
|
|
case GHOST_kKeyMediaLast:
|
|
return EVT_MEDIALAST;
|
|
|
|
case GHOST_kKeyUnknown:
|
|
return EVT_UNKNOWNKEY;
|
|
|
|
#if defined(__GNUC__) || defined(__clang__)
|
|
/* Ensure all members of this enum are handled, otherwise generate a compiler warning.
|
|
* Note that these members have been handled, these ranges are to satisfy the compiler. */
|
|
case GHOST_kKeyF1 ... GHOST_kKeyF24:
|
|
case GHOST_kKeyA ... GHOST_kKeyZ:
|
|
case GHOST_kKeyNumpad0 ... GHOST_kKeyNumpad9:
|
|
case GHOST_kKey0 ... GHOST_kKey9: {
|
|
BLI_assert_unreachable();
|
|
break;
|
|
}
|
|
#else
|
|
default: {
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
CLOG_WARN(WM_LOG_EVENTS, "unknown event type %d from ghost", int(key));
|
|
return EVENT_NONE;
|
|
}
|
|
|
|
static void wm_eventemulation(wmEvent *event, bool test_only)
|
|
{
|
|
/* Store last middle-mouse event value to make emulation work
|
|
* when modifier keys are released first.
|
|
* This really should be in a data structure somewhere. */
|
|
static int emulating_event = EVENT_NONE;
|
|
|
|
/* Middle-mouse emulation. */
|
|
if (U.flag & USER_TWOBUTTONMOUSE) {
|
|
|
|
if (event->type == LEFTMOUSE) {
|
|
const uint8_t mod_test = (
|
|
#if !defined(WIN32)
|
|
(U.mouse_emulate_3_button_modifier == USER_EMU_MMB_MOD_OSKEY) ? KM_OSKEY : KM_ALT
|
|
#else
|
|
/* Disable for WIN32 for now because it accesses the start menu. */
|
|
KM_ALT
|
|
#endif
|
|
);
|
|
|
|
if (event->val == KM_PRESS) {
|
|
if (event->modifier & mod_test) {
|
|
event->modifier &= ~mod_test;
|
|
event->type = MIDDLEMOUSE;
|
|
|
|
if (!test_only) {
|
|
emulating_event = MIDDLEMOUSE;
|
|
}
|
|
}
|
|
}
|
|
else if (event->val == KM_RELEASE) {
|
|
/* Only send middle-mouse release if emulated. */
|
|
if (emulating_event == MIDDLEMOUSE) {
|
|
event->type = MIDDLEMOUSE;
|
|
event->modifier &= ~mod_test;
|
|
}
|
|
|
|
if (!test_only) {
|
|
emulating_event = EVENT_NONE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Numeric-pad emulation. */
|
|
if (U.flag & USER_NONUMPAD) {
|
|
switch (event->type) {
|
|
case EVT_ZEROKEY:
|
|
event->type = EVT_PAD0;
|
|
break;
|
|
case EVT_ONEKEY:
|
|
event->type = EVT_PAD1;
|
|
break;
|
|
case EVT_TWOKEY:
|
|
event->type = EVT_PAD2;
|
|
break;
|
|
case EVT_THREEKEY:
|
|
event->type = EVT_PAD3;
|
|
break;
|
|
case EVT_FOURKEY:
|
|
event->type = EVT_PAD4;
|
|
break;
|
|
case EVT_FIVEKEY:
|
|
event->type = EVT_PAD5;
|
|
break;
|
|
case EVT_SIXKEY:
|
|
event->type = EVT_PAD6;
|
|
break;
|
|
case EVT_SEVENKEY:
|
|
event->type = EVT_PAD7;
|
|
break;
|
|
case EVT_EIGHTKEY:
|
|
event->type = EVT_PAD8;
|
|
break;
|
|
case EVT_NINEKEY:
|
|
event->type = EVT_PAD9;
|
|
break;
|
|
case EVT_MINUSKEY:
|
|
event->type = EVT_PADMINUS;
|
|
break;
|
|
case EVT_EQUALKEY:
|
|
event->type = EVT_PADPLUSKEY;
|
|
break;
|
|
case EVT_BACKSLASHKEY:
|
|
event->type = EVT_PADSLASHKEY;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
constexpr wmTabletData wm_event_tablet_data_default()
|
|
{
|
|
wmTabletData tablet_data{};
|
|
tablet_data.active = EVT_TABLET_NONE;
|
|
tablet_data.pressure = 1.0f;
|
|
tablet_data.x_tilt = 0.0f;
|
|
tablet_data.y_tilt = 0.0f;
|
|
tablet_data.is_motion_absolute = false;
|
|
return tablet_data;
|
|
}
|
|
|
|
void WM_event_tablet_data_default_set(wmTabletData *tablet_data)
|
|
{
|
|
*tablet_data = wm_event_tablet_data_default();
|
|
}
|
|
|
|
void wm_tablet_data_from_ghost(const GHOST_TabletData *tablet_data, wmTabletData *wmtab)
|
|
{
|
|
if ((tablet_data != nullptr) && tablet_data->Active != GHOST_kTabletModeNone) {
|
|
wmtab->active = int(tablet_data->Active);
|
|
wmtab->pressure = wm_pressure_curve(tablet_data->Pressure);
|
|
wmtab->x_tilt = tablet_data->Xtilt;
|
|
wmtab->y_tilt = tablet_data->Ytilt;
|
|
/* We could have a preference to support relative tablet motion (we can't detect that). */
|
|
wmtab->is_motion_absolute = true;
|
|
// printf("%s: using tablet %.5f\n", __func__, wmtab->pressure);
|
|
}
|
|
else {
|
|
*wmtab = wm_event_tablet_data_default();
|
|
// printf("%s: not using tablet\n", __func__);
|
|
}
|
|
}
|
|
|
|
#ifdef WITH_INPUT_NDOF
|
|
/* Adds custom-data to event. */
|
|
static void attach_ndof_data(wmEvent *event, const GHOST_TEventNDOFMotionData *ghost)
|
|
{
|
|
wmNDOFMotionData *data = MEM_cnew<wmNDOFMotionData>("Custom-data NDOF");
|
|
|
|
const float ts = U.ndof_sensitivity;
|
|
const float rs = U.ndof_orbit_sensitivity;
|
|
|
|
mul_v3_v3fl(data->tvec, &ghost->tx, ts);
|
|
mul_v3_v3fl(data->rvec, &ghost->rx, rs);
|
|
|
|
if (U.ndof_flag & NDOF_PAN_YZ_SWAP_AXIS) {
|
|
float t;
|
|
t = data->tvec[1];
|
|
data->tvec[1] = -data->tvec[2];
|
|
data->tvec[2] = t;
|
|
}
|
|
|
|
data->dt = ghost->dt;
|
|
|
|
data->progress = (wmProgress)ghost->progress;
|
|
|
|
event->custom = EVT_DATA_NDOF_MOTION;
|
|
event->customdata = data;
|
|
event->customdata_free = true;
|
|
}
|
|
#endif /* WITH_INPUT_NDOF */
|
|
|
|
/* Imperfect but probably usable... draw/enable drags to other windows. */
|
|
static wmWindow *wm_event_cursor_other_windows(wmWindowManager *wm, wmWindow *win, wmEvent *event)
|
|
{
|
|
/* If GHOST doesn't support window positioning, don't use this feature at all. */
|
|
const static int8_t supports_window_position = GHOST_SupportsWindowPosition();
|
|
if (!supports_window_position) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (wm->windows.first == wm->windows.last) {
|
|
return nullptr;
|
|
}
|
|
|
|
/* In order to use window size and mouse position (pixels), we have to use a WM function. */
|
|
|
|
/* Check if outside, include top window bar. */
|
|
int event_xy[2] = {UNPACK2(event->xy)};
|
|
if (event_xy[0] < 0 || event_xy[1] < 0 || event_xy[0] > WM_window_pixels_x(win) ||
|
|
event_xy[1] > WM_window_pixels_y(win) + 30) {
|
|
/* Let's skip windows having modal handlers now. */
|
|
/* Potential XXX ugly... I wouldn't have added a `modalhandlers` list
|
|
* (introduced in rev 23331, ton). */
|
|
LISTBASE_FOREACH (wmEventHandler *, handler, &win->modalhandlers) {
|
|
if (ELEM(handler->type, WM_HANDLER_TYPE_UI, WM_HANDLER_TYPE_OP)) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
wmWindow *win_other = WM_window_find_under_cursor(win, event_xy, event_xy);
|
|
if (win_other && win_other != win) {
|
|
copy_v2_v2_int(event->xy, event_xy);
|
|
return win_other;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static bool wm_event_is_double_click(const wmEvent *event)
|
|
{
|
|
if ((event->type == event->prev_type) && (event->prev_val == KM_RELEASE) &&
|
|
(event->val == KM_PRESS)) {
|
|
if (ISMOUSE_BUTTON(event->type) && WM_event_drag_test(event, event->prev_press_xy)) {
|
|
/* Pass. */
|
|
}
|
|
else {
|
|
if ((PIL_check_seconds_timer() - event->prev_press_time) * 1000 < U.dbl_click_time) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Copy the current state to the previous event state.
|
|
*/
|
|
static void wm_event_prev_values_set(wmEvent *event, wmEvent *event_state)
|
|
{
|
|
event->prev_val = event_state->prev_val = event_state->val;
|
|
event->prev_type = event_state->prev_type = event_state->type;
|
|
}
|
|
|
|
static void wm_event_prev_click_set(wmEvent *event_state)
|
|
{
|
|
event_state->prev_press_time = PIL_check_seconds_timer();
|
|
event_state->prev_press_type = event_state->type;
|
|
event_state->prev_press_modifier = event_state->modifier;
|
|
event_state->prev_press_keymodifier = event_state->keymodifier;
|
|
event_state->prev_press_xy[0] = event_state->xy[0];
|
|
event_state->prev_press_xy[1] = event_state->xy[1];
|
|
}
|
|
|
|
static wmEvent *wm_event_add_mousemove(wmWindow *win, const wmEvent *event)
|
|
{
|
|
wmEvent *event_last = static_cast<wmEvent *>(win->event_queue.last);
|
|
|
|
/* Some painting operators want accurate mouse events, they can
|
|
* handle in between mouse move moves, others can happily ignore
|
|
* them for better performance. */
|
|
if (event_last && event_last->type == MOUSEMOVE) {
|
|
event_last->type = INBETWEEN_MOUSEMOVE;
|
|
event_last->flag = (eWM_EventFlag)0;
|
|
}
|
|
|
|
wmEvent *event_new = wm_event_add(win, event);
|
|
if (event_last == nullptr) {
|
|
event_last = win->eventstate;
|
|
}
|
|
|
|
copy_v2_v2_int(event_new->prev_xy, event_last->xy);
|
|
return event_new;
|
|
}
|
|
|
|
static wmEvent *wm_event_add_mousemove_to_head(wmWindow *win)
|
|
{
|
|
/* Use the last handled event instead of `win->eventstate` because the state of the modifiers
|
|
* and previous values should be set based on the last state, not using values from the future.
|
|
* So this gives an accurate simulation of mouse motion before the next event is handled. */
|
|
const wmEvent *event_last = win->event_last_handled;
|
|
|
|
wmEvent tevent;
|
|
if (event_last) {
|
|
tevent = *event_last;
|
|
|
|
tevent.flag = (eWM_EventFlag)0;
|
|
tevent.utf8_buf[0] = '\0';
|
|
|
|
wm_event_custom_clear(&tevent);
|
|
}
|
|
else {
|
|
memset(&tevent, 0x0, sizeof(tevent));
|
|
}
|
|
|
|
tevent.type = MOUSEMOVE;
|
|
tevent.val = KM_NOTHING;
|
|
copy_v2_v2_int(tevent.prev_xy, tevent.xy);
|
|
|
|
wmEvent *event_new = wm_event_add(win, &tevent);
|
|
BLI_remlink(&win->event_queue, event_new);
|
|
BLI_addhead(&win->event_queue, event_new);
|
|
|
|
copy_v2_v2_int(event_new->prev_xy, event_last->xy);
|
|
return event_new;
|
|
}
|
|
|
|
static wmEvent *wm_event_add_trackpad(wmWindow *win, const wmEvent *event, int deltax, int deltay)
|
|
{
|
|
/* Ignore in between track-pad events for performance, we only need high accuracy
|
|
* for painting with mouse moves, for navigation using the accumulated value is ok. */
|
|
wmEvent *event_last = static_cast<wmEvent *>(win->event_queue.last);
|
|
if (event_last && event_last->type == event->type) {
|
|
deltax += event_last->xy[0] - event_last->prev_xy[0];
|
|
deltay += event_last->xy[1] - event_last->prev_xy[1];
|
|
|
|
wm_event_free_last(win);
|
|
}
|
|
|
|
/* Set prev_xy, the delta is computed from this in operators. */
|
|
wmEvent *event_new = wm_event_add(win, event);
|
|
event_new->prev_xy[0] = event_new->xy[0] - deltax;
|
|
event_new->prev_xy[1] = event_new->xy[1] - deltay;
|
|
|
|
return event_new;
|
|
}
|
|
|
|
/**
|
|
* Update the event-state for any kind of event that supports #KM_PRESS / #KM_RELEASE.
|
|
*
|
|
* \param check_double_click: Optionally skip checking for double-click events.
|
|
* Needed for event simulation where the time of click events is not so predictable.
|
|
*/
|
|
static void wm_event_state_update_and_click_set_ex(wmEvent *event,
|
|
wmEvent *event_state,
|
|
const bool is_keyboard,
|
|
const bool check_double_click)
|
|
{
|
|
BLI_assert(ISKEYBOARD_OR_BUTTON(event->type));
|
|
BLI_assert(ELEM(event->val, KM_PRESS, KM_RELEASE));
|
|
|
|
/* Only copy these flags into the `event_state`. */
|
|
const eWM_EventFlag event_state_flag_mask = WM_EVENT_IS_REPEAT;
|
|
|
|
wm_event_prev_values_set(event, event_state);
|
|
|
|
/* Copy to event state. */
|
|
event_state->val = event->val;
|
|
event_state->type = event->type;
|
|
/* It's important only to write into the `event_state` modifier for keyboard
|
|
* events because emulate MMB clears one of the modifiers in `event->modifier`,
|
|
* making the second press not behave as if the modifier is pressed, see T96279. */
|
|
if (is_keyboard) {
|
|
event_state->modifier = event->modifier;
|
|
}
|
|
event_state->flag = (event->flag & event_state_flag_mask);
|
|
/* NOTE: It's important that `keymodifier` is handled in the keyboard event handling logic
|
|
* since the `event_state` and the `event` are not kept in sync. */
|
|
|
|
/* Double click test. */
|
|
if (check_double_click && wm_event_is_double_click(event)) {
|
|
CLOG_INFO(WM_LOG_HANDLERS, 1, "DBL_CLICK: detected");
|
|
event->val = KM_DBL_CLICK;
|
|
}
|
|
else if (event->val == KM_PRESS) {
|
|
if ((event->flag & WM_EVENT_IS_REPEAT) == 0) {
|
|
wm_event_prev_click_set(event_state);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void wm_event_state_update_and_click_set(wmEvent *event,
|
|
wmEvent *event_state,
|
|
const GHOST_TEventType type)
|
|
{
|
|
const bool is_keyboard = ELEM(type, GHOST_kEventKeyDown, GHOST_kEventKeyUp);
|
|
const bool check_double_click = true;
|
|
wm_event_state_update_and_click_set_ex(event, event_state, is_keyboard, check_double_click);
|
|
}
|
|
|
|
/* Returns true when the two events corresponds to a press of the same key with the same modifiers.
|
|
*/
|
|
static bool wm_event_is_same_key_press(const wmEvent &event_a, const wmEvent &event_b)
|
|
{
|
|
if (event_a.val != KM_PRESS || event_b.val != KM_PRESS) {
|
|
return false;
|
|
}
|
|
|
|
if (event_a.modifier != event_b.modifier || event_a.type != event_b.type) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the event is a key press event which is to be ignored and not added to the event
|
|
* queue.
|
|
*
|
|
* A key press event will be ignored if there is already matched key press in the queue.
|
|
* This avoids the event queue "clogging" in the situations when there is an operator bound to a
|
|
* key press event and the execution time of the operator is longer than the key repeat.
|
|
*/
|
|
static bool wm_event_is_ignorable_key_press(const wmWindow *win, const wmEvent &event)
|
|
{
|
|
if (BLI_listbase_is_empty(&win->event_queue)) {
|
|
/* If the queue is empty never ignore the event.
|
|
* Empty queue at this point means that the events are handled fast enough, and there is no
|
|
* reason to ignore anything. */
|
|
return false;
|
|
}
|
|
|
|
if ((event.flag & WM_EVENT_IS_REPEAT) == 0) {
|
|
/* Only ignore repeat events from the keyboard, and allow accumulation of non-repeat events.
|
|
*
|
|
* The goal of this check is to allow events coming from a keyboard macro software, which can
|
|
* generate events quicker than the main loop handles them. In this case we want all events to
|
|
* be handled (unless the keyboard macro software tags them as repeat) because otherwise it
|
|
* will become impossible to get reliable results of automated events testing. */
|
|
return false;
|
|
}
|
|
|
|
const wmEvent &last_event = *static_cast<const wmEvent *>(win->event_queue.last);
|
|
|
|
return wm_event_is_same_key_press(last_event, event);
|
|
}
|
|
|
|
void wm_event_add_ghostevent(wmWindowManager *wm, wmWindow *win, int type, void *customdata)
|
|
{
|
|
if (UNLIKELY(G.f & G_FLAG_EVENT_SIMULATE)) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Having both, \a event and \a event_state, can be highly confusing to work with,
|
|
* but is necessary for our current event system, so let's clear things up a bit:
|
|
*
|
|
* - Data added to event only will be handled immediately,
|
|
* but will not be copied to the next event.
|
|
* - Data added to \a event_state only stays,
|
|
* but is handled with the next event -> execution delay.
|
|
* - Data added to event and \a event_state stays and is handled immediately.
|
|
*/
|
|
wmEvent event, *event_state = win->eventstate;
|
|
|
|
/* Initialize and copy state (only mouse x y and modifiers). */
|
|
event = *event_state;
|
|
event.flag = (eWM_EventFlag)0;
|
|
|
|
/**
|
|
* Always support accessing the last key press/release. This is set from `win->eventstate`,
|
|
* so it will always be a valid event type to store in the previous state.
|
|
*
|
|
* Note that these values are intentionally _not_ set in the `win->eventstate`,
|
|
* as copying these values only makes sense when `win->eventstate->{val/type}` would be
|
|
* written to (which only happens for some kinds of events).
|
|
* If this was done it could leave `win->eventstate` previous and current value
|
|
* set to the same key press/release state which doesn't make sense.
|
|
*/
|
|
event.prev_type = event.type;
|
|
event.prev_val = event.val;
|
|
|
|
/* Always use modifiers from the active window since
|
|
* changes to modifiers aren't sent to inactive windows, see: T66088. */
|
|
if ((wm->winactive != win) && (wm->winactive && wm->winactive->eventstate)) {
|
|
event.modifier = wm->winactive->eventstate->modifier;
|
|
event.keymodifier = wm->winactive->eventstate->keymodifier;
|
|
}
|
|
|
|
/* Ensure the event state is correct, any deviation from this may cause bugs.
|
|
*
|
|
* NOTE: #EVENT_NONE is set when unknown keys are pressed,
|
|
* while not common, avoid a false alarm. */
|
|
#ifndef NDEBUG
|
|
if ((event_state->type || event_state->val) && /* Ignore cleared event state. */
|
|
!(ISKEYBOARD_OR_BUTTON(event_state->type) || (event_state->type == EVENT_NONE))) {
|
|
CLOG_WARN(WM_LOG_HANDLERS,
|
|
"Non-keyboard/mouse button found in 'win->eventstate->type = %d'",
|
|
event_state->type);
|
|
}
|
|
if ((event_state->prev_type || event_state->prev_val) && /* Ignore cleared event state. */
|
|
!(ISKEYBOARD_OR_BUTTON(event_state->prev_type) || (event_state->type == EVENT_NONE))) {
|
|
CLOG_WARN(WM_LOG_HANDLERS,
|
|
"Non-keyboard/mouse button found in 'win->eventstate->prev_type = %d'",
|
|
event_state->prev_type);
|
|
}
|
|
#endif
|
|
|
|
switch (type) {
|
|
/* Mouse move, also to inactive window (X11 does this). */
|
|
case GHOST_kEventCursorMove: {
|
|
GHOST_TEventCursorData *cd = static_cast<GHOST_TEventCursorData *>(customdata);
|
|
|
|
copy_v2_v2_int(event.xy, &cd->x);
|
|
wm_stereo3d_mouse_offset_apply(win, event.xy);
|
|
wm_tablet_data_from_ghost(&cd->tablet, &event.tablet);
|
|
|
|
event.type = MOUSEMOVE;
|
|
event.val = KM_NOTHING;
|
|
{
|
|
wmEvent *event_new = wm_event_add_mousemove(win, &event);
|
|
copy_v2_v2_int(event_state->xy, event_new->xy);
|
|
event_state->tablet.is_motion_absolute = event_new->tablet.is_motion_absolute;
|
|
}
|
|
|
|
/* Also add to other window if event is there, this makes overdraws disappear nicely. */
|
|
/* It remaps mouse-coord to other window in event. */
|
|
wmWindow *win_other = wm_event_cursor_other_windows(wm, win, &event);
|
|
if (win_other) {
|
|
wmEvent event_other = *win_other->eventstate;
|
|
|
|
/* Use the modifier state of this window. */
|
|
event_other.modifier = event.modifier;
|
|
event_other.keymodifier = event.keymodifier;
|
|
|
|
/* See comment for this operation on `event` for details. */
|
|
event_other.prev_type = event_other.type;
|
|
event_other.prev_val = event_other.val;
|
|
|
|
copy_v2_v2_int(event_other.xy, event.xy);
|
|
event_other.type = MOUSEMOVE;
|
|
event_other.val = KM_NOTHING;
|
|
{
|
|
wmEvent *event_new = wm_event_add_mousemove(win_other, &event_other);
|
|
copy_v2_v2_int(win_other->eventstate->xy, event_new->xy);
|
|
win_other->eventstate->tablet.is_motion_absolute = event_new->tablet.is_motion_absolute;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventTrackpad: {
|
|
GHOST_TEventTrackpadData *pd = static_cast<GHOST_TEventTrackpadData *>(customdata);
|
|
switch (pd->subtype) {
|
|
case GHOST_kTrackpadEventMagnify:
|
|
event.type = MOUSEZOOM;
|
|
pd->deltaX = -pd->deltaX;
|
|
pd->deltaY = -pd->deltaY;
|
|
break;
|
|
case GHOST_kTrackpadEventSmartMagnify:
|
|
event.type = MOUSESMARTZOOM;
|
|
break;
|
|
case GHOST_kTrackpadEventRotate:
|
|
event.type = MOUSEROTATE;
|
|
break;
|
|
case GHOST_kTrackpadEventScroll:
|
|
default:
|
|
event.type = MOUSEPAN;
|
|
break;
|
|
}
|
|
|
|
event.xy[0] = event_state->xy[0] = pd->x;
|
|
event.xy[1] = event_state->xy[1] = pd->y;
|
|
event.val = KM_NOTHING;
|
|
|
|
/* The direction is inverted from the device due to system preferences. */
|
|
if (pd->isDirectionInverted) {
|
|
event.flag |= WM_EVENT_SCROLL_INVERT;
|
|
}
|
|
|
|
wm_event_add_trackpad(win, &event, pd->deltaX, -pd->deltaY);
|
|
break;
|
|
}
|
|
/* Mouse button. */
|
|
case GHOST_kEventButtonDown:
|
|
case GHOST_kEventButtonUp: {
|
|
GHOST_TEventButtonData *bd = static_cast<GHOST_TEventButtonData *>(customdata);
|
|
|
|
/* Get value and type from Ghost. */
|
|
event.val = (type == GHOST_kEventButtonDown) ? KM_PRESS : KM_RELEASE;
|
|
|
|
if (bd->button == GHOST_kButtonMaskLeft) {
|
|
event.type = LEFTMOUSE;
|
|
}
|
|
else if (bd->button == GHOST_kButtonMaskRight) {
|
|
event.type = RIGHTMOUSE;
|
|
}
|
|
else if (bd->button == GHOST_kButtonMaskButton4) {
|
|
event.type = BUTTON4MOUSE;
|
|
}
|
|
else if (bd->button == GHOST_kButtonMaskButton5) {
|
|
event.type = BUTTON5MOUSE;
|
|
}
|
|
else if (bd->button == GHOST_kButtonMaskButton6) {
|
|
event.type = BUTTON6MOUSE;
|
|
}
|
|
else if (bd->button == GHOST_kButtonMaskButton7) {
|
|
event.type = BUTTON7MOUSE;
|
|
}
|
|
else {
|
|
event.type = MIDDLEMOUSE;
|
|
}
|
|
|
|
/* Get tablet data. */
|
|
wm_tablet_data_from_ghost(&bd->tablet, &event.tablet);
|
|
|
|
wm_eventemulation(&event, false);
|
|
wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type);
|
|
|
|
/* Add to other window if event is there (not to both!). */
|
|
wmWindow *win_other = wm_event_cursor_other_windows(wm, win, &event);
|
|
if (win_other) {
|
|
wmEvent event_other = *win_other->eventstate;
|
|
|
|
/* Use the modifier state of this window. */
|
|
event_other.modifier = event.modifier;
|
|
event_other.keymodifier = event.keymodifier;
|
|
|
|
/* See comment for this operation on `event` for details. */
|
|
event_other.prev_type = event_other.type;
|
|
event_other.prev_val = event_other.val;
|
|
|
|
copy_v2_v2_int(event_other.xy, event.xy);
|
|
|
|
event_other.type = event.type;
|
|
event_other.val = event.val;
|
|
event_other.tablet = event.tablet;
|
|
|
|
wm_event_add(win_other, &event_other);
|
|
}
|
|
else {
|
|
wm_event_add(win, &event);
|
|
}
|
|
|
|
break;
|
|
}
|
|
/* Keyboard. */
|
|
case GHOST_kEventKeyDown:
|
|
case GHOST_kEventKeyUp: {
|
|
GHOST_TEventKeyData *kd = static_cast<GHOST_TEventKeyData *>(customdata);
|
|
event.type = convert_key(kd->key);
|
|
if (UNLIKELY(event.type == EVENT_NONE)) {
|
|
break;
|
|
}
|
|
|
|
/* Might be not null terminated. */
|
|
memcpy(event.utf8_buf, kd->utf8_buf, sizeof(event.utf8_buf));
|
|
if (kd->is_repeat) {
|
|
event.flag |= WM_EVENT_IS_REPEAT;
|
|
}
|
|
event.val = (type == GHOST_kEventKeyDown) ? KM_PRESS : KM_RELEASE;
|
|
|
|
wm_eventemulation(&event, false);
|
|
|
|
/* Exclude arrow keys, escape, etc from text input. */
|
|
if (type == GHOST_kEventKeyUp) {
|
|
/* Ghost should do this already for key up. */
|
|
if (event.utf8_buf[0]) {
|
|
CLOG_ERROR(WM_LOG_EVENTS,
|
|
"ghost on your platform is misbehaving, utf8 events on key up!");
|
|
}
|
|
event.utf8_buf[0] = '\0';
|
|
}
|
|
else {
|
|
if (event.utf8_buf[0] < 32 && event.utf8_buf[0] > 0) {
|
|
event.utf8_buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
if (event.utf8_buf[0]) {
|
|
/* NOTE(@campbellbarton): Detect non-ASCII characters stored in `utf8_buf`,
|
|
* ideally this would never happen but it can't be ruled out for X11 which has
|
|
* special handling of Latin1 when building without UTF8 support.
|
|
* Avoid regressions by adding this conversions, it should eventually be removed. */
|
|
if ((event.utf8_buf[0] >= 0x80) && (event.utf8_buf[1] == '\0')) {
|
|
const uint c = uint(event.utf8_buf[0]);
|
|
int utf8_buf_len = BLI_str_utf8_from_unicode(c, event.utf8_buf, sizeof(event.utf8_buf));
|
|
CLOG_ERROR(WM_LOG_EVENTS,
|
|
"ghost detected non-ASCII single byte character '%u', converting to utf8 "
|
|
"('%.*s', length=%d)",
|
|
c,
|
|
utf8_buf_len,
|
|
event.utf8_buf,
|
|
utf8_buf_len);
|
|
}
|
|
|
|
if (BLI_str_utf8_size(event.utf8_buf) == -1) {
|
|
CLOG_ERROR(WM_LOG_EVENTS,
|
|
"ghost detected an invalid unicode character '%d'",
|
|
int(uchar(event.utf8_buf[0])));
|
|
event.utf8_buf[0] = '\0';
|
|
}
|
|
}
|
|
|
|
/* NOTE(@campbellbarton): Setting the modifier state based on press/release
|
|
* is technically incorrect.
|
|
*
|
|
* - The user might hold both left/right modifier keys, then only release one.
|
|
*
|
|
* This could be solved by storing a separate flag for the left/right modifiers,
|
|
* and combine them into `event.modifiers`.
|
|
*
|
|
* - The user might have multiple keyboards (or keyboard + NDOF device)
|
|
* where it's possible to press the same modifier key multiple times.
|
|
*
|
|
* This could be solved by tracking the number of held modifier keys,
|
|
* (this is in fact what LIBXKB does), however doing this relies on all GHOST
|
|
* back-ends properly reporting every press/release as any mismatch could result
|
|
* in modifier keys being stuck (which is very bad!).
|
|
*
|
|
* To my knowledge users never reported a bug relating to these limitations so
|
|
* it seems reasonable to keep the current logic. */
|
|
|
|
switch (event.type) {
|
|
case EVT_LEFTSHIFTKEY:
|
|
case EVT_RIGHTSHIFTKEY: {
|
|
SET_FLAG_FROM_TEST(event.modifier, (event.val == KM_PRESS), KM_SHIFT);
|
|
break;
|
|
}
|
|
case EVT_LEFTCTRLKEY:
|
|
case EVT_RIGHTCTRLKEY: {
|
|
SET_FLAG_FROM_TEST(event.modifier, (event.val == KM_PRESS), KM_CTRL);
|
|
break;
|
|
}
|
|
case EVT_LEFTALTKEY:
|
|
case EVT_RIGHTALTKEY: {
|
|
SET_FLAG_FROM_TEST(event.modifier, (event.val == KM_PRESS), KM_ALT);
|
|
break;
|
|
}
|
|
case EVT_OSKEY: {
|
|
SET_FLAG_FROM_TEST(event.modifier, (event.val == KM_PRESS), KM_OSKEY);
|
|
break;
|
|
}
|
|
default: {
|
|
if (event.val == KM_PRESS) {
|
|
if (event.keymodifier == 0) {
|
|
/* Only set in `eventstate`, for next event. */
|
|
event_state->keymodifier = event.type;
|
|
}
|
|
}
|
|
else {
|
|
BLI_assert(event.val == KM_RELEASE);
|
|
if (event.keymodifier == event.type) {
|
|
event.keymodifier = event_state->keymodifier = 0;
|
|
}
|
|
}
|
|
|
|
/* This case happens on holding a key pressed,
|
|
* it should not generate press events with the same key as modifier. */
|
|
if (event.keymodifier == event.type) {
|
|
event.keymodifier = 0;
|
|
}
|
|
else if (event.keymodifier == EVT_UNKNOWNKEY) {
|
|
/* This case happens with an external number-pad, and also when using 'dead keys'
|
|
* (to compose complex latin characters e.g.), it's not really clear why.
|
|
* Since it's impossible to map a key modifier to an unknown key,
|
|
* it shouldn't harm to clear it. */
|
|
event_state->keymodifier = event.keymodifier = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* It's important `event.modifier` has been initialized first. */
|
|
wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type);
|
|
|
|
/* If test_break set, it catches this. Do not set with modifier presses.
|
|
* Exclude modifiers because MS-Windows uses these to bring up the task manager.
|
|
*
|
|
* NOTE: in general handling events here isn't great design as
|
|
* event handling should be managed by the event handling loop.
|
|
* Make an exception for `G.is_break` as it ensures we can always cancel operations
|
|
* such as rendering or baking no matter which operation is currently handling events. */
|
|
if ((event.type == EVT_ESCKEY) && (event.val == KM_PRESS) && (event.modifier == 0)) {
|
|
G.is_break = true;
|
|
}
|
|
|
|
if (!wm_event_is_ignorable_key_press(win, event)) {
|
|
wm_event_add(win, &event);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case GHOST_kEventWheel: {
|
|
GHOST_TEventWheelData *wheelData = static_cast<GHOST_TEventWheelData *>(customdata);
|
|
|
|
if (wheelData->z > 0) {
|
|
event.type = WHEELUPMOUSE;
|
|
}
|
|
else {
|
|
event.type = WHEELDOWNMOUSE;
|
|
}
|
|
|
|
event.val = KM_PRESS;
|
|
wm_event_add(win, &event);
|
|
|
|
break;
|
|
}
|
|
case GHOST_kEventTimer: {
|
|
event.type = TIMER;
|
|
event.custom = EVT_DATA_TIMER;
|
|
event.customdata = customdata;
|
|
event.val = KM_NOTHING;
|
|
event.keymodifier = 0;
|
|
wm_event_add(win, &event);
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef WITH_INPUT_NDOF
|
|
case GHOST_kEventNDOFMotion: {
|
|
event.type = NDOF_MOTION;
|
|
event.val = KM_NOTHING;
|
|
attach_ndof_data(&event, static_cast<const GHOST_TEventNDOFMotionData *>(customdata));
|
|
wm_event_add(win, &event);
|
|
|
|
CLOG_INFO(WM_LOG_HANDLERS, 1, "sending NDOF_MOTION, prev = %d %d", event.xy[0], event.xy[1]);
|
|
break;
|
|
}
|
|
|
|
case GHOST_kEventNDOFButton: {
|
|
GHOST_TEventNDOFButtonData *e = static_cast<GHOST_TEventNDOFButtonData *>(customdata);
|
|
|
|
event.type = NDOF_BUTTON_INDEX_AS_EVENT(e->button);
|
|
|
|
switch (e->action) {
|
|
case GHOST_kPress:
|
|
event.val = KM_PRESS;
|
|
break;
|
|
case GHOST_kRelease:
|
|
event.val = KM_RELEASE;
|
|
break;
|
|
default:
|
|
BLI_assert_unreachable();
|
|
}
|
|
|
|
event.custom = 0;
|
|
event.customdata = nullptr;
|
|
|
|
wm_event_state_update_and_click_set(&event, event_state, (GHOST_TEventType)type);
|
|
|
|
wm_event_add(win, &event);
|
|
|
|
break;
|
|
}
|
|
#endif /* WITH_INPUT_NDOF */
|
|
|
|
case GHOST_kEventUnknown:
|
|
case GHOST_kNumEventTypes:
|
|
break;
|
|
|
|
case GHOST_kEventWindowDeactivate: {
|
|
event.type = WINDEACTIVATE;
|
|
wm_event_add(win, &event);
|
|
|
|
break;
|
|
}
|
|
|
|
#ifdef WITH_INPUT_IME
|
|
case GHOST_kEventImeCompositionStart: {
|
|
event.val = KM_PRESS;
|
|
win->ime_data = static_cast<wmIMEData *>(customdata);
|
|
win->ime_data->is_ime_composing = true;
|
|
event.type = WM_IME_COMPOSITE_START;
|
|
wm_event_add(win, &event);
|
|
break;
|
|
}
|
|
case GHOST_kEventImeComposition: {
|
|
event.val = KM_PRESS;
|
|
event.type = WM_IME_COMPOSITE_EVENT;
|
|
wm_event_add(win, &event);
|
|
break;
|
|
}
|
|
case GHOST_kEventImeCompositionEnd: {
|
|
event.val = KM_PRESS;
|
|
if (win->ime_data) {
|
|
win->ime_data->is_ime_composing = false;
|
|
}
|
|
event.type = WM_IME_COMPOSITE_END;
|
|
wm_event_add(win, &event);
|
|
break;
|
|
}
|
|
#endif /* WITH_INPUT_IME */
|
|
}
|
|
|
|
#if 0
|
|
WM_event_print(&event);
|
|
#endif
|
|
}
|
|
|
|
#ifdef WITH_XR_OPENXR
|
|
void wm_event_add_xrevent(wmWindow *win, wmXrActionData *actiondata, short val)
|
|
{
|
|
BLI_assert(ELEM(val, KM_PRESS, KM_RELEASE));
|
|
|
|
wmEvent event{};
|
|
event.type = EVT_XR_ACTION;
|
|
event.val = val;
|
|
event.flag = (eWM_EventFlag)0;
|
|
event.custom = EVT_DATA_XR;
|
|
event.customdata = actiondata;
|
|
event.customdata_free = true;
|
|
|
|
wm_event_add(win, &event);
|
|
}
|
|
#endif /* WITH_XR_OPENXR */
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name WM Interface Locking
|
|
* \{ */
|
|
|
|
/**
|
|
* Check whether operator is allowed to run in case interface is locked,
|
|
* If interface is unlocked, will always return truth.
|
|
*/
|
|
static bool wm_operator_check_locked_interface(bContext *C, wmOperatorType *ot)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
|
|
if (wm->is_interface_locked) {
|
|
if ((ot->flag & OPTYPE_LOCK_BYPASS) == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void WM_set_locked_interface(wmWindowManager *wm, bool lock)
|
|
{
|
|
/* This will prevent events from being handled while interface is locked
|
|
*
|
|
* Use a "local" flag for now, because currently no other areas could
|
|
* benefit of locked interface anyway (aka using G.is_interface_locked
|
|
* wouldn't be useful anywhere outside of window manager, so let's not
|
|
* pollute global context with such an information for now).
|
|
*/
|
|
wm->is_interface_locked = lock ? 1 : 0;
|
|
|
|
/* This will prevent drawing regions which uses non-thread-safe data.
|
|
* Currently it'll be just a 3D viewport.
|
|
*
|
|
* TODO(sergey): Make it different locked states, so different jobs
|
|
* could lock different areas of blender and allow
|
|
* interaction with others?
|
|
*/
|
|
BKE_spacedata_draw_locks(lock);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Event / Keymap Matching API
|
|
* \{ */
|
|
|
|
void WM_event_get_keymaps_from_handler(wmWindowManager *wm,
|
|
wmWindow *win,
|
|
wmEventHandler_Keymap *handler,
|
|
wmEventHandler_KeymapResult *km_result)
|
|
{
|
|
if (handler->dynamic.keymap_fn != nullptr) {
|
|
handler->dynamic.keymap_fn(wm, win, handler, km_result);
|
|
BLI_assert(handler->keymap == nullptr);
|
|
}
|
|
else {
|
|
memset(km_result, 0x0, sizeof(*km_result));
|
|
wmKeyMap *keymap = WM_keymap_active(wm, handler->keymap);
|
|
BLI_assert(keymap != nullptr);
|
|
if (keymap != nullptr) {
|
|
km_result->keymaps[km_result->keymaps_len++] = keymap;
|
|
}
|
|
}
|
|
}
|
|
|
|
wmKeyMapItem *WM_event_match_keymap_item(bContext *C, wmKeyMap *keymap, const wmEvent *event)
|
|
{
|
|
LISTBASE_FOREACH (wmKeyMapItem *, kmi, &keymap->items) {
|
|
if (wm_eventmatch(event, kmi)) {
|
|
wmOperatorType *ot = WM_operatortype_find(kmi->idname, false);
|
|
if (WM_operator_poll_context(C, ot, WM_OP_INVOKE_DEFAULT)) {
|
|
return kmi;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
wmKeyMapItem *WM_event_match_keymap_item_from_handlers(
|
|
bContext *C, wmWindowManager *wm, wmWindow *win, ListBase *handlers, const wmEvent *event)
|
|
{
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
|
|
/* During this loop, UI handlers for nested menus can tag multiple handlers free. */
|
|
if (handler_base->flag & WM_HANDLER_DO_FREE) {
|
|
/* Pass. */
|
|
}
|
|
else if (handler_base->poll == nullptr || handler_base->poll(CTX_wm_region(C), event)) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_KEYMAP) {
|
|
wmEventHandler_Keymap *handler = (wmEventHandler_Keymap *)handler_base;
|
|
wmEventHandler_KeymapResult km_result;
|
|
WM_event_get_keymaps_from_handler(wm, win, handler, &km_result);
|
|
for (int km_index = 0; km_index < km_result.keymaps_len; km_index++) {
|
|
wmKeyMap *keymap = km_result.keymaps[km_index];
|
|
if (WM_keymap_poll(C, keymap)) {
|
|
wmKeyMapItem *kmi = WM_event_match_keymap_item(C, keymap, event);
|
|
if (kmi != nullptr) {
|
|
return kmi;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Cursor Keymap Status
|
|
*
|
|
* Show cursor keys in the status bar.
|
|
* This is done by detecting changes to the state - full key-map lookups are expensive
|
|
* so only perform this on changing tools, space types, pressing different modifier keys... etc.
|
|
* \{ */
|
|
|
|
/** State storage to detect changes between calls to refresh the information. */
|
|
struct CursorKeymapInfo_State {
|
|
uint8_t modifier;
|
|
short space_type;
|
|
short region_type;
|
|
/** Never use, just compare memory for changes. */
|
|
bToolRef tref;
|
|
};
|
|
|
|
struct CursorKeymapInfo {
|
|
/**
|
|
* 0: Mouse button index.
|
|
* 1: Event type (click/press, drag).
|
|
* 2: Text.
|
|
*/
|
|
char text[3][2][128];
|
|
wmEvent state_event;
|
|
CursorKeymapInfo_State state;
|
|
};
|
|
|
|
static void wm_event_cursor_store(CursorKeymapInfo_State *state,
|
|
const wmEvent *event,
|
|
short space_type,
|
|
short region_type,
|
|
const bToolRef *tref)
|
|
{
|
|
state->modifier = event->modifier;
|
|
state->space_type = space_type;
|
|
state->region_type = region_type;
|
|
state->tref = tref ? *tref : bToolRef{};
|
|
}
|
|
|
|
const char *WM_window_cursor_keymap_status_get(const wmWindow *win,
|
|
int button_index,
|
|
int type_index)
|
|
{
|
|
if (win->cursor_keymap_status != nullptr) {
|
|
CursorKeymapInfo *cd = static_cast<CursorKeymapInfo *>(win->cursor_keymap_status);
|
|
const char *msg = cd->text[button_index][type_index];
|
|
if (*msg) {
|
|
return msg;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ScrArea *WM_window_status_area_find(wmWindow *win, bScreen *screen)
|
|
{
|
|
if (screen->state == SCREENFULL) {
|
|
return nullptr;
|
|
}
|
|
ScrArea *area_statusbar = nullptr;
|
|
LISTBASE_FOREACH (ScrArea *, area, &win->global_areas.areabase) {
|
|
if (area->spacetype == SPACE_STATUSBAR) {
|
|
area_statusbar = area;
|
|
break;
|
|
}
|
|
}
|
|
return area_statusbar;
|
|
}
|
|
|
|
void WM_window_status_area_tag_redraw(wmWindow *win)
|
|
{
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
ScrArea *area = WM_window_status_area_find(win, screen);
|
|
if (area != nullptr) {
|
|
ED_area_tag_redraw(area);
|
|
}
|
|
}
|
|
|
|
void WM_window_cursor_keymap_status_refresh(bContext *C, wmWindow *win)
|
|
{
|
|
bScreen *screen = WM_window_get_active_screen(win);
|
|
ScrArea *area_statusbar = WM_window_status_area_find(win, screen);
|
|
if (area_statusbar == nullptr) {
|
|
MEM_SAFE_FREE(win->cursor_keymap_status);
|
|
return;
|
|
}
|
|
|
|
CursorKeymapInfo *cd;
|
|
if (UNLIKELY(win->cursor_keymap_status == nullptr)) {
|
|
win->cursor_keymap_status = MEM_callocN(sizeof(CursorKeymapInfo), __func__);
|
|
}
|
|
cd = static_cast<CursorKeymapInfo *>(win->cursor_keymap_status);
|
|
|
|
/* Detect unchanged state (early exit). */
|
|
if (memcmp(&cd->state_event, win->eventstate, sizeof(wmEvent)) == 0) {
|
|
return;
|
|
}
|
|
|
|
/* Now perform more comprehensive check,
|
|
* still keep this fast since it happens on mouse-move. */
|
|
CursorKeymapInfo cd_prev = *((CursorKeymapInfo *)win->cursor_keymap_status);
|
|
cd->state_event = *win->eventstate;
|
|
|
|
/* Find active region and associated area. */
|
|
ARegion *region = screen->active_region;
|
|
if (region == nullptr) {
|
|
return;
|
|
}
|
|
|
|
ScrArea *area = nullptr;
|
|
ED_screen_areas_iter (win, screen, area_iter) {
|
|
if (BLI_findindex(&area_iter->regionbase, region) != -1) {
|
|
area = area_iter;
|
|
break;
|
|
}
|
|
}
|
|
if (area == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* Keep as-is. */
|
|
if (ELEM(area->spacetype, SPACE_STATUSBAR, SPACE_TOPBAR)) {
|
|
return;
|
|
}
|
|
if (ELEM(region->regiontype,
|
|
RGN_TYPE_HEADER,
|
|
RGN_TYPE_TOOL_HEADER,
|
|
RGN_TYPE_FOOTER,
|
|
RGN_TYPE_TEMPORARY,
|
|
RGN_TYPE_HUD)) {
|
|
return;
|
|
}
|
|
/* Fallback to window. */
|
|
if (ELEM(region->regiontype, RGN_TYPE_TOOLS, RGN_TYPE_TOOL_PROPS)) {
|
|
region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
|
|
}
|
|
|
|
/* Detect changes to the state. */
|
|
{
|
|
bToolRef *tref = nullptr;
|
|
if ((region->regiontype == RGN_TYPE_WINDOW) &&
|
|
((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK)) {
|
|
const Scene *scene = WM_window_get_active_scene(win);
|
|
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
|
|
WorkSpace *workspace = WM_window_get_active_workspace(win);
|
|
bToolKey tkey{};
|
|
tkey.space_type = area->spacetype;
|
|
tkey.mode = WM_toolsystem_mode_from_spacetype(scene, view_layer, area, area->spacetype);
|
|
tref = WM_toolsystem_ref_find(workspace, &tkey);
|
|
}
|
|
wm_event_cursor_store(&cd->state, win->eventstate, area->spacetype, region->regiontype, tref);
|
|
if (memcmp(&cd->state, &cd_prev.state, sizeof(cd->state)) == 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Changed context found, detect changes to key-map and refresh the status bar. */
|
|
const struct {
|
|
int button_index;
|
|
int type_index; /* 0: press or click, 1: drag. */
|
|
int event_type;
|
|
int event_value;
|
|
} event_data[] = {
|
|
{0, 0, LEFTMOUSE, KM_PRESS},
|
|
{0, 0, LEFTMOUSE, KM_CLICK},
|
|
{0, 0, LEFTMOUSE, KM_CLICK_DRAG},
|
|
|
|
{1, 0, MIDDLEMOUSE, KM_PRESS},
|
|
{1, 0, MIDDLEMOUSE, KM_CLICK},
|
|
{1, 0, MIDDLEMOUSE, KM_CLICK_DRAG},
|
|
|
|
{2, 0, RIGHTMOUSE, KM_PRESS},
|
|
{2, 0, RIGHTMOUSE, KM_CLICK},
|
|
{2, 0, RIGHTMOUSE, KM_CLICK_DRAG},
|
|
};
|
|
|
|
for (int button_index = 0; button_index < 3; button_index++) {
|
|
cd->text[button_index][0][0] = '\0';
|
|
cd->text[button_index][1][0] = '\0';
|
|
}
|
|
|
|
CTX_wm_window_set(C, win);
|
|
CTX_wm_area_set(C, area);
|
|
CTX_wm_region_set(C, region);
|
|
|
|
ListBase *handlers[] = {
|
|
®ion->handlers,
|
|
&area->handlers,
|
|
&win->handlers,
|
|
};
|
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
for (int data_index = 0; data_index < ARRAY_SIZE(event_data); data_index++) {
|
|
const int button_index = event_data[data_index].button_index;
|
|
const int type_index = event_data[data_index].type_index;
|
|
if (cd->text[button_index][type_index][0] != 0) {
|
|
continue;
|
|
}
|
|
wmEvent test_event = *win->eventstate;
|
|
test_event.type = event_data[data_index].event_type;
|
|
test_event.val = event_data[data_index].event_value;
|
|
test_event.flag = (eWM_EventFlag)0;
|
|
wm_eventemulation(&test_event, true);
|
|
wmKeyMapItem *kmi = nullptr;
|
|
for (int handler_index = 0; handler_index < ARRAY_SIZE(handlers); handler_index++) {
|
|
kmi = WM_event_match_keymap_item_from_handlers(
|
|
C, wm, win, handlers[handler_index], &test_event);
|
|
if (kmi) {
|
|
break;
|
|
}
|
|
}
|
|
if (kmi) {
|
|
wmOperatorType *ot = WM_operatortype_find(kmi->idname, false);
|
|
const char *name = (ot) ? WM_operatortype_name(ot, kmi->ptr) : kmi->idname;
|
|
STRNCPY(cd->text[button_index][type_index], name);
|
|
}
|
|
}
|
|
|
|
if (memcmp(&cd_prev.text, &cd->text, sizeof(cd_prev.text)) != 0) {
|
|
ED_area_tag_redraw(area_statusbar);
|
|
}
|
|
|
|
CTX_wm_window_set(C, nullptr);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Modal Keymap Status
|
|
* \{ */
|
|
|
|
bool WM_window_modal_keymap_status_draw(bContext *C, wmWindow *win, uiLayout *layout)
|
|
{
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
|
wmKeyMap *keymap = nullptr;
|
|
wmOperator *op = nullptr;
|
|
LISTBASE_FOREACH (wmEventHandler *, handler_base, &win->modalhandlers) {
|
|
if (handler_base->type == WM_HANDLER_TYPE_OP) {
|
|
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
|
|
if (handler->op != nullptr) {
|
|
/* 'handler->keymap' could be checked too, seems not to be used. */
|
|
wmKeyMap *keymap_test = WM_keymap_active(wm, handler->op->type->modalkeymap);
|
|
if (keymap_test && keymap_test->modal_items) {
|
|
keymap = keymap_test;
|
|
op = handler->op;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (keymap == nullptr || keymap->modal_items == nullptr) {
|
|
return false;
|
|
}
|
|
const EnumPropertyItem *items = static_cast<const EnumPropertyItem *>(keymap->modal_items);
|
|
|
|
uiLayout *row = uiLayoutRow(layout, true);
|
|
for (int i = 0; items[i].identifier; i++) {
|
|
if (!items[i].identifier[0]) {
|
|
continue;
|
|
}
|
|
if ((keymap->poll_modal_item != nullptr) &&
|
|
(keymap->poll_modal_item(op, items[i].value) == false)) {
|
|
continue;
|
|
}
|
|
|
|
bool show_text = true;
|
|
|
|
{
|
|
/* WARNING: O(n^2). */
|
|
wmKeyMapItem *kmi = nullptr;
|
|
for (kmi = static_cast<wmKeyMapItem *>(keymap->items.first); kmi; kmi = kmi->next) {
|
|
if (kmi->propvalue == items[i].value) {
|
|
break;
|
|
}
|
|
}
|
|
if (kmi != nullptr) {
|
|
if (kmi->val == KM_RELEASE) {
|
|
/* Assume release events just disable something which was toggled on. */
|
|
continue;
|
|
}
|
|
if (uiTemplateEventFromKeymapItem(row, items[i].name, kmi, false)) {
|
|
show_text = false;
|
|
}
|
|
}
|
|
}
|
|
if (show_text) {
|
|
char buf[UI_MAX_DRAW_STR];
|
|
int available_len = sizeof(buf);
|
|
char *p = buf;
|
|
WM_modalkeymap_operator_items_to_string_buf(
|
|
op->type, items[i].value, true, UI_MAX_SHORTCUT_STR, &available_len, &p);
|
|
p -= 1;
|
|
if (p > buf) {
|
|
BLI_snprintf(p, available_len, ": %s", items[i].name);
|
|
uiItemL(row, buf, 0);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** \} */
|