From 5ba5254ec1ebba6c1e89b663592d716b92e13165 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Wed, 21 Mar 2018 16:00:18 +0100 Subject: UI: Optional prompt to quit for non win32 systems D566 by @januz Use Blender native dialog when OS dialog's aren't supported. --- intern/ghost/GHOST_C-api.h | 5 + intern/ghost/GHOST_ISystem.h | 6 + intern/ghost/intern/GHOST_C-api.cpp | 5 + intern/ghost/intern/GHOST_System.cpp | 5 + intern/ghost/intern/GHOST_System.h | 4 + intern/ghost/intern/GHOST_SystemNULL.h | 1 + intern/ghost/intern/GHOST_SystemSDL.cpp | 5 + intern/ghost/intern/GHOST_SystemSDL.h | 4 + intern/ghost/intern/GHOST_SystemX11.cpp | 5 + intern/ghost/intern/GHOST_SystemX11.h | 4 + release/scripts/startup/bl_ui/space_userpref.py | 6 +- source/blender/windowmanager/intern/wm_files.c | 8 ++ source/blender/windowmanager/intern/wm_operators.c | 15 ++- source/blender/windowmanager/intern/wm_window.c | 136 ++++++++++++++++++++- source/blender/windowmanager/wm_window.h | 1 + 15 files changed, 195 insertions(+), 15 deletions(-) diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 967d3f58143..55a56b1b09f 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -897,6 +897,11 @@ extern int GHOST_toggleConsole(int action); */ extern int GHOST_confirmQuit(GHOST_WindowHandle windowhandle); +/** + * Informs if the system provides native dialogs (eg. confirm quit) + */ +extern int GHOST_SupportsNativeDialogs(void); + /** * Use native pixel size (MacBook pro 'retina'), if supported. */ diff --git a/intern/ghost/GHOST_ISystem.h b/intern/ghost/GHOST_ISystem.h index 03193d6e1da..6f719572b73 100644 --- a/intern/ghost/GHOST_ISystem.h +++ b/intern/ghost/GHOST_ISystem.h @@ -418,6 +418,12 @@ public: * in the application */ virtual int confirmQuit(GHOST_IWindow *window) const = 0; + + /** + * Informs if the system provides native dialogs (eg. confirm quit) + */ + virtual bool supportsNativeDialogs(void) = 0; + protected: /** * Initialize the system. diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index ce653188760..daafe3640e0 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -893,6 +893,11 @@ int GHOST_toggleConsole(int action) return system->toggleConsole(action); } +int GHOST_SupportsNativeDialogs(void) +{ + GHOST_ISystem *system = GHOST_ISystem::getSystem(); + return system->supportsNativeDialogs(); +} int GHOST_confirmQuit(GHOST_WindowHandle windowhandle) { diff --git a/intern/ghost/intern/GHOST_System.cpp b/intern/ghost/intern/GHOST_System.cpp index 56d68b98ce0..4db2f0616d7 100644 --- a/intern/ghost/intern/GHOST_System.cpp +++ b/intern/ghost/intern/GHOST_System.cpp @@ -380,6 +380,11 @@ int GHOST_System::confirmQuit(GHOST_IWindow * /*window*/) const return 1; } +bool GHOST_System::supportsNativeDialogs(void) +{ + return 1; +} + bool GHOST_System::useNativePixel(void) { m_nativePixel = true; diff --git a/intern/ghost/intern/GHOST_System.h b/intern/ghost/intern/GHOST_System.h index af083996d91..50c893b1113 100644 --- a/intern/ghost/intern/GHOST_System.h +++ b/intern/ghost/intern/GHOST_System.h @@ -319,6 +319,10 @@ public: */ virtual int confirmQuit(GHOST_IWindow *window) const; + /** + * Informs if the system provides native dialogs (eg. confirm quit) + */ + virtual bool supportsNativeDialogs(void); protected: diff --git a/intern/ghost/intern/GHOST_SystemNULL.h b/intern/ghost/intern/GHOST_SystemNULL.h index 868416cd227..7c8d26d7486 100644 --- a/intern/ghost/intern/GHOST_SystemNULL.h +++ b/intern/ghost/intern/GHOST_SystemNULL.h @@ -52,6 +52,7 @@ public: GHOST_TSuccess setCursorPosition(GHOST_TInt32 x, GHOST_TInt32 y) { return GHOST_kFailure; } void getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const { /* nop */ } void getAllDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const { /* nop */ } + bool supportsNativeDialogs(void) { return false;} GHOST_TSuccess init() { GHOST_TSuccess success = GHOST_System::init(); diff --git a/intern/ghost/intern/GHOST_SystemSDL.cpp b/intern/ghost/intern/GHOST_SystemSDL.cpp index e9768e4b845..db555910f4b 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cpp +++ b/intern/ghost/intern/GHOST_SystemSDL.cpp @@ -635,6 +635,11 @@ GHOST_SystemSDL::addDirtyWindow(GHOST_WindowSDL *bad_wind) m_dirty_windows.push_back(bad_wind); } +bool +GHOST_SystemSDL::supportsNativeDialogs(void) +{ + return false +} GHOST_TSuccess GHOST_SystemSDL::getButtons(GHOST_Buttons& buttons) const { diff --git a/intern/ghost/intern/GHOST_SystemSDL.h b/intern/ghost/intern/GHOST_SystemSDL.h index 6f4ecec586b..41f110ed15d 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.h +++ b/intern/ghost/intern/GHOST_SystemSDL.h @@ -95,6 +95,10 @@ public: getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const; + /** + * Informs if the system provides native dialogs (eg. confirm quit) + */ + virtual bool supportsNativeDialogs(void); private: GHOST_TSuccess diff --git a/intern/ghost/intern/GHOST_SystemX11.cpp b/intern/ghost/intern/GHOST_SystemX11.cpp index 9b617a34e1a..7fde6791d78 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cpp +++ b/intern/ghost/intern/GHOST_SystemX11.cpp @@ -379,6 +379,11 @@ createWindow(const STR_String& title, return window; } +bool GHOST_SystemX11::supportsNativeDialogs(void) +{ + return false; +} + #if defined(WITH_X11_XINPUT) && defined(X_HAVE_UTF8_STRING) static void destroyIMCallback(XIM /*xim*/, XPointer ptr, XPointer /*data*/) { diff --git a/intern/ghost/intern/GHOST_SystemX11.h b/intern/ghost/intern/GHOST_SystemX11.h index e60cab6a194..9973f6e133f 100644 --- a/intern/ghost/intern/GHOST_SystemX11.h +++ b/intern/ghost/intern/GHOST_SystemX11.h @@ -97,6 +97,10 @@ public: init( ); + /** + * Informs if the system provides native dialogs (eg. confirm quit) + */ + virtual bool supportsNativeDialogs(void); /** * \section Interface Inherited from GHOST_ISystem diff --git a/release/scripts/startup/bl_ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index e61a6abbbca..7e7e6ccca2d 100644 --- a/release/scripts/startup/bl_ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -211,7 +211,6 @@ class USERPREF_PT_interface(Panel): return (userpref.active_section == 'INTERFACE') def draw(self, context): - import sys layout = self.layout userpref = context.user_preferences @@ -244,9 +243,8 @@ class USERPREF_PT_interface(Panel): col.separator() - if sys.platform[:3] == "win": - col.label("Warnings") - col.prop(view, "use_quit_dialog") + col.label("Warnings") + col.prop(view, "use_quit_dialog") row.separator() row.separator() diff --git a/source/blender/windowmanager/intern/wm_files.c b/source/blender/windowmanager/intern/wm_files.c index 16cc1d44923..551b17302fe 100644 --- a/source/blender/windowmanager/intern/wm_files.c +++ b/source/blender/windowmanager/intern/wm_files.c @@ -2075,6 +2075,10 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op) WM_event_add_notifier(C, NC_WM | ND_FILESAVE, NULL); + if (RNA_boolean_get(op->ptr, "exit")) { + WM_exit(C); + } + return OPERATOR_FINISHED; } @@ -2174,12 +2178,16 @@ void WM_OT_save_mainfile(wmOperatorType *ot) ot->check = blend_save_check; /* omit window poll so this can work in background mode */ + PropertyRNA *prop; WM_operator_properties_filesel( ot, FILE_TYPE_FOLDER | FILE_TYPE_BLENDER, FILE_BLENDER, FILE_SAVE, WM_FILESEL_FILEPATH, FILE_DEFAULTDISPLAY, FILE_SORT_ALPHA); RNA_def_boolean(ot->srna, "compress", false, "Compress", "Write compressed .blend file"); RNA_def_boolean(ot->srna, "relative_remap", false, "Remap Relative", "Remap relative paths when saving in a different directory"); + + prop = RNA_def_boolean(ot->srna, "exit", false, "Exit", "Exit Blender after saving"); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); } /** \} */ diff --git a/source/blender/windowmanager/intern/wm_operators.c b/source/blender/windowmanager/intern/wm_operators.c index 2a50de62dda..363b8e61763 100644 --- a/source/blender/windowmanager/intern/wm_operators.c +++ b/source/blender/windowmanager/intern/wm_operators.c @@ -2162,10 +2162,16 @@ static void WM_OT_window_fullscreen_toggle(wmOperatorType *ot) static int wm_exit_blender_exec(bContext *C, wmOperator *op) { - WM_operator_free(op); - - WM_exit(C); - + wmWindowManager *wm = CTX_wm_manager(C); + + if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved) { + wm_confirm_quit(C); + } + else { + WM_operator_free(op); + WM_exit(C); + } + return OPERATOR_FINISHED; } @@ -2175,7 +2181,6 @@ static void WM_OT_quit_blender(wmOperatorType *ot) ot->idname = "WM_OT_quit_blender"; ot->description = "Quit Blender"; - ot->invoke = WM_operator_confirm; ot->exec = wm_exit_blender_exec; } diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index cbf06e8c6e9..2e12ceab5b3 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -70,6 +70,7 @@ #include "ED_fileselect.h" #include "UI_interface.h" +#include "UI_resources.h" #include "PIL_time.h" @@ -295,6 +296,124 @@ wmWindow *wm_window_copy_test(bContext *C, wmWindow *win_src) } } + +/* -------------------------------------------------------------------- */ +/** \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)) +{ + wmWindow *win = CTX_wm_window(C); + UI_popup_block_close(C, win, arg_block); +} + +/** Discard the file changes and quit */ +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); + 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)) +{ + + 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_emboss_set(block, UI_EMBOSS); + + uiLayout *layout = UI_block_layout( + block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 10, 2, U.pixelsize * 480, U.pixelsize * 110, 0, style); + + /* Text and some vertical space */ + { + char *message; + if (G.main->name[0] == '\0') { + message = BLI_strdup(IFACE_("This file has not been saved yet. Save before closing?")); + } + else { + const char *basename = BLI_path_basename(G.main->name); + 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_block_bounds_set_centered(block, 10); + + return block; +} + + +/** Call the confirm dialog on quitting. */ +void wm_confirm_quit(bContext *C) +{ + wmWindowManager *wm = CTX_wm_manager(C); + + /* The popup needs to have a window set in context to show up since + * it's being called outside the normal operator event handling loop */ + if (wm->winactive) { + CTX_wm_window_set(C, wm->winactive); + } + + UI_popup_block_invoke(C, block_create_confirm_quit, NULL); +} + +/** \} */ + /* this is event from ghost, or exit-blender op */ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win) { @@ -311,19 +430,24 @@ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win) if (tmpwin == NULL) do_exit = 1; - - if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved && !G.background) { - if (do_exit) { + + if ((U.uiflag & USER_QUIT_PROMPT) && !wm->file_saved && !G.background && do_exit) { + /* We have unsaved changes and we're quitting */ + if(GHOST_SupportsNativeDialogs() == 0) { + wm_confirm_quit(C); + } + else { if (!GHOST_confirmQuit(win->ghostwin)) return; } } - - /* let WM_exit do all freeing, for correct quit.blend save */ - if (do_exit) { + else if (do_exit) { + /* No changes but we're quitting */ + /* let WM_exit do all freeing, for correct quit.blend save */ WM_exit(C); } else { + /* We're just closing a window */ bScreen *screen = win->screen; BLI_remlink(&wm->windows, win); diff --git a/source/blender/windowmanager/wm_window.h b/source/blender/windowmanager/wm_window.h index f70ec6b47f6..4313c978ef4 100644 --- a/source/blender/windowmanager/wm_window.h +++ b/source/blender/windowmanager/wm_window.h @@ -78,6 +78,7 @@ void wm_window_IME_end (wmWindow *win); int wm_window_close_exec(bContext *C, struct wmOperator *op); int wm_window_duplicate_exec(bContext *C, struct wmOperator *op); int wm_window_fullscreen_toggle_exec(bContext *C, struct wmOperator *op); +void wm_confirm_quit(bContext *C); /* Initial (unmaximized) size to start with for * systems that can't find it for themselves (X11). -- cgit v1.2.3