Changeset View
Changeset View
Standalone View
Standalone View
source/blender/windowmanager/intern/wm_event_system.c
| Show First 20 Lines • Show All 1,898 Lines • ▼ Show 20 Lines | |||||
| * \{ */ | * \{ */ | ||||
| void wm_event_free_handler(wmEventHandler *handler) | void wm_event_free_handler(wmEventHandler *handler) | ||||
| { | { | ||||
| /* Future extra custom-data free? */ | /* Future extra custom-data free? */ | ||||
| MEM_freeN(handler); | 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) | * Check if the area and/or region are actually part of the screen, and return them if so. | ||||
| */ | |||||
| static void wm_handler_op_context_get(bContext *C, | |||||
| wmEventHandler_Op *handler, | |||||
| const wmEvent *event, | |||||
| ScrArea **r_area, | |||||
| ARegion **r_region) | |||||
| { | { | ||||
| wmWindow *win = handler->context.win ? handler->context.win : CTX_wm_window(C); | 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 | /* 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 | * code has been getting it through context since forever, so play safe and stick to that when | ||||
| * possible. */ | * possible. */ | ||||
| bScreen *screen = handler->context.win ? WM_window_get_active_screen(win) : CTX_wm_screen(C); | bScreen *screen = handler->context.win ? WM_window_get_active_screen(win) : CTX_wm_screen(C); | ||||
| *r_area = NULL; | |||||
| *r_region = NULL; | |||||
| if (screen == NULL || handler->op == NULL) { | if (screen == NULL || handler->op == NULL) { | ||||
| return; | return; | ||||
| } | } | ||||
| if (handler->context.area == NULL) { | if (handler->context.area == NULL) { | ||||
| CTX_wm_area_set(C, NULL); | /* Pass */ | ||||
| } | } | ||||
| else { | else { | ||||
| ScrArea *area = NULL; | ScrArea *area = NULL; | ||||
| ED_screen_areas_iter (win, screen, area_iter) { | ED_screen_areas_iter (win, screen, area_iter) { | ||||
| if (area_iter == handler->context.area) { | if (area_iter == handler->context.area) { | ||||
| area = area_iter; | area = area_iter; | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| if (area == NULL) { | if (area == NULL) { | ||||
| /* When changing screen layouts with running modal handlers (like render display), this | /* When changing screen layouts with running modal handlers (like render display), this | ||||
| * is not an error to print. */ | * is not an error to print. */ | ||||
| if (handler->op == NULL) { | if (handler->op == NULL) { | ||||
| CLOG_ERROR(WM_LOG_HANDLERS, | CLOG_ERROR(WM_LOG_HANDLERS, | ||||
| "internal error: handler (%s) has invalid area", | "internal error: handler (%s) has invalid area", | ||||
| handler->op->type->idname); | handler->op->type->idname); | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| ARegion *region; | ARegion *region; | ||||
| wmOperator *op = handler->op ? (handler->op->opm ? handler->op->opm : handler->op) : NULL; | wmOperator *op = handler->op ? (handler->op->opm ? handler->op->opm : handler->op) : NULL; | ||||
| CTX_wm_area_set(C, area); | *r_area = area; | ||||
| if (op && (op->flag & OP_IS_MODAL_CURSOR_REGION)) { | if (op && (op->flag & OP_IS_MODAL_CURSOR_REGION)) { | ||||
| region = BKE_area_find_region_xy(area, handler->context.region_type, event->xy); | region = BKE_area_find_region_xy(area, handler->context.region_type, event->xy); | ||||
| if (region) { | if (region) { | ||||
| handler->context.region = region; | handler->context.region = region; | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| region = NULL; | region = NULL; | ||||
| } | } | ||||
| if (region == NULL) { | if (region == NULL) { | ||||
| LISTBASE_FOREACH (ARegion *, region_iter, &area->regionbase) { | LISTBASE_FOREACH (ARegion *, region_iter, &area->regionbase) { | ||||
| region = region_iter; | region = region_iter; | ||||
| if (region == handler->context.region) { | if (region == handler->context.region) { | ||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /* No warning print here, after full-area and back regions are remade. */ | /* No warning print here, after full-area and back regions are remade. */ | ||||
| if (region) { | if (region) { | ||||
| CTX_wm_region_set(C, region); | *r_region = region; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| static void wm_handler_op_context(bContext *C, wmEventHandler_Op *handler, const wmEvent *event) | |||||
| { | |||||
| ScrArea *area = NULL; | |||||
| ARegion *region = NULL; | |||||
| wm_handler_op_context_get(C, handler, event, &area, ®ion); | |||||
| CTX_wm_area_set(C, area); | |||||
| CTX_wm_region_set(C, region); | |||||
| } | |||||
| void WM_event_remove_handlers(bContext *C, ListBase *handlers) | void WM_event_remove_handlers(bContext *C, ListBase *handlers) | ||||
| { | { | ||||
| wmWindowManager *wm = CTX_wm_manager(C); | wmWindowManager *wm = CTX_wm_manager(C); | ||||
| /* C is zero on freeing database, modal handlers then already were freed. */ | /* C is zero on freeing database, modal handlers then already were freed. */ | ||||
| wmEventHandler *handler_base; | wmEventHandler *handler_base; | ||||
| while ((handler_base = BLI_pophead(handlers))) { | while ((handler_base = BLI_pophead(handlers))) { | ||||
| BLI_assert(handler_base->type != 0); | BLI_assert(handler_base->type != 0); | ||||
| ▲ Show 20 Lines • Show All 520 Lines • ▼ Show 20 Lines | case EVT_FILESELECT_FULL_OPEN: { | ||||
| action = WM_HANDLER_BREAK; | action = WM_HANDLER_BREAK; | ||||
| break; | break; | ||||
| } | } | ||||
| case EVT_FILESELECT_EXEC: | case EVT_FILESELECT_EXEC: | ||||
| case EVT_FILESELECT_CANCEL: | case EVT_FILESELECT_CANCEL: | ||||
| case EVT_FILESELECT_EXTERNAL_CANCEL: { | case EVT_FILESELECT_EXTERNAL_CANCEL: { | ||||
| wmWindow *ctx_win = CTX_wm_window(C); | wmWindow *ctx_win = CTX_wm_window(C); | ||||
| wmEvent *eventstate = ctx_win->eventstate; | |||||
| /* The root window of the operation as determined in #WM_event_add_fileselect(). */ | |||||
| wmWindow *root_win = handler->context.win; | |||||
Severin: A further improvement could be splitting the file-select handler off from the regular operator… | |||||
| /* Remove link now, for load file case before removing. */ | /* Remove link now, for load file case before removing. */ | ||||
| BLI_remlink(handlers, handler); | BLI_remlink(handlers, handler); | ||||
| if (val == EVT_FILESELECT_EXTERNAL_CANCEL) { | if (val == EVT_FILESELECT_EXTERNAL_CANCEL) { | ||||
| /* The window might have been freed already. */ | /* The window might have been freed already. */ | ||||
| if (BLI_findindex(&wm->windows, handler->context.win) == -1) { | if (BLI_findindex(&wm->windows, handler->context.win) == -1) { | ||||
| handler->context.win = NULL; | handler->context.win = NULL; | ||||
| Show All 19 Lines | case EVT_FILESELECT_EXTERNAL_CANCEL: { | ||||
| } | } | ||||
| int win_size[2]; | int win_size[2]; | ||||
| bool is_maximized; | bool is_maximized; | ||||
| ED_fileselect_window_params_get(win, win_size, &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); | ED_fileselect_params_to_userdef(file_area->spacedata.first, win_size, is_maximized); | ||||
| if (BLI_listbase_is_single(&file_area->spacedata)) { | if (BLI_listbase_is_single(&file_area->spacedata)) { | ||||
| BLI_assert(ctx_win != win); | BLI_assert(root_win != win); | ||||
| wm_window_close(C, wm, win); | wm_window_close(C, wm, win); | ||||
| CTX_wm_window_set(C, ctx_win); /* #wm_window_close() NULLs. */ | CTX_wm_window_set(C, root_win); /* #wm_window_close() NULLs. */ | ||||
| /* Some operators expect a drawable context (for #EVT_FILESELECT_EXEC). */ | /* Some operators expect a drawable context (for #EVT_FILESELECT_EXEC). */ | ||||
| wm_window_make_drawable(wm, ctx_win); | wm_window_make_drawable(wm, root_win); | ||||
| /* Ensure correct cursor position, otherwise, popups may close immediately after | /* Ensure correct cursor position, otherwise, popups may close immediately after | ||||
| * opening (#UI_BLOCK_MOVEMOUSE_QUIT). */ | * opening (#UI_BLOCK_MOVEMOUSE_QUIT). */ | ||||
| wm_cursor_position_get( | wm_cursor_position_get(root_win, &eventstate->xy[0], &eventstate->xy[1]); | ||||
| ctx_win, &ctx_win->eventstate->xy[0], &ctx_win->eventstate->xy[1]); | wm->winactive = root_win; /* Reports use this... */ | ||||
| wm->winactive = ctx_win; /* Reports use this... */ | |||||
| if (handler->context.win == win) { | |||||
| handler->context.win = NULL; | |||||
| } | |||||
| } | } | ||||
| else if (file_area->full) { | else if (file_area->full) { | ||||
| ED_screen_full_prevspace(C, file_area); | ED_screen_full_prevspace(C, file_area); | ||||
| } | } | ||||
| else { | else { | ||||
| ED_area_prevspace(C, file_area); | ED_area_prevspace(C, file_area); | ||||
| } | } | ||||
| temp_win = win; | temp_win = win; | ||||
| break; | break; | ||||
| } | } | ||||
| if (!temp_win && ctx_area->full) { | if (!temp_win && ctx_area->full) { | ||||
| ED_fileselect_params_to_userdef(ctx_area->spacedata.first, NULL, false); | ED_fileselect_params_to_userdef(ctx_area->spacedata.first, NULL, false); | ||||
| ED_screen_full_prevspace(C, ctx_area); | ED_screen_full_prevspace(C, ctx_area); | ||||
| } | } | ||||
| } | } | ||||
| wm_handler_op_context(C, handler, ctx_win->eventstate); | CTX_wm_window_set(C, root_win); | ||||
| wm_handler_op_context(C, handler, eventstate); | |||||
| /* At this point context is supposed to match the root context determined by | |||||
| * #WM_event_add_fileselect(). */ | |||||
| BLI_assert(!CTX_wm_area(C) || (CTX_wm_area(C) == handler->context.area)); | |||||
| BLI_assert(!CTX_wm_region(C) || (CTX_wm_region(C) == handler->context.region)); | |||||
| ScrArea *handler_area = CTX_wm_area(C); | ScrArea *handler_area = CTX_wm_area(C); | ||||
| /* Make sure new context area is ready, the operator callback may operate on it. */ | /* Make sure new context area is ready, the operator callback may operate on it. */ | ||||
| if (handler_area) { | if (handler_area) { | ||||
| ED_area_do_refresh(C, handler_area); | ED_area_do_refresh(C, handler_area); | ||||
| } | } | ||||
| /* Needed for #UI_popup_menu_reports. */ | /* Needed for #UI_popup_menu_reports. */ | ||||
| if (val == EVT_FILESELECT_EXEC) { | if (val == EVT_FILESELECT_EXEC) { | ||||
| int retval; | int retval; | ||||
| /* FIXME this should poll here! */ | |||||
Done Inline ActionsWant to do this separately. Severin: Want to do this separately. | |||||
| if (handler->op->type->flag & OPTYPE_UNDO) { | if (handler->op->type->flag & OPTYPE_UNDO) { | ||||
| wm->op_undo_depth++; | wm->op_undo_depth++; | ||||
| } | } | ||||
| retval = handler->op->type->exec(C, handler->op); | retval = handler->op->type->exec(C, handler->op); | ||||
| /* XXX check this carefully, `CTX_wm_manager(C) == wm` is a bit hackish. */ | /* 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) { | if (handler->op->type->flag & OPTYPE_UNDO && CTX_wm_manager(C) == wm) { | ||||
| ▲ Show 20 Lines • Show All 1,384 Lines • ▼ Show 20 Lines | LISTBASE_FOREACH (wmWindow *, win, &wm->windows) { | ||||
| event.type = EVT_FILESELECT; | event.type = EVT_FILESELECT; | ||||
| event.val = eventval; | event.val = eventval; | ||||
| event.customdata = ophandle; /* Only as void pointer type check. */ | event.customdata = ophandle; /* Only as void pointer type check. */ | ||||
| wm_event_add(win, &event); | wm_event_add(win, &event); | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * From the currently active window (as defined by context), try to find a window that is | |||||
| * appropriate for use as root window of a modal File Browser. The root window will be the parent | |||||
| * of the File Browser and it will be used to execute the file operator after closing the File | |||||
| * Browser. | |||||
| * | |||||
| * An appropriate window is either of the following: | |||||
| * * A window that does not yet contain a modal File Browser. This is checked using | |||||
| * #ED_fileselect_handler_area_find_any(). | |||||
| * * A window with a maximized/full-screen. It should not be possible to maximize a temporary | |||||
| * window like the modal File Browser, so this way we can find windows where a File Browser was | |||||
| * opened from using #USER_TEMP_SPACE_DISPLAY_FULLSCREEN. | |||||
| * | |||||
| * If no window can be found using the active window from context, return the first registered | |||||
| * window (which can be assumed to be a regular window, e.g. no modal File Browser). | |||||
| */ | |||||
| static wmWindow *wm_event_find_fileselect_root_window_from_context(const bContext *C) | |||||
| { | |||||
| wmWindow *ctx_win = CTX_wm_window(C); | |||||
| for (wmWindow *ctx_win_or_parent = ctx_win; ctx_win_or_parent; | |||||
| ctx_win_or_parent = ctx_win_or_parent->parent) { | |||||
| ScrArea *file_area = ED_fileselect_handler_area_find_any(ctx_win_or_parent); | |||||
| if (!file_area) { | |||||
| return ctx_win_or_parent; | |||||
| } | |||||
| if (file_area->full) { | |||||
| return ctx_win_or_parent; | |||||
| } | |||||
| } | |||||
| /* Fallback to the first window. */ | |||||
| const wmWindowManager *wm = CTX_wm_manager(C); | |||||
| BLI_assert(!ED_fileselect_handler_area_find_any(wm->windows.first)); | |||||
| return wm->windows.first; | |||||
| } | |||||
| /* Operator is supposed to have a filled "path" property. */ | /* Operator is supposed to have a filled "path" property. */ | ||||
| /* Optional property: file-type (XXX enum?) */ | /* Optional property: file-type (XXX enum?) */ | ||||
| void WM_event_add_fileselect(bContext *C, wmOperator *op) | void WM_event_add_fileselect(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| wmWindowManager *wm = CTX_wm_manager(C); | wmWindowManager *wm = CTX_wm_manager(C); | ||||
| wmWindow *win = CTX_wm_window(C); | wmWindow *ctx_win = CTX_wm_window(C); | ||||
| const bool is_temp_screen = WM_window_is_temp_screen(win); | |||||
| /* The following vars define the root context. That is essentially the "parent" context of the | |||||
| * File Browser operation, to be restored for eventually executing the file operation. */ | |||||
| wmWindow *root_win = wm_event_find_fileselect_root_window_from_context(C); | |||||
| /* Determined later. */ | |||||
| ScrArea *root_area = NULL; | |||||
| ARegion *root_region = NULL; | |||||
| /* Close any popups, like when opening a file browser from the splash. */ | /* Close any popups, like when opening a file browser from the splash. */ | ||||
| UI_popup_handlers_remove_all(C, &win->modalhandlers); | UI_popup_handlers_remove_all(C, &root_win->modalhandlers); | ||||
| CTX_wm_window_set(C, root_win); | |||||
| if (!is_temp_screen) { | /* The root window may already have a File Browser open. Cancel it if so, only 1 should be open | ||||
| /* Only allow 1 file selector open per window. */ | * per window. The root context of this operation is also used for the new operation. */ | ||||
| LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, &win->modalhandlers) { | LISTBASE_FOREACH_MUTABLE (wmEventHandler *, handler_base, &root_win->modalhandlers) { | ||||
| if (handler_base->type == WM_HANDLER_TYPE_OP) { | if (handler_base->type == WM_HANDLER_TYPE_OP) { | ||||
| wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base; | wmEventHandler_Op *handler = (wmEventHandler_Op *)handler_base; | ||||
| if (handler->is_fileselect == false) { | if (handler->is_fileselect == false) { | ||||
| continue; | continue; | ||||
| } | } | ||||
| ScrArea *file_area = ED_fileselect_handler_area_find(win, handler->op); | wm_handler_op_context_get(C, handler, ctx_win->eventstate, &root_area, &root_region); | ||||
| ScrArea *file_area = ED_fileselect_handler_area_find(root_win, handler->op); | |||||
| if (file_area) { | if (file_area) { | ||||
| CTX_wm_area_set(C, file_area); | CTX_wm_area_set(C, file_area); | ||||
| wm_handler_fileselect_do(C, &win->modalhandlers, handler, EVT_FILESELECT_CANCEL); | wm_handler_fileselect_do(C, &root_win->modalhandlers, handler, EVT_FILESELECT_CANCEL); | ||||
| } | } | ||||
| /* If not found we stop the handler without changing the screen. */ | /* If not found we stop the handler without changing the screen. */ | ||||
| else { | else { | ||||
| wm_handler_fileselect_do( | wm_handler_fileselect_do( | ||||
| C, &win->modalhandlers, handler, EVT_FILESELECT_EXTERNAL_CANCEL); | C, &root_win->modalhandlers, handler, EVT_FILESELECT_EXTERNAL_CANCEL); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| BLI_assert(root_win != NULL); | |||||
| /* When not reusing the root context from a previous file browsing operation, use the current | |||||
| * area & region, if they are inside the root window. */ | |||||
| if (!root_area && ctx_win == root_win) { | |||||
| root_area = CTX_wm_area(C); | |||||
| root_region = CTX_wm_region(C); | |||||
| } | } | ||||
| wmEventHandler_Op *handler = MEM_callocN(sizeof(*handler), __func__); | wmEventHandler_Op *handler = MEM_callocN(sizeof(*handler), __func__); | ||||
| handler->head.type = WM_HANDLER_TYPE_OP; | handler->head.type = WM_HANDLER_TYPE_OP; | ||||
| handler->is_fileselect = true; | handler->is_fileselect = true; | ||||
| handler->op = op; | handler->op = op; | ||||
| handler->context.win = CTX_wm_window(C); | handler->context.win = root_win; | ||||
| handler->context.area = CTX_wm_area(C); | handler->context.area = root_area; | ||||
| handler->context.region = CTX_wm_region(C); | handler->context.region = root_region; | ||||
| BLI_addhead(&win->modalhandlers, handler); | BLI_addhead(&root_win->modalhandlers, handler); | ||||
| /* Check props once before invoking if check is available | /* Check props once before invoking if check is available | ||||
| * ensures initial properties are valid. */ | * ensures initial properties are valid. */ | ||||
| if (op->type->check) { | if (op->type->check) { | ||||
| op->type->check(C, op); /* Ignore return value. */ | op->type->check(C, op); /* Ignore return value. */ | ||||
| } | } | ||||
| WM_event_fileselect_event(wm, op, EVT_FILESELECT_FULL_OPEN); | WM_event_fileselect_event(wm, op, EVT_FILESELECT_FULL_OPEN); | ||||
| CTX_wm_window_set(C, ctx_win); | |||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Modal Operator Handling | /** \name Modal Operator Handling | ||||
| * \{ */ | * \{ */ | ||||
| ▲ Show 20 Lines • Show All 1,778 Lines • Show Last 20 Lines | |||||
A further improvement could be splitting the file-select handler off from the regular operator handler. Then this could read fileselect_handler->root_context.win.