Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/undo/ed_undo.c
| Show First 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | |||||
| #include "RNA_define.h" | #include "RNA_define.h" | ||||
| #include "UI_interface.h" | #include "UI_interface.h" | ||||
| #include "UI_resources.h" | #include "UI_resources.h" | ||||
| /** We only need this locally. */ | /** We only need this locally. */ | ||||
| static CLG_LogRef LOG = {"ed.undo"}; | static CLG_LogRef LOG = {"ed.undo"}; | ||||
| /** | |||||
| * \warning Values are used in #ED_undo_gpencil_step, | |||||
| * which should eventually be replaced with the undo-system. | |||||
| */ | |||||
| enum eUndoStepDir { | |||||
| STEP_REDO = 1, | |||||
| STEP_UNDO = -1, | |||||
| }; | |||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Generic Undo System Access | /** \name Generic Undo System Access | ||||
| * | * | ||||
| * Non-operator undo editor functions. | * Non-operator undo editor functions. | ||||
| * \{ */ | * \{ */ | ||||
| /** | /** | ||||
| * Run from the main event loop, basic checks that undo is left in a correct state. | * Run from the main event loop, basic checks that undo is left in a correct state. | ||||
| ▲ Show 20 Lines • Show All 83 Lines • ▼ Show 20 Lines | void ED_undo_push(bContext *C, const char *str) | ||||
| } | } | ||||
| if (push_retval & UNDO_PUSH_RET_OVERRIDE_CHANGED) { | if (push_retval & UNDO_PUSH_RET_OVERRIDE_CHANGED) { | ||||
| WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); | WM_main_add_notifier(NC_WM | ND_LIB_OVERRIDE_CHANGED, NULL); | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * \note Also check #undo_history_exec in bottom if you change notifiers. | * Common pre management of undo/redo (killing all running jobs, calling pre handlers, etc.). | ||||
| */ | */ | ||||
| static int ed_undo_step_impl( | static void ed_undo_step_pre(bContext *C, | ||||
| bContext *C, int step, const char *undoname, int undo_index, ReportList *reports) | wmWindowManager *wm, | ||||
| const enum eUndoStepDir undo_dir, | |||||
| ReportList *reports) | |||||
| { | { | ||||
| /* Mutually exclusives, ensure correct input. */ | BLI_assert(ELEM(undo_dir, STEP_UNDO, STEP_REDO)); | ||||
| BLI_assert(((undoname || undo_index != -1) && !step) || | |||||
| (!(undoname || undo_index != -1) && step)); | Main *bmain = CTX_data_main(C); | ||||
| CLOG_INFO(&LOG, 1, "name='%s', step=%d", undoname, step); | |||||
| wmWindowManager *wm = CTX_wm_manager(C); | |||||
| Scene *scene = CTX_data_scene(C); | Scene *scene = CTX_data_scene(C); | ||||
| ScrArea *area = CTX_wm_area(C); | ScrArea *area = CTX_wm_area(C); | ||||
| /* undo during jobs are running can easily lead to freeing data using by jobs, | /* undo during jobs are running can easily lead to freeing data using by jobs, | ||||
| * or they can just lead to freezing job in some other cases */ | * or they can just lead to freezing job in some other cases */ | ||||
| WM_jobs_kill_all(wm); | WM_jobs_kill_all(wm); | ||||
| if (G.debug & G_DEBUG_IO) { | if (G.debug & G_DEBUG_IO) { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| if (bmain->lock != NULL) { | if (bmain->lock != NULL) { | ||||
| BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *BEFORE* undo step"); | BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *BEFORE* undo step"); | ||||
| BLO_main_validate_libraries(bmain, reports); | BLO_main_validate_libraries(bmain, reports); | ||||
| } | } | ||||
| } | } | ||||
| /* TODO(campbell): undo_system: use undo system */ | |||||
| /* grease pencil can be can be used in plenty of spaces, so check it first */ | |||||
| if (ED_gpencil_session_active()) { | |||||
| return ED_undo_gpencil_step(C, step, undoname); | |||||
| } | |||||
| if (area && (area->spacetype == SPACE_VIEW3D)) { | if (area && (area->spacetype == SPACE_VIEW3D)) { | ||||
| Object *obact = CTX_data_active_object(C); | Object *obact = CTX_data_active_object(C); | ||||
| if (obact && (obact->type == OB_GPENCIL)) { | if (obact && (obact->type == OB_GPENCIL)) { | ||||
| ED_gpencil_toggle_brush_cursor(C, false, NULL); | ED_gpencil_toggle_brush_cursor(C, false, NULL); | ||||
| } | } | ||||
| } | } | ||||
| UndoStep *step_data_from_name = NULL; | |||||
| int step_for_callback = step; | |||||
| if (undoname != NULL) { | |||||
| step_data_from_name = BKE_undosys_step_find_by_name(wm->undo_stack, undoname); | |||||
| if (step_data_from_name == NULL) { | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| /* TODO(campbell), could use simple optimization. */ | |||||
| /* Pointers match on redo. */ | |||||
| step_for_callback = (BLI_findindex(&wm->undo_stack->steps, step_data_from_name) < | |||||
| BLI_findindex(&wm->undo_stack->steps, wm->undo_stack->step_active)) ? | |||||
| 1 : | |||||
| -1; | |||||
| } | |||||
| else if (undo_index != -1) { | |||||
| step_for_callback = (undo_index < | |||||
| BLI_findindex(&wm->undo_stack->steps, wm->undo_stack->step_active)) ? | |||||
| 1 : | |||||
| -1; | |||||
| } | |||||
| /* App-Handlers (pre). */ | /* App-Handlers (pre). */ | ||||
| { | { | ||||
| /* Note: ignore grease pencil for now. */ | /* Note: ignore grease pencil for now. */ | ||||
| Main *bmain = CTX_data_main(C); | |||||
| wm->op_undo_depth++; | wm->op_undo_depth++; | ||||
| BKE_callback_exec_id( | BKE_callback_exec_id( | ||||
| bmain, &scene->id, (step_for_callback > 0) ? BKE_CB_EVT_UNDO_PRE : BKE_CB_EVT_REDO_PRE); | bmain, &scene->id, (undo_dir == STEP_UNDO) ? BKE_CB_EVT_UNDO_PRE : BKE_CB_EVT_REDO_PRE); | ||||
| wm->op_undo_depth--; | wm->op_undo_depth--; | ||||
| } | } | ||||
| } | |||||
| /* Undo System */ | /** | ||||
| * Common post management of undo/redo (calling post handlers, adding notifiers etc.). | |||||
| * | |||||
| * \note Also check #undo_history_exec in bottom if you change notifiers. | |||||
| */ | |||||
| static void ed_undo_step_post(bContext *C, | |||||
| wmWindowManager *wm, | |||||
| const enum eUndoStepDir undo_dir, | |||||
| ReportList *reports) | |||||
| { | { | ||||
| if (undoname) { | BLI_assert(ELEM(undo_dir, STEP_UNDO, STEP_REDO)); | ||||
| BKE_undosys_step_undo_with_data(wm->undo_stack, C, step_data_from_name); | |||||
| } | |||||
| else if (undo_index != -1) { | |||||
| BKE_undosys_step_undo_from_index(wm->undo_stack, C, undo_index); | |||||
| } | |||||
| else { | |||||
| if (step == 1) { | |||||
| BKE_undosys_step_undo(wm->undo_stack, C); | |||||
| } | |||||
| else { | |||||
| BKE_undosys_step_redo(wm->undo_stack, C); | |||||
| } | |||||
| } | |||||
| /* Set special modes for grease pencil */ | Main *bmain = CTX_data_main(C); | ||||
| if (area && (area->spacetype == SPACE_VIEW3D)) { | Scene *scene = CTX_data_scene(C); | ||||
| Object *obact = CTX_data_active_object(C); | |||||
| if (obact && (obact->type == OB_GPENCIL)) { | |||||
| /* set cursor */ | |||||
| if (ELEM(obact->mode, | |||||
| OB_MODE_PAINT_GPENCIL, | |||||
| OB_MODE_SCULPT_GPENCIL, | |||||
| OB_MODE_WEIGHT_GPENCIL, | |||||
| OB_MODE_VERTEX_GPENCIL)) { | |||||
| ED_gpencil_toggle_brush_cursor(C, true, NULL); | |||||
| } | |||||
| else { | |||||
| ED_gpencil_toggle_brush_cursor(C, false, NULL); | |||||
| } | |||||
| /* set workspace mode */ | |||||
| Base *basact = CTX_data_active_base(C); | |||||
| ED_object_base_activate(C, basact); | |||||
| } | |||||
| } | |||||
| } | |||||
| /* App-Handlers (post). */ | /* App-Handlers (post). */ | ||||
| { | { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| scene = CTX_data_scene(C); | |||||
| wm->op_undo_depth++; | wm->op_undo_depth++; | ||||
| BKE_callback_exec_id( | BKE_callback_exec_id( | ||||
| bmain, &scene->id, step_for_callback > 0 ? BKE_CB_EVT_UNDO_POST : BKE_CB_EVT_REDO_POST); | bmain, &scene->id, (undo_dir == STEP_UNDO) ? BKE_CB_EVT_UNDO_POST : BKE_CB_EVT_REDO_POST); | ||||
| wm->op_undo_depth--; | wm->op_undo_depth--; | ||||
| } | } | ||||
| if (G.debug & G_DEBUG_IO) { | if (G.debug & G_DEBUG_IO) { | ||||
| Main *bmain = CTX_data_main(C); | |||||
| if (bmain->lock != NULL) { | if (bmain->lock != NULL) { | ||||
| BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *AFTER* undo step"); | BKE_report(reports, RPT_INFO, "Checking sanity of current .blend file *AFTER* undo step"); | ||||
| BLO_main_validate_libraries(bmain, reports); | BLO_main_validate_libraries(bmain, reports); | ||||
| } | } | ||||
| } | } | ||||
| WM_event_add_notifier(C, NC_WINDOW, NULL); | WM_event_add_notifier(C, NC_WINDOW, NULL); | ||||
| WM_event_add_notifier(C, NC_WM | ND_UNDO, NULL); | WM_event_add_notifier(C, NC_WM | ND_UNDO, NULL); | ||||
| WM_toolsystem_refresh_active(C); | WM_toolsystem_refresh_active(C); | ||||
| Main *bmain = CTX_data_main(C); | |||||
| WM_toolsystem_refresh_screen_all(bmain); | WM_toolsystem_refresh_screen_all(bmain); | ||||
| if (CLOG_CHECK(&LOG, 1)) { | if (CLOG_CHECK(&LOG, 1)) { | ||||
| BKE_undosys_print(wm->undo_stack); | BKE_undosys_print(wm->undo_stack); | ||||
| } | } | ||||
| return OPERATOR_FINISHED; | |||||
| } | } | ||||
| static int ed_undo_step_direction(bContext *C, int step, ReportList *reports) | /** Undo or redo one step from current active one. | ||||
| * May undo or redo several steps at once only if the target step is a 'skipped' one. | |||||
| * The target step will be the one immediately before or after the active one. */ | |||||
| static int ed_undo_step_direction(bContext *C, enum eUndoStepDir step, ReportList *reports) | |||||
| { | { | ||||
| return ed_undo_step_impl(C, step, NULL, -1, reports); | BLI_assert(ELEM(step, STEP_UNDO, STEP_REDO)); | ||||
| CLOG_INFO(&LOG, 1, "direction=%s", (step == STEP_UNDO) ? "STEP_UNDO" : "STEP_REDO"); | |||||
| /* TODO(campbell): undo_system: use undo system */ | |||||
| /* grease pencil can be can be used in plenty of spaces, so check it first */ | |||||
| /* FIXME: This gpencil undo effectively only supports the one step undo/redo, undo based on name | |||||
| * or index is fully not implemented. | |||||
| * FIXME: However, it seems to never be used in current code (`ED_gpencil_session_active` seems | |||||
| * to always return false). */ | |||||
| if (ED_gpencil_session_active()) { | |||||
| return ED_undo_gpencil_step(C, (int)step); | |||||
| } | } | ||||
| wmWindowManager *wm = CTX_wm_manager(C); | |||||
| ed_undo_step_pre(C, wm, step, reports); | |||||
| if (step == STEP_UNDO) { | |||||
| BKE_undosys_step_undo(wm->undo_stack, C); | |||||
| } | |||||
| else { | |||||
| BKE_undosys_step_redo(wm->undo_stack, C); | |||||
| } | |||||
| ed_undo_step_post(C, wm, step, reports); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| /** Undo the step matching given name. | |||||
| * May undo several steps at once. | |||||
| * The target step will be the one immediately before given named one. */ | |||||
| static int ed_undo_step_by_name(bContext *C, const char *undo_name, ReportList *reports) | static int ed_undo_step_by_name(bContext *C, const char *undo_name, ReportList *reports) | ||||
| { | { | ||||
| return ed_undo_step_impl(C, 0, undo_name, -1, reports); | BLI_assert(undo_name != NULL); | ||||
| /* FIXME: See comments in `ed_undo_step_direction`. */ | |||||
| if (ED_gpencil_session_active()) { | |||||
| BLI_assert(!"Not implemented currently."); | |||||
| } | } | ||||
| static int ed_undo_step_by_index(bContext *C, int index, ReportList *reports) | wmWindowManager *wm = CTX_wm_manager(C); | ||||
| UndoStep *undo_step_from_name = BKE_undosys_step_find_by_name(wm->undo_stack, undo_name); | |||||
| if (undo_step_from_name == NULL) { | |||||
| CLOG_ERROR(&LOG, "Step name='%s' not found in current undo stack", undo_name); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| UndoStep *undo_step_target = undo_step_from_name->prev; | |||||
| if (undo_step_target == NULL) { | |||||
| CLOG_ERROR(&LOG, "Step name='%s' cannot be undone", undo_name); | |||||
| return OPERATOR_CANCELLED; | |||||
| } | |||||
| const int undo_dir_i = BKE_undosys_step_calc_direction(wm->undo_stack, undo_step_target, NULL); | |||||
| BLI_assert(ELEM(undo_dir_i, -1, 1)); | |||||
| const enum eUndoStepDir undo_dir = (undo_dir_i == -1) ? STEP_UNDO : STEP_REDO; | |||||
| CLOG_INFO(&LOG, | |||||
| 1, | |||||
| "name='%s', found direction=%s", | |||||
| undo_name, | |||||
| (undo_dir == STEP_UNDO) ? "STEP_UNDO" : "STEP_REDO"); | |||||
| ed_undo_step_pre(C, wm, undo_dir, reports); | |||||
| BKE_undosys_step_load_data_ex(wm->undo_stack, C, undo_step_target, NULL, true); | |||||
| ed_undo_step_post(C, wm, undo_dir, reports); | |||||
| return OPERATOR_FINISHED; | |||||
| } | |||||
| /** Load the step matching given index in the stack. | |||||
| * May undo or redo several steps at once. | |||||
| * The target step will be the one indicated by the given index. */ | |||||
| static int ed_undo_step_by_index(bContext *C, const int undo_index, ReportList *reports) | |||||
| { | { | ||||
| return ed_undo_step_impl(C, 0, NULL, index, reports); | BLI_assert(undo_index >= 0); | ||||
| /* FIXME: See comments in `ed_undo_step_direction`. */ | |||||
| if (ED_gpencil_session_active()) { | |||||
| BLI_assert(!"Not implemented currently."); | |||||
| } | |||||
| wmWindowManager *wm = CTX_wm_manager(C); | |||||
| const int active_step_index = BLI_findindex(&wm->undo_stack->steps, wm->undo_stack->step_active); | |||||
| const enum eUndoStepDir undo_dir = (undo_index < active_step_index) ? STEP_UNDO : STEP_REDO; | |||||
| CLOG_INFO(&LOG, | |||||
| 1, | |||||
| "index='%d', found direction=%s", | |||||
| undo_index, | |||||
| (undo_dir == STEP_UNDO) ? "STEP_UNDO" : "STEP_REDO"); | |||||
| ed_undo_step_pre(C, wm, undo_dir, reports); | |||||
| BKE_undosys_step_load_from_index(wm->undo_stack, C, undo_index); | |||||
| ed_undo_step_post(C, wm, undo_dir, reports); | |||||
| return OPERATOR_FINISHED; | |||||
| } | } | ||||
| void ED_undo_grouped_push(bContext *C, const char *str) | void ED_undo_grouped_push(bContext *C, const char *str) | ||||
| { | { | ||||
| /* do nothing if previous undo task is the same as this one (or from the same undo group) */ | /* do nothing if previous undo task is the same as this one (or from the same undo group) */ | ||||
| wmWindowManager *wm = CTX_wm_manager(C); | wmWindowManager *wm = CTX_wm_manager(C); | ||||
| const UndoStep *us = wm->undo_stack->step_active; | const UndoStep *us = wm->undo_stack->step_active; | ||||
| if (us && STREQ(str, us->name)) { | if (us && STREQ(str, us->name)) { | ||||
| BKE_undosys_stack_clear_active(wm->undo_stack); | BKE_undosys_stack_clear_active(wm->undo_stack); | ||||
| } | } | ||||
| /* push as usual */ | /* push as usual */ | ||||
| ED_undo_push(C, str); | ED_undo_push(C, str); | ||||
| } | } | ||||
| void ED_undo_pop(bContext *C) | void ED_undo_pop(bContext *C) | ||||
| { | { | ||||
| ed_undo_step_direction(C, 1, NULL); | ed_undo_step_direction(C, STEP_UNDO, NULL); | ||||
| } | } | ||||
| void ED_undo_redo(bContext *C) | void ED_undo_redo(bContext *C) | ||||
| { | { | ||||
| ed_undo_step_direction(C, -1, NULL); | ed_undo_step_direction(C, STEP_REDO, NULL); | ||||
| } | } | ||||
| void ED_undo_push_op(bContext *C, wmOperator *op) | void ED_undo_push_op(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| /* in future, get undo string info? */ | /* in future, get undo string info? */ | ||||
| ED_undo_push(C, op->type->name); | ED_undo_push(C, op->type->name); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | |||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Undo, Undo Push & Redo Operators | /** \name Undo, Undo Push & Redo Operators | ||||
| * \{ */ | * \{ */ | ||||
| static int ed_undo_exec(bContext *C, wmOperator *op) | static int ed_undo_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| /* "last operator" should disappear, later we can tie this with undo stack nicer */ | /* "last operator" should disappear, later we can tie this with undo stack nicer */ | ||||
| WM_operator_stack_clear(CTX_wm_manager(C)); | WM_operator_stack_clear(CTX_wm_manager(C)); | ||||
| int ret = ed_undo_step_direction(C, 1, op->reports); | int ret = ed_undo_step_direction(C, STEP_UNDO, op->reports); | ||||
| if (ret & OPERATOR_FINISHED) { | if (ret & OPERATOR_FINISHED) { | ||||
| /* Keep button under the cursor active. */ | /* Keep button under the cursor active. */ | ||||
| WM_event_add_mousemove(CTX_wm_window(C)); | WM_event_add_mousemove(CTX_wm_window(C)); | ||||
| } | } | ||||
| ED_outliner_select_sync_from_all_tag(C); | ED_outliner_select_sync_from_all_tag(C); | ||||
| return ret; | return ret; | ||||
| } | } | ||||
| Show All 12 Lines | static int ed_undo_push_exec(bContext *C, wmOperator *op) | ||||
| char str[BKE_UNDO_STR_MAX]; | char str[BKE_UNDO_STR_MAX]; | ||||
| RNA_string_get(op->ptr, "message", str); | RNA_string_get(op->ptr, "message", str); | ||||
| ED_undo_push(C, str); | ED_undo_push(C, str); | ||||
| return OPERATOR_FINISHED; | return OPERATOR_FINISHED; | ||||
| } | } | ||||
| static int ed_redo_exec(bContext *C, wmOperator *op) | static int ed_redo_exec(bContext *C, wmOperator *op) | ||||
| { | { | ||||
| int ret = ed_undo_step_direction(C, -1, op->reports); | int ret = ed_undo_step_direction(C, STEP_REDO, op->reports); | ||||
| if (ret & OPERATOR_FINISHED) { | if (ret & OPERATOR_FINISHED) { | ||||
| /* Keep button under the cursor active. */ | /* Keep button under the cursor active. */ | ||||
| WM_event_add_mousemove(CTX_wm_window(C)); | WM_event_add_mousemove(CTX_wm_window(C)); | ||||
| } | } | ||||
| ED_outliner_select_sync_from_all_tag(C); | ED_outliner_select_sync_from_all_tag(C); | ||||
| return ret; | return ret; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 475 Lines • Show Last 20 Lines | |||||