diff --git a/source/blender/editors/include/ED_image.h b/source/blender/editors/include/ED_image.h index affe98e10f8..a09e1d579fd 100644 --- a/source/blender/editors/include/ED_image.h +++ b/source/blender/editors/include/ED_image.h @@ -111,6 +111,7 @@ void ED_image_draw_info(struct Scene *scene, bool ED_space_image_show_cache(struct SpaceImage *sima); +bool ED_image_should_save_modified(const struct bContext *C); int ED_image_save_all_modified_info(const struct bContext *C, struct ReportList *reports); bool ED_image_save_all_modified(const struct bContext *C, struct ReportList *reports); diff --git a/source/blender/editors/space_image/image_ops.c b/source/blender/editors/space_image/image_ops.c index b49fd92beb3..533e39a27bc 100644 --- a/source/blender/editors/space_image/image_ops.c +++ b/source/blender/editors/space_image/image_ops.c @@ -2161,126 +2161,117 @@ void IMAGE_OT_save_sequence(wmOperatorType *ot) /********************** save all operator **********************/ -static int image_save_all_modified(const bContext *C, - ReportList *reports, - int *num_files, - const bool dry_run, - const bool ignore_dry_run_warnings) +static bool image_should_be_saved_when_modified(Image *ima) +{ + return !ELEM(ima->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE); +} + +static bool image_should_be_saved(Image *ima) +{ + if (BKE_image_is_dirty(ima) && (ima->source == IMA_SRC_FILE)) { + return image_should_be_saved_when_modified(ima); + } + else { + return false; + } +} + +static bool image_has_valid_path(Image *ima) +{ + return strchr(ima->name, '\\') || strchr(ima->name, '/'); +} + +bool ED_image_should_save_modified(const bContext *C) +{ + return ED_image_save_all_modified_info(C, NULL) > 0; +} + +int ED_image_save_all_modified_info(const bContext *C, ReportList *reports) { Main *bmain = CTX_data_main(C); - Scene *scene = CTX_data_scene(C); GSet *unique_paths = BLI_gset_str_new(__func__); - bool ok = true; - if (num_files) { - *num_files = 0; - } + int num_saveable_images = 0; for (Image *ima = bmain->images.first; ima; ima = ima->id.next) { - if (ELEM(ima->type, IMA_TYPE_R_RESULT, IMA_TYPE_COMPOSITE)) { - /* Don't save render results automatically. */ - } - else if (BKE_image_is_dirty(ima) && (ima->source == IMA_SRC_FILE)) { + if (image_should_be_saved(ima)) { if (BKE_image_has_packedfile(ima)) { if (ima->id.lib == NULL) { - /* Re-pack. */ - if (!dry_run) { - BKE_image_memorypack(ima); - } - - if (num_files) { - (*num_files)++; - } + num_saveable_images++; } - else if (!ignore_dry_run_warnings) { - /* Can't pack to library data. */ + else { BKE_reportf(reports, RPT_WARNING, "Packed library image: %s from library %s can't be saved", ima->id.name, ima->id.lib->name); - ok = false; } } else { - /* Save to file. */ - const bool valid_path = strchr(ima->name, '\\') || strchr(ima->name, '/'); - - if (valid_path) { - ImageSaveOptions opts; - - BKE_image_save_options_init(&opts, bmain, scene); - - if (image_save_options_init(bmain, &opts, ima, NULL, false, false)) { - if (!BLI_gset_haskey(unique_paths, opts.filepath)) { - if (!dry_run) { - const bool save_ok = BKE_image_save(reports, bmain, ima, NULL, &opts); - - if (save_ok) { - BLI_gset_insert(unique_paths, BLI_strdup(opts.filepath)); - } - - ok = ok && save_ok; - } - - if (num_files) { - (*num_files)++; - } - } - else if (!ignore_dry_run_warnings) { - BKE_reportf(reports, - RPT_WARNING, - "File path used by more than one saved image: %s", - opts.filepath); - ok = false; - } + if (image_has_valid_path(ima)) { + num_saveable_images++; + if (BLI_gset_haskey(unique_paths, ima->name)) { + BKE_reportf(reports, + RPT_WARNING, + "File path used by more than one saved image: %s", + ima->name); + } + else { + BLI_gset_insert(unique_paths, BLI_strdup(ima->name)); } } - else if (!ignore_dry_run_warnings) { + else { BKE_reportf(reports, RPT_WARNING, "Image %s can't be saved, no valid file path: %s", ima->id.name, ima->name); - ok = false; } } } } BLI_gset_free(unique_paths, MEM_freeN); - - return ok; -} - -int ED_image_save_all_modified_info(const bContext *C, ReportList *reports) -{ - /* Dry run to get number of files, and any warnings we can detect in advance. */ - int num_files; - image_save_all_modified(C, reports, &num_files, true, false); - return num_files; + return num_saveable_images; } bool ED_image_save_all_modified(const bContext *C, ReportList *reports) { - /* Save, and ignore any warnings that we already detected in - * ED_image_save_all_modified_info. */ - return image_save_all_modified(C, reports, NULL, false, true); + ED_image_save_all_modified_info(C, reports); + + Main *bmain = CTX_data_main(C); + bool ok = true; + + for (Image *ima = bmain->images.first; ima; ima = ima->id.next) { + if (image_should_be_saved(ima)) { + if (BKE_image_has_packedfile(ima)) { + BKE_image_memorypack(ima); + } + else { + if (image_has_valid_path(ima)) { + ImageSaveOptions opts; + Scene *scene = CTX_data_scene(C); + BKE_image_save_options_init(&opts, bmain, scene); + if (image_save_options_init(bmain, &opts, ima, NULL, false, false)) { + bool saved_successfully = BKE_image_save(reports, bmain, ima, NULL, &opts); + ok = ok && saved_successfully; + } + } + } + } + } + return ok; } static bool image_save_all_modified_poll(bContext *C) { - /* Let operator run if there are any files to saved, or any warnings to - * report about files that we can't save. */ - int num_files; - bool ok = image_save_all_modified(C, NULL, &num_files, true, false); - return (num_files > 0) || !ok; + int num_files = ED_image_save_all_modified_info(C, NULL); + return num_files > 0; } static int image_save_all_modified_exec(bContext *C, wmOperator *op) { - /* Save, and show all warnings. */ - image_save_all_modified(C, op->reports, NULL, false, false); + ED_image_save_all_modified(C, op->reports); return OPERATOR_FINISHED; } diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index bd02a1e13c1..70f986732ad 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -401,6 +401,10 @@ int WM_operator_name_call(struct bContext *C, const char *opstring, short context, struct PointerRNA *properties); +int WM_operator_name_call_with_properties(struct bContext *C, + const char *opstring, + short context, + struct IDProperty *properties); int WM_operator_call_py(struct bContext *C, struct wmOperatorType *ot, short context, diff --git a/source/blender/windowmanager/intern/wm_event_system.c b/source/blender/windowmanager/intern/wm_event_system.c index 8ad23af446d..6683085e6d3 100644 --- a/source/blender/windowmanager/intern/wm_event_system.c +++ b/source/blender/windowmanager/intern/wm_event_system.c @@ -1678,6 +1678,17 @@ int WM_operator_name_call(bContext *C, const char *opstring, short context, Poin return 0; } +int WM_operator_name_call_with_properties(struct bContext *C, + const char *opstring, + short context, + struct IDProperty *properties) +{ + PointerRNA props_ptr; + wmOperatorType *ot = WM_operatortype_find(opstring, false); + RNA_pointer_create(NULL, ot->srna, properties, &props_ptr); + return WM_operator_name_call_ptr(C, ot, context, &props_ptr); +} + /** * Call an existent menu. The menu can be created in C or Python. */ diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 329763a17fd..fabdd71fc5c 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -76,6 +76,7 @@ #include "BKE_blender_undo.h" #include "BKE_context.h" #include "BKE_global.h" +#include "BKE_idprop.h" #include "BKE_main.h" #include "BKE_packedFile.h" #include "BKE_report.h" @@ -98,6 +99,7 @@ #include "ED_datafiles.h" #include "ED_fileselect.h" +#include "ED_image.h" #include "ED_screen.h" #include "ED_view3d.h" #include "ED_util.h" @@ -1922,11 +1924,27 @@ static int wm_homefile_read_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static void wm_homefile_read_after_dialog_callback(bContext *C, void *user_data) +{ + WM_operator_name_call_with_properties( + C, "WM_OT_read_homefile", WM_OP_EXEC_DEFAULT, (IDProperty *)user_data); +} + +static void wm_free_operator_properties_callback(void *user_data) +{ + IDProperty *properties = (IDProperty *)user_data; + IDP_FreeProperty(properties); +} + static int wm_homefile_read_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) { - wmWindowManager *wm = CTX_wm_manager(C); - if (U.uiflag & USER_SAVE_PROMPT && !wm->file_saved) { - return WM_operator_confirm_message(C, op, "Changes in current file will be lost. Continue?"); + if (U.uiflag & USER_SAVE_PROMPT && wm_file_or_image_is_modified(C)) { + GenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); + callback->exec = wm_homefile_read_after_dialog_callback; + callback->user_data = IDP_CopyProperty(op->properties); + callback->free_user_data = wm_free_operator_properties_callback; + wm_close_file_dialog(C, callback); + return OPERATOR_INTERFACE; } else { return wm_homefile_read_exec(C, op); @@ -2071,6 +2089,12 @@ enum { static int wm_open_mainfile_dispatch(bContext *C, wmOperator *op); +static void wm_open_mainfile_after_dialog_callback(bContext *C, void *user_data) +{ + WM_operator_name_call_with_properties( + C, "WM_OT_open_mainfile", WM_OP_INVOKE_DEFAULT, (IDProperty *)user_data); +} + static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op) { if (RNA_boolean_get(op->ptr, "display_file_selector")) { @@ -2080,14 +2104,13 @@ static int wm_open_mainfile__discard_changes(bContext *C, wmOperator *op) set_next_operator_state(op, OPEN_MAINFILE_STATE_OPEN); } - wmWindowManager *wm = CTX_wm_manager(C); - if (U.uiflag & USER_SAVE_PROMPT && !wm->file_saved) { - return WM_operator_confirm_message_ex(C, - op, - "Warning", - ICON_INFO, - "Changes in current file will be lost. Continue?", - WM_OP_INVOKE_DEFAULT); + if (U.uiflag & USER_SAVE_PROMPT && wm_file_or_image_is_modified(C)) { + GenericCallback *callback = MEM_callocN(sizeof(*callback), __func__); + callback->exec = wm_open_mainfile_after_dialog_callback; + callback->user_data = IDP_CopyProperty(op->properties); + callback->free_user_data = wm_free_operator_properties_callback; + wm_close_file_dialog(C, callback); + return OPERATOR_INTERFACE; } else { return wm_open_mainfile_dispatch(C, op); @@ -2819,4 +2842,234 @@ void wm_test_autorun_warning(bContext *C) } } +/* Close File Dialog + *************************************/ + +static char save_images_when_file_is_closed = true; + +static void wm_block_file_close_cancel(bContext *C, void *arg_block, void *UNUSED(arg_data)) +{ + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); +} + +static void wm_block_file_close_discard(bContext *C, void *arg_block, void *arg_data) +{ + GenericCallback *callback = wm_generic_callback_steal((GenericCallback *)arg_data); + + /* Close the popup before executing the callback. Otherwise + * the popup might be closed by the callback, which will lead + * to a crash. */ + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); + + callback->exec(C, callback->user_data); + wm_generic_callback_free(callback); +} + +static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_data) +{ + GenericCallback *callback = wm_generic_callback_steal((GenericCallback *)arg_data); + bool execute_callback = true; + + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); + + if (save_images_when_file_is_closed) { + ReportList *reports = CTX_wm_reports(C); + if (!ED_image_save_all_modified(C, reports)) { + execute_callback = false; + } + WM_report_banner_show(); + } + + Main *bmain = CTX_data_main(C); + bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0'; + + if (file_has_been_saved_before) { + WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_EXEC_DEFAULT, NULL); + } + else { + WM_operator_name_call(C, "WM_OT_save_mainfile", WM_OP_INVOKE_DEFAULT, NULL); + execute_callback = false; + } + + if (execute_callback) { + callback->exec(C, callback->user_data); + } + wm_generic_callback_free(callback); +} + +static uiBlock *block_create__close_file_dialog(struct bContext *C, struct ARegion *ar, void *arg1) +{ + GenericCallback *post_action = (GenericCallback *)arg1; + Main *bmain = CTX_data_main(C); + + uiStyle *style = UI_style_get(); + uiBlock *block = UI_block_begin(C, ar, "file_close_popup", UI_EMBOSS); + + UI_block_flag_enable( + block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT); + UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); + UI_block_emboss_set(block, UI_EMBOSS); + + uiLayout *layout = UI_block_layout(block, + UI_LAYOUT_VERTICAL, + UI_LAYOUT_PANEL, + 10, + 2, + U.widget_unit * 24, + U.widget_unit * 6, + 0, + style); + + bool blend_file_is_saved = BKE_main_blendfile_path(bmain)[0] != '\0'; + if (blend_file_is_saved) { + uiItemL(layout, "This file has unsaved changes.", ICON_NONE); + } + else { + uiItemL(layout, "This file has not been saved yet.", ICON_NONE); + } + + ReportList reports; + BKE_reports_init(&reports, RPT_STORE); + uint modified_images_count = ED_image_save_all_modified_info(C, &reports); + + if (modified_images_count > 0) { + char message[64]; + BLI_snprintf(message, + sizeof(message), + (modified_images_count == 1) ? "Save %u modified image" : + "Save %u modified images", + modified_images_count); + uiDefButBitC(block, + UI_BTYPE_CHECKBOX, + 1, + 0, + message, + 0, + 0, + 0, + UI_UNIT_Y, + &save_images_when_file_is_closed, + 0, + 0, + 0, + 0, + ""); + + LISTBASE_FOREACH (Report *, report, &reports.list) { + uiItemL(layout, report->message, ICON_ERROR); + } + } + + BKE_reports_clear(&reports); + + uiItemL(layout, "", ICON_NONE); + + uiBut *but; + uiLayout *split = uiLayoutSplit(layout, 0.0f, true); + uiLayout *col = uiLayoutColumn(split, false); + + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_SCREEN_BACK, + IFACE_("Cancel"), + 0, + 0, + 0, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but, wm_block_file_close_cancel, block, post_action); + + /* empty space between buttons */ + col = uiLayoutColumn(split, false); + uiItemS(col); + + col = uiLayoutColumn(split, true); + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_CANCEL, + IFACE_("Discard Changes"), + 0, + 0, + 50, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but, wm_block_file_close_discard, block, post_action); + + col = uiLayoutColumn(split, true); + but = uiDefIconTextBut(block, + UI_BTYPE_BUT, + 0, + ICON_CANCEL, + IFACE_("Save"), + 0, + 0, + 50, + UI_UNIT_Y, + NULL, + 0, + 0, + 0, + 0, + ""); + UI_but_func_set(but, wm_block_file_close_save, block, post_action); + UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT); + + UI_block_bounds_set_centered(block, 10); + return block; +} + +static void free_post_file_close_action(void *arg) +{ + GenericCallback *action = (GenericCallback *)arg; + wm_generic_callback_free(action); +} + +void wm_close_file_dialog(bContext *C, GenericCallback *post_action) +{ + UI_popup_block_invoke( + C, block_create__close_file_dialog, post_action, free_post_file_close_action); +} + +bool wm_file_or_image_is_modified(const bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + return !wm->file_saved || ED_image_should_save_modified(C); +} + +void wm_generic_callback_free(GenericCallback *callback) +{ + if (callback->free_user_data) { + callback->free_user_data(callback->user_data); + } + MEM_freeN(callback); +} + +static void do_nothing(bContext *UNUSED(C), void *UNUSED(user_data)) +{ +} + +GenericCallback *wm_generic_callback_steal(GenericCallback *callback) +{ + GenericCallback *new_callback = MEM_dupallocN(callback); + callback->exec = do_nothing; + callback->free_user_data = NULL; + callback->user_data = NULL; + return new_callback; +} + /** \} */ diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index 409befd8c47..c7168673f26 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -61,6 +61,7 @@ #include "WM_types.h" #include "wm.h" #include "wm_draw.h" +#include "wm_files.h" #include "wm_window.h" #include "wm_event_system.h" @@ -356,150 +357,9 @@ wmWindow *wm_window_copy_test(bContext *C, /** \name Quit Confirmation Dialog * \{ */ -/** Cancel quitting and close the dialog */ -static void wm_block_confirm_quit_cancel(bContext *C, void *arg_block, void *UNUSED(arg)) +static void wm_save_file_on_quit_dialog_callback(bContext *C, void *UNUSED(user_data)) { - wmWindow *win = CTX_wm_window(C); - UI_popup_block_close(C, win, arg_block); -} - -/** Discard the file changes and quit */ -ATTR_NORETURN -static void wm_block_confirm_quit_discard(bContext *C, void *arg_block, void *UNUSED(arg)) -{ - wmWindow *win = CTX_wm_window(C); - UI_popup_block_close(C, win, arg_block); - WM_exit(C); -} - -/* Save changes and quit */ -static void wm_block_confirm_quit_save(bContext *C, void *arg_block, void *UNUSED(arg)) -{ - PointerRNA props_ptr; - wmWindow *win = CTX_wm_window(C); - - UI_popup_block_close(C, win, arg_block); - - wmOperatorType *ot = WM_operatortype_find("WM_OT_save_mainfile", false); - - WM_operator_properties_create_ptr(&props_ptr, ot); - RNA_boolean_set(&props_ptr, "exit", true); - /* No need for second confirmation popup. */ - RNA_boolean_set(&props_ptr, "check_existing", false); - WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &props_ptr); - WM_operator_properties_free(&props_ptr); -} - -/* Build the confirm dialog UI */ -static uiBlock *block_create_confirm_quit(struct bContext *C, - struct ARegion *ar, - void *UNUSED(arg1)) -{ - Main *bmain = CTX_data_main(C); - - uiStyle *style = UI_style_get(); - uiBlock *block = UI_block_begin(C, ar, "confirm_quit_popup", UI_EMBOSS); - - UI_block_flag_enable( - block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_LOOP | UI_BLOCK_NO_WIN_CLIP | UI_BLOCK_NUMSELECT); - UI_block_theme_style_set(block, UI_BLOCK_THEME_STYLE_POPUP); - UI_block_emboss_set(block, UI_EMBOSS); - - uiLayout *layout = UI_block_layout(block, - UI_LAYOUT_VERTICAL, - UI_LAYOUT_PANEL, - 10, - 2, - U.widget_unit * 24, - U.widget_unit * 6, - 0, - style); - - /* Text and some vertical space */ - { - char *message; - if (BKE_main_blendfile_path(bmain)[0] == '\0') { - message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?")); - } - else { - const char *basename = BLI_path_basename(BKE_main_blendfile_path(bmain)); - message = BLI_sprintfN(IFACE_("Save changes to \"%s\" before closing?"), basename); - } - uiItemL(layout, message, ICON_ERROR); - MEM_freeN(message); - } - - uiItemS(layout); - uiItemS(layout); - - /* Buttons */ - uiBut *but; - - uiLayout *split = uiLayoutSplit(layout, 0.0f, true); - - uiLayout *col = uiLayoutColumn(split, false); - - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_SCREEN_BACK, - IFACE_("Cancel"), - 0, - 0, - 0, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Do not quit")); - UI_but_func_set(but, wm_block_confirm_quit_cancel, block, NULL); - - /* empty space between buttons */ - col = uiLayoutColumn(split, false); - uiItemS(col); - - col = uiLayoutColumn(split, 1); - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_CANCEL, - IFACE_("Discard Changes"), - 0, - 0, - 50, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Discard changes and quit")); - UI_but_func_set(but, wm_block_confirm_quit_discard, block, NULL); - - col = uiLayoutColumn(split, 1); - but = uiDefIconTextBut(block, - UI_BTYPE_BUT, - 0, - ICON_FILE_TICK, - IFACE_("Save & Quit"), - 0, - 0, - 50, - UI_UNIT_Y, - NULL, - 0, - 0, - 0, - 0, - TIP_("Save and quit")); - UI_but_func_set(but, wm_block_confirm_quit_save, block, NULL); - UI_but_flag_enable(but, UI_BUT_ACTIVE_DEFAULT); - - UI_block_bounds_set_centered(block, 10); - - return block; + wm_exit_schedule_delayed(C); } /** @@ -508,16 +368,9 @@ static uiBlock *block_create_confirm_quit(struct bContext *C, */ static void wm_confirm_quit(bContext *C) { - wmWindow *win = CTX_wm_window(C); - - if (GHOST_SupportsNativeDialogs() == 0) { - if (!UI_popup_block_name_exists(C, "confirm_quit_popup")) { - UI_popup_block_invoke(C, block_create_confirm_quit, NULL, NULL); - } - } - else if (GHOST_confirmQuit(win->ghostwin)) { - wm_exit_schedule_delayed(C); - } + GenericCallback *action = MEM_callocN(sizeof(*action), __func__); + action->exec = wm_save_file_on_quit_dialog_callback; + wm_close_file_dialog(C, action); } /** @@ -529,7 +382,6 @@ static void wm_confirm_quit(bContext *C) */ void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win) { - wmWindowManager *wm = CTX_wm_manager(C); wmWindow *win_ctx = CTX_wm_window(C); /* The popup will be displayed in the context window which may not be set @@ -537,7 +389,7 @@ void wm_quit_with_optional_confirmation_prompt(bContext *C, wmWindow *win) CTX_wm_window_set(C, win); if (U.uiflag & USER_SAVE_PROMPT) { - if (!wm->file_saved && !G.background) { + if (wm_file_or_image_is_modified(C) && !G.background) { wm_confirm_quit(C); } else { diff --git a/source/blender/windowmanager/wm_files.h b/source/blender/windowmanager/wm_files.h index 24209504a07..8b3f1ead215 100644 --- a/source/blender/windowmanager/wm_files.h +++ b/source/blender/windowmanager/wm_files.h @@ -40,6 +40,17 @@ void wm_homefile_read(struct bContext *C, bool *r_is_factory_startup); void wm_file_read_report(bContext *C, struct Main *bmain); +typedef struct GenericCallback { + void (*exec)(bContext *C, void *user_data); + void *user_data; + void (*free_user_data)(void *user_data); +} GenericCallback; + +GenericCallback *wm_generic_callback_steal(GenericCallback *callback); +void wm_generic_callback_free(GenericCallback *callback); +void wm_close_file_dialog(bContext *C, GenericCallback *post_action); +bool wm_file_or_image_is_modified(const struct bContext *C); + void WM_OT_save_homefile(struct wmOperatorType *ot); void WM_OT_userpref_autoexec_path_add(struct wmOperatorType *ot); void WM_OT_userpref_autoexec_path_remove(struct wmOperatorType *ot);