This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/windowmanager/intern/wm_event_system.c
Campbell Barton 9a659f2573 WM: improve logging for handling operators
- Add logging for CLICK_DRAG event handling to debug drag events.
- Use logging API for reporting the key-map, operator and event.

This command now prints useful information for investigating
key-map and event handling issues:

  blender --log "wm.handler.*" --log-level 4
2022-04-13 16:10:32 +10:00

5824 lines
180 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 <math.h>
#include <stdlib.h>
#include <string.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_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_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 "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 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);
/* -------------------------------------------------------------------- */
/** \name Event Management
* \{ */
wmEvent *wm_event_add_ex(wmWindow *win,
const wmEvent *event_to_add,
const wmEvent *event_to_add_after)
{
wmEvent *event = MEM_mallocN(sizeof(wmEvent), "wmEvent");
*event = *event_to_add;
if (event_to_add_after == NULL) {
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, NULL);
}
wmEvent *WM_event_add_simulate(wmWindow *win, const wmEvent *event_to_add)
{
if ((G.f & G_FLAG_EVENT_SIMULATE) == 0) {
BLI_assert_unreachable();
return NULL;
}
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)) {
win->eventstate->prev_val = event->prev_val = win->eventstate->val;
win->eventstate->prev_type = event->prev_type = win->eventstate->type;
win->eventstate->val = event->val;
win->eventstate->type = event->type;
if (event->val == KM_PRESS) {
if ((event->flag & WM_EVENT_IS_REPEAT) == 0) {
copy_v2_v2_int(win->eventstate->prev_press_xy, event->xy);
}
}
}
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 = 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 = NULL;
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 (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE) && (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 = 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 = BLI_poptail(&win->event_queue);
if (event != NULL) {
wm_event_free(event);
}
}
void wm_event_free_all(wmWindow *win)
{
wmEvent *event;
while ((event = 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
* \{ */
static bool wm_test_duplicate_notifier(const wmWindowManager *wm, uint type, void *reference)
{
LISTBASE_FOREACH (wmNotifier *, note, &wm->notifier_queue) {
if ((note->category | note->data | note->subtype | note->action) == type &&
note->reference == reference) {
return true;
}
}
return false;
}
void WM_event_add_notifier_ex(wmWindowManager *wm, const wmWindow *win, uint type, void *reference)
{
if (wm_test_duplicate_notifier(wm, type, reference)) {
return;
}
wmNotifier *note = MEM_callocN(sizeof(wmNotifier), "notifier");
BLI_addtail(&wm->notifier_queue, note);
note->window = win;
note->category = type & NOTE_CATEGORY;
note->data = type & NOTE_DATA;
note->subtype = type & NOTE_SUBTYPE;
note->action = type & NOTE_ACTION;
note->reference = reference;
}
/* 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(unsigned int type, void *reference)
{
Main *bmain = G_MAIN;
wmWindowManager *wm = bmain->wm.first;
if (!wm || wm_test_duplicate_notifier(wm, type, reference)) {
return;
}
wmNotifier *note = MEM_callocN(sizeof(wmNotifier), "notifier");
BLI_addtail(&wm->notifier_queue, note);
note->category = type & NOTE_CATEGORY;
note->data = type & NOTE_DATA;
note->subtype = type & NOTE_SUBTYPE;
note->action = type & NOTE_ACTION;
note->reference = reference;
}
void WM_main_remove_notifier_reference(const void *reference)
{
Main *bmain = G_MAIN;
wmWindowManager *wm = bmain->wm.first;
if (wm) {
LISTBASE_FOREACH_MUTABLE (wmNotifier *, note, &wm->notifier_queue) {
if (note->reference == reference) {
/* 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 *UNUSED(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)
{
struct wmMsgBus *mbus = user_data;
if (new_id != NULL) {
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 struct 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, NULL);
wmWindowManager *wm = bmain->wm.first;
if (wm && wm->message_bus) {
BKE_id_remapper_iter(mappings, wm_main_remap_msgbus_notify, wm->message_bus);
}
}
static void wm_notifier_clear(wmNotifier *note)
{
/* NULL the entire notifier, only leaving (`next`, `prev`) members intact. */
memset(((char *)note) + sizeof(Link), 0, sizeof(*note) - sizeof(Link));
}
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, NULL);
}
static void wm_event_execute_timers(bContext *C)
{
wmWindowManager *wm = CTX_wm_manager(C);
if (UNLIKELY(wm == NULL)) {
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, wm->windows.first);
BLI_timer_execute();
CTX_wm_window_set(C, NULL);
}
void wm_event_do_notifiers(bContext *C)
{
/* 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 == NULL) {
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 (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 = 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 = 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(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 = 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 == NULL && (ELEM(note->reference, NULL, 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, NULL);
}
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. */
wmNotifier *note;
while ((note = BLI_pophead(&wm->notifier_queue))) {
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_screen_do_listen(C, note);
LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
wmRegionListenerParams region_params = {
.window = win,
.area = NULL,
.region = region,
.scene = scene,
.notifier = note,
};
ED_region_do_listen(&region_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 = {
.window = win,
.area = area,
.notifier = note,
.scene = scene,
};
ED_area_do_listen(&area_params);
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
wmRegionListenerParams region_params = {
.window = win,
.area = area,
.region = region,
.scene = scene,
.notifier = note,
};
ED_region_do_listen(&region_params);
}
}
}
}
MEM_freeN(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, NULL);
}
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, NULL);
}
/* Auto-run warning. */
wm_test_autorun_warning(C);
}
static int 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.
*
* Can't be executed if the handler just loaded a file (typically identified by `CTX_wm_window(C)`
* returning `NULL`), because the event will have been freed then.
*/
BLI_INLINE void wm_event_handler_return_value_check(const wmEvent *event, const int action)
{
BLI_assert_msg(!wm_event_always_pass(event) || (action != WM_HANDLER_BREAK),
"Return value for events that should always pass should never be BREAK.");
UNUSED_VARS_NDEBUG(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, NULL);
CTX_wm_region_set(C, NULL);
CTX_wm_menu_set(C, NULL);
}
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, &region->handlers) {
if (handler_base->type == WM_HANDLER_TYPE_UI) {
wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
BLI_assert(handler->handle_fn != NULL);
wmEvent event;
wm_event_init_from_window(win, &event);
event.type = EVT_BUT_CANCEL;
event.val = reactivate_button ? 0 : 1;
event.flag = 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 = 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, NULL, 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_callocN(sizeof(ReportTimerInfo), "ReportTimerInfo");
wm_reports->reporttimer->customdata = rti;
}
void WM_report_banners_cancel(Main *bmain)
{
wmWindowManager *wm = bmain->wm.first;
BKE_reports_clear(&wm->reports);
WM_event_remove_timer(wm, NULL, 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 = 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);
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
* \{ */
bool WM_operator_poll(bContext *C, wmOperatorType *ot)
{
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
wmOperatorType *ot_macro = WM_operatortype_find(macro->idname, 0);
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, NULL, NULL, context, true, NULL);
}
bool WM_operator_check_ui_empty(wmOperatorType *ot)
{
if (ot->macro.first != NULL) {
/* For macros, check all have exec() we can call. */
LISTBASE_FOREACH (wmOperatorTypeMacro *, macro, &ot->macro) {
wmOperatorType *otm = WM_operatortype_find(macro->idname, 0);
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 == NULL) {
CTX_wm_window_set(C, 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, NULL);
}
/* 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));
}
static void wm_operator_finished(bContext *C, wmOperator *op, const bool repeat, const bool store)
{
wmWindowManager *wm = CTX_wm_manager(C);
enum {
NOP,
SET,
CLEAR,
} hud_status = NOP;
op->customdata = NULL;
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;
}
}
}
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, NULL);
}
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 == NULL || op->type == NULL) {
return retval;
}
if (0 == WM_operator_poll(C, op->type)) {
return retval;
}
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) {
wm_operator_finished(C, op, repeat, store && wm->op_undo_depth == 0);
}
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 == NULL || op->type == NULL || op->type->exec == NULL) {
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 *UNUSED(C), wmOperator *op)
{
if (op->type->exec != NULL) {
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, 0);
if (otm && otm->exec == NULL) {
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 == NULL && op->next == NULL) {
wmWindowManager *wm = CTX_wm_manager(C);
op_prev = 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_callocN(sizeof(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_callocN(sizeof(PointerRNA), "wmOperatorPtrRNA");
if (properties && properties->data) {
op->properties = IDP_CopyProperty(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_mallocN(sizeof(ReportList), "wmOperatorReportList");
BKE_reports_init(op->reports, RPT_STORE | RPT_FREE);
}
/* Recursive filling of operator macro list. */
if (ot->macro.first) {
static wmOperator *motherop = NULL;
int root = 0;
/* Ensure all ops are in execution order in 1 list. */
if (motherop == NULL) {
motherop = op;
root = 1;
}
/* If properties exist, it will contain everything needed. */
if (properties) {
wmOperatorTypeMacro *otmacro = ot->macro.first;
RNA_STRUCT_BEGIN (properties, prop) {
if (otmacro == NULL) {
break;
}
/* Skip invalid properties. */
if (STREQ(RNA_property_identifier(prop), otmacro->idname)) {
wmOperatorType *otm = WM_operatortype_find(otmacro->idname, 0);
PointerRNA someptr = RNA_property_pointer_get(properties, prop);
wmOperator *opm = wm_operator_create(wm, otm, &someptr, NULL);
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, 0);
wmOperator *opm = wm_operator_create(wm, otm, macro->ptr, NULL);
BLI_addtail(&motherop->macro, opm);
opm->opm = motherop; /* Pointer to mom, for modal(). */
}
}
if (root) {
motherop = NULL;
}
}
WM_operator_properties_sanitize(op->ptr, 0);
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)
{
ED_screen_areas_iter (win, screen, area) {
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->gizmo_map != NULL) {
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 NULL.
*/
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);
/* If `reports == NULL`, 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 != NULL) {
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 == NULL) || (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) {
/* Temporarily write into `mval` (not technically `const` correct) but this is restored. */
int mval_prev[2] = {UNPACK2(event->mval)};
wm_region_mouse_co(C, (wmEvent *)event);
if (op->type->flag & OPTYPE_UNDO) {
wm->op_undo_depth++;
}
retval = op->type->invoke(C, op, event);
OPERATOR_RETVAL_CHECK(retval);
if (op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) {
wm->op_undo_depth--;
}
copy_v2_v2_int(((wmEvent *)event)->mval, mval_prev);
}
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 != NULL));
}
if (retval & OPERATOR_HANDLED) {
/* Do nothing, #wm_operator_exec() has been called somewhere. */
}
else if (retval & OPERATOR_FINISHED) {
const bool store = !is_nested_call && use_last_properties;
wm_operator_finished(C, op, false, store);
}
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)) {
int bounds[4] = {-1, -1, -1, -1};
int wrap = WM_CURSOR_WRAP_NONE;
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) {
const rcti *winrect = NULL;
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(&region->winrct, event->xy)) {
winrect = &region->winrct;
}
else if (area && BLI_rcti_isect_pt_v(&area->totrct, event->xy)) {
winrect = &area->totrct;
}
if (winrect) {
bounds[0] = winrect->xmin;
bounds[1] = winrect->ymax;
bounds[2] = winrect->xmax;
bounds[3] = winrect->ymin;
}
}
WM_cursor_grab_enable(CTX_wm_window(C), wrap, false, bounds);
}
/* 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 == NULL) {
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 == NULL) {
if (poll_only) {
CTX_wm_operator_poll_msg_set(C, "Missing 'window' in context");
}
return 0;
}
else {
event = window->eventstate;
}
break;
default:
event = NULL;
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 = NULL;
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, NULL);
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, NULL);
CTX_wm_area_set(C, NULL);
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, NULL, 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, 0);
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, 0);
if (!ot) {
return false;
}
return WM_operator_poll(C, ot);
}
int WM_operator_name_call_with_properties(struct bContext *C,
const char *opstring,
wmOperatorCallContext context,
struct IDProperty *properties,
const wmEvent *event)
{
PointerRNA props_ptr;
wmOperatorType *ot = WM_operatortype_find(opstring, false);
RNA_pointer_create(G_MAIN->wm.first, 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, context, &ptr, NULL);
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, NULL);
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.
* \{ */
typedef struct uiOperatorWaitForInput {
ScrArea *area;
wmOperatorCallParams optype_params;
bContextStore *context;
} uiOperatorWaitForInput;
static void ui_handler_wait_for_input_remove(bContext *C, void *userdata)
{
uiOperatorWaitForInput *opwait = userdata;
if (opwait->optype_params.opptr) {
if (opwait->optype_params.opptr->data) {
IDP_FreeProperty(opwait->optype_params.opptr->data);
}
MEM_freeN(opwait->optype_params.opptr);
}
if (opwait->context) {
CTX_store_free(opwait->context);
}
if (opwait->area != NULL) {
ED_area_status_text(opwait->area, NULL);
}
else {
ED_workspace_status_text(C, NULL);
}
MEM_freeN(opwait);
}
static int ui_handler_wait_for_input(bContext *C, const wmEvent *event, void *userdata)
{
uiOperatorWaitForInput *opwait = 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, NULL);
}
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, 0);
if (otm != NULL) {
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) : NULL;
{
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 != NULL) {
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_callocN(sizeof(*opwait), __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_mallocN(sizeof(*opwait->optype_params.opptr), __func__);
*opwait->optype_params.opptr = *properties;
if (properties->data != NULL) {
opwait->optype_params.opptr->data = IDP_CopyProperty(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);
}
/* Only set context when area/region is part of screen. */
static void wm_handler_op_context(bContext *C, wmEventHandler_Op *handler, const wmEvent *event)
{
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);
if (screen == NULL || handler->op == NULL) {
return;
}
if (handler->context.area == NULL) {
CTX_wm_area_set(C, NULL);
}
else {
ScrArea *area = NULL;
ED_screen_areas_iter (win, screen, area_iter) {
if (area_iter == handler->context.area) {
area = area_iter;
break;
}
}
if (area == NULL) {
/* When changing screen layouts with running modal handlers (like render display), this
* is not an error to print. */
if (handler->op == NULL) {
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) : NULL;
CTX_wm_area_set(C, 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 = NULL;
}
if (region == NULL) {
LISTBASE_FOREACH (ARegion *, region_iter, &area->regionbase) {
region = region_iter;
if (region == handler->context.region) {
break;
}
}
}
/* No warning print here, after full-area and back regions are remade. */
if (region) {
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 = 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, NULL);
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. */
/* Not using #ISTEXTINPUT anymore because (at least on Windows) some key codes above 255
* could have printable ascii keys, See T30479. */
if (ISKEYBOARD(winevent->type) && (winevent->ascii || 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 != 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 != 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 != 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 != 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 == NULL) || (keymap->poll_modal_item(op, kmi->propvalue))) {
return kmi;
}
}
}
return NULL;
}
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,
struct 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 = NULL;
const wmEvent *event_match = NULL;
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 != NULL) {
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 struct 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 != NULL)) {
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);
struct wmEvent_ModalMapStore event_backup;
wm_event_modalkeymap_begin(C, op, event, &event_backup);
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 NULL-ing the context members. */
if (retval & OPERATOR_FINISHED) {
wm_operator_finished(C, op, false, true);
handler->op = NULL;
}
else if (retval & (OPERATOR_CANCELLED | OPERATOR_FINISHED)) {
WM_operator_free(op);
handler->op = NULL;
}
/* 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, NULL);
CTX_wm_region_set(C, NULL);
}
/* 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), NULL);
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, 0);
if (ot && wm_operator_check_locked_interface(C, ot)) {
bool use_last_properties = true;
PointerRNA tool_properties = {0};
bToolRef *keymap_tool = NULL;
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 != NULL);
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, NULL, 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 != NULL) {
if ((gzgt->flag & WM_GIZMOGROUPTYPE_TOOL_INIT) != 0) {
ARegion *region = CTX_wm_region(C);
if (region != NULL) {
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;
}
/**
* 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);
/* 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 = NULL;
}
}
else {
ScrArea *ctx_area = CTX_wm_area(C);
wmWindow *temp_win = NULL;
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
bScreen *screen = WM_window_get_active_screen(win);
ScrArea *file_area = 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(file_area->spacedata.first, win_size, is_maximized);
if (BLI_listbase_is_single(&file_area->spacedata)) {
BLI_assert(ctx_win != win);
wm_window_close(C, wm, win);
CTX_wm_window_set(C, ctx_win); /* #wm_window_close() NULLs. */
/* Some operators expect a drawable context (for #EVT_FILESELECT_EXEC). */
wm_window_make_drawable(wm, ctx_win);
/* Ensure correct cursor position, otherwise, popups may close immediately after
* opening (#UI_BLOCK_MOVEMOUSE_QUIT). */
wm_cursor_position_get(
ctx_win, &ctx_win->eventstate->xy[0], &ctx_win->eventstate->xy[1]);
wm->winactive = ctx_win; /* Reports use this... */
if (handler->context.win == win) {
handler->context.win = NULL;
}
}
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(ctx_area->spacedata.first, NULL, false);
ED_screen_full_prevspace(C, ctx_area);
}
}
wm_handler_op_context(C, handler, ctx_win->eventstate);
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 == NULL) {
CTX_wm_window_set(C, 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(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(handler->op);
}
CTX_wm_area_set(C, NULL);
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 int 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 = NULL;
if (kmi->properties != NULL) {
wmOperatorType *ot = WM_operatortype_find(kmi->idname, 0);
if (ot) {
kmi_props = RNA_pointer_as_string_keywords(C, kmi->ptr, false, false, true, 512);
}
else { /* Fallback. */
kmi_props = IDP_reprN(kmi->properties, NULL);
}
}
BLI_snprintf(buf, buf_maxlen, "%s(%s)", kmi->idname, kmi_props ? kmi_props : "");
if (kmi_props != NULL) {
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 == NULL) {
/* Only callback is allowed to have NULL 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)) {
struct 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 != NULL) {
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, NULL);
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 != NULL);
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 != NULL && 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, NULL, 0);
}
}
return action;
}
}
struct {
wmGizmo *gz_modal;
wmGizmo *gz;
int part;
} prev = {
.gz_modal = wm_gizmomap_modal_get(gzmap),
.gz = gz,
.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 == NULL) &&
((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 != NULL) {
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 != NULL) && (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, 0);
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 = NULL;
/* 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, NULL);
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). */
!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE);
wmWindowManager *wm = CTX_wm_manager(C);
int action = WM_HANDLER_CONTINUE;
if (handlers == NULL) {
wm_event_handler_return_value_check(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 = 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 == NULL || 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) != NULL) {
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 != NULL);
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 = {0};
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, NULL, 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) == NULL) {
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, NULL, NULL);
}
}
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) == NULL) {
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. */
if (CTX_wm_window(C) != NULL) {
wm_event_handler_return_value_check(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 NULL in the file read case. */
wmWindow *win = CTX_wm_window(C);
if (win == NULL) {
return action;
}
if (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
/* 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->prev_press_type == event->type) {
if (event->val == KM_RELEASE) {
if (event->prev_val == KM_PRESS) {
if (win->event_queue_check_click == true) {
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. */
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(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 NULL;
}
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(&region->winrct, xy)) {
return region;
}
}
}
return NULL;
}
static void wm_paintcursor_tag(bContext *C, wmPaintCursor *pc, ARegion *region)
{
if (region) {
for (; pc; pc = pc->next) {
if (pc->poll == NULL || 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, wm->paintcursors.first, region);
}
/* If previous position was not in current region, we have to set a temp new context. */
if (region == NULL || !BLI_rcti_isect_pt_v(&region->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, 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.c`) */
// 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 = event->customdata;
if (actiondata->ot->modal && event->val == KM_RELEASE) {
/* Don't execute modal operators on release. */
}
else {
PointerRNA properties = {.type = actiondata->ot->srna, .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 : NULL,
NULL,
false,
false);
}
else {
/* Execute operator. */
wmOperator *op = wm_operator_create(
wm, actiondata->ot, actiondata->op_properties ? &properties : NULL, NULL);
if ((WM_operator_call(C, op) & OPERATOR_HANDLED) == 0) {
WM_operator_free(op);
}
}
}
}
CTX_wm_region_set(C, NULL);
CTX_wm_area_set(C, NULL);
}
#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, &region->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(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));
/* 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 == NULL) {
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 != NULL) ? DEG_get_evaluated_scene(depsgraph) : NULL;
if (scene_eval != NULL) {
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) != NULL);
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, NULL);
}
}
}
}
}
}
wmEvent *event;
while ((event = 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) &&
!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
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 (!ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
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 (ELEM(event->type, MOUSEMOVE, INBETWEEN_MOUSEMOVE)) {
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) == NULL) {
wm_event_free_and_remove_from_queue_if_valid(event);
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 == true) {
/* 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) == NULL) {
wm_event_free_and_remove_from_queue_if_valid(event);
return;
}
CTX_wm_region_set(C, NULL);
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, NULL);
/* 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) == NULL) {
wm_event_free_and_remove_from_queue_if_valid(event);
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, screen);
}
/* 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 = 0;
wm_event_add(win, &tevent);
win->addmousemove = 0;
}
CTX_wm_window_set(C, NULL);
}
/* Update key configuration after handling events. */
WM_keyconfig_update(wm);
WM_gizmoconfig_update(CTX_data_main(C));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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.customdata = ophandle; /* Only as void pointer type check. */
wm_event_add(win, &event);
}
}
/* 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 *win = CTX_wm_window(C);
const bool is_temp_screen = WM_window_is_temp_screen(win);
/* Close any popups, like when opening a file browser from the splash. */
UI_popup_handlers_remove_all(C, &win->modalhandlers);
if (!is_temp_screen) {
/* Only allow 1 file selector open per window. */
LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, &win->modalhandlers) {
if (handler_base->type == WM_HANDLER_TYPE_OP) {
wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base;
if (handler->is_fileselect == false) {
continue;
}
ScrArea *file_area = ED_fileselect_handler_area_find(win, handler->op);
if (file_area) {
CTX_wm_area_set(C, file_area);
wm_handler_fileselect_do(C, &win->modalhandlers, handler, EVT_FILESELECT_CANCEL);
}
/* If not found we stop the handler without changing the screen. */
else {
wm_handler_fileselect_do(
C, &win->modalhandlers, handler, EVT_FILESELECT_EXTERNAL_CANCEL);
}
}
}
}
wmEventHandler_Op *handler = MEM_callocN(sizeof(*handler), __func__);
handler->head.type = WM_HANDLER_TYPE_OP;
handler->is_fileselect = true;
handler->op = op;
handler->context.win = CTX_wm_window(C);
handler->context.area = CTX_wm_area(C);
handler->context.region = CTX_wm_region(C);
BLI_addhead(&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);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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_callocN(sizeof(*handler), __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 : RGN_TYPE_WINDOW;
}
}
}
}
wmEventHandler_Keymap *WM_event_add_keymap_handler(ListBase *handlers, wmKeyMap *keymap)
{
if (!keymap) {
CLOG_WARN(WM_LOG_HANDLERS, "called with NULL key-map");
return NULL;
}
/* 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_callocN(sizeof(*handler), __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 NULL, 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 NULL checks in run-time code since any crashes here are difficult to redo. */
BLI_assert_msg(win != NULL, "The window should always be set for tool interactions!");
const Scene *scene = win ? win->scene : NULL;
ScrArea *area = handler->dynamic.user_data;
handler->keymap_tool = NULL;
bToolRef_Runtime *tref_rt = area->runtime.tool ? area->runtime.tool->runtime : NULL;
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 = NULL;
wmGizmoGroup *gzgroup = NULL;
LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
if (region->gizmo_map != NULL) {
gzmap = region->gizmo_map;
gzgroup = WM_gizmomap_group_find(gzmap, tref_rt->gizmo_group);
if (gzgroup != NULL) {
break;
}
}
}
if (gzgroup != NULL) {
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 == NULL) {
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);
}
struct 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 NULL keymap_fn");
return NULL;
}
/* 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_callocN(sizeof(*handler), __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 UNUSED(priority))
{
WM_event_remove_keymap_handler(handlers, keymap);
wmEventHandler_Keymap *handler = MEM_callocN(sizeof(*handler), "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 == NULL) {
return NULL;
}
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 char flag)
{
wmEventHandler_UI *handler = MEM_callocN(sizeof(*handler), __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 = NULL;
handler->context.region = NULL;
handler->context.menu = NULL;
}
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_callocN(sizeof(*handler), __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);
}
}
}
}
#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_kKeyOS:
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;
}
}
}
static const wmTabletData wm_event_tablet_data_default = {
.active = EVT_TABLET_NONE,
.pressure = 1.0f,
.x_tilt = 0.0f,
.y_tilt = 0.0f,
.is_motion_absolute = false,
};
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 != NULL) && 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_mallocN(sizeof(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)
{
int mval[2] = {event->xy[0], event->xy[1]};
if (wm->windows.first == wm->windows.last) {
return NULL;
}
/* In order to use window size and mouse position (pixels), we have to use a WM function. */
/* Check if outside, include top window bar. */
if (mval[0] < 0 || mval[1] < 0 || mval[0] > WM_window_pixels_x(win) ||
mval[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 NULL;
}
}
wmWindow *win_other = WM_window_find_under_cursor(win, mval, mval);
if (win_other && win_other != win) {
copy_v2_v2_int(event->xy, mval);
return win_other;
}
}
return NULL;
}
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(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 = 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 = 0;
}
wmEvent *event_new = wm_event_add(win, event);
if (event_last == NULL) {
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 = 0;
tevent.ascii = '\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 = 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.
*/
static void wm_event_state_update_and_click_set(const GHOST_TEventType type,
wmEvent *event,
wmEvent *event_state)
{
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 (ELEM(type, GHOST_kEventKeyDown, GHOST_kEventKeyUp)) {
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 (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);
}
}
}
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 = 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;
/* 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 = 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;
/* 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 = 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 = 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(type, &event, event_state);
/* 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;
/* 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 = customdata;
event.type = convert_key(kd->key);
if (UNLIKELY(event.type == EVENT_NONE)) {
break;
}
event.ascii = kd->ascii;
/* 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) {
event.ascii = '\0';
/* 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.ascii < 32 && event.ascii > 0) {
event.ascii = '\0';
}
if (event.utf8_buf[0] < 32 && event.utf8_buf[0] > 0) {
event.utf8_buf[0] = '\0';
}
}
if (event.utf8_buf[0]) {
if (BLI_str_utf8_size(event.utf8_buf) == -1) {
CLOG_ERROR(WM_LOG_EVENTS,
"ghost detected an invalid unicode character '%d'",
(int)(unsigned char)event.utf8_buf[0]);
event.utf8_buf[0] = '\0';
}
}
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(type, &event, event_state);
/* 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;
}
wm_event_add(win, &event);
break;
}
case GHOST_kEventWheel: {
GHOST_TEventWheelData *wheelData = 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, 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 = customdata;
event.type = NDOF_BUTTON_NONE + 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 = NULL;
wm_event_state_update_and_click_set(type, &event, event_state);
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 = 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 = {
.type = EVT_XR_ACTION,
.val = val,
.flag = 0,
.custom = EVT_DATA_XR,
.customdata = actiondata,
.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 != NULL) {
handler->dynamic.keymap_fn(wm, win, handler, km_result);
BLI_assert(handler->keymap == NULL);
}
else {
memset(km_result, 0x0, sizeof(*km_result));
wmKeyMap *keymap = WM_keymap_active(wm, handler->keymap);
BLI_assert(keymap != NULL);
if (keymap != NULL) {
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, 0);
if (WM_operator_poll_context(C, ot, WM_OP_INVOKE_DEFAULT)) {
return kmi;
}
}
}
return NULL;
}
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 == NULL || 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 != NULL) {
return kmi;
}
}
}
}
}
}
return NULL;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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;
struct CursorKeymapInfo_State state;
};
static void wm_event_cursor_store(struct 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){0};
}
const char *WM_window_cursor_keymap_status_get(const wmWindow *win,
int button_index,
int type_index)
{
if (win->cursor_keymap_status != NULL) {
struct CursorKeymapInfo *cd = win->cursor_keymap_status;
const char *msg = cd->text[button_index][type_index];
if (*msg) {
return msg;
}
}
return NULL;
}
ScrArea *WM_window_status_area_find(wmWindow *win, bScreen *screen)
{
if (screen->state == SCREENFULL) {
return NULL;
}
ScrArea *area_statusbar = NULL;
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 != NULL) {
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 == NULL) {
MEM_SAFE_FREE(win->cursor_keymap_status);
return;
}
struct CursorKeymapInfo *cd;
if (UNLIKELY(win->cursor_keymap_status == NULL)) {
win->cursor_keymap_status = MEM_callocN(sizeof(struct CursorKeymapInfo), __func__);
}
cd = 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. */
struct CursorKeymapInfo cd_prev = *((struct CursorKeymapInfo *)win->cursor_keymap_status);
cd->state_event = *win->eventstate;
/* Find active region and associated area. */
ARegion *region = screen->active_region;
if (region == NULL) {
return;
}
ScrArea *area = NULL;
ED_screen_areas_iter (win, screen, area_iter) {
if (BLI_findindex(&area_iter->regionbase, region) != -1) {
area = area_iter;
break;
}
}
if (area == NULL) {
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 = NULL;
if ((region->regiontype == RGN_TYPE_WINDOW) &&
((1 << area->spacetype) & WM_TOOLSYSTEM_SPACE_MASK)) {
ViewLayer *view_layer = WM_window_get_active_view_layer(win);
WorkSpace *workspace = WM_window_get_active_workspace(win);
const bToolKey tkey = {
.space_type = area->spacetype,
.mode = WM_toolsystem_mode_from_spacetype(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[] = {
&region->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;
wm_eventemulation(&test_event, true);
wmKeyMapItem *kmi = NULL;
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, 0);
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, NULL);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \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 = NULL;
wmOperator *op = NULL;
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 != NULL) {
/* '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 == NULL || keymap->modal_items == NULL) {
return false;
}
const EnumPropertyItem *items = 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 != NULL) &&
(keymap->poll_modal_item(op, items[i].value) == false)) {
continue;
}
bool show_text = true;
{
/* Warning: O(n^2). */
wmKeyMapItem *kmi = NULL;
for (kmi = keymap->items.first; kmi; kmi = kmi->next) {
if (kmi->propvalue == items[i].value) {
break;
}
}
if (kmi != NULL) {
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;
}
/** \} */