diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index c0b7077fb53..9460fb504a9 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -598,6 +598,16 @@ extern GHOST_TWindowState GHOST_GetWindowState(GHOST_WindowHandle windowhandle); extern GHOST_TSuccess GHOST_SetWindowState(GHOST_WindowHandle windowhandle, GHOST_TWindowState state); + +/** + * Sets the window "modified" status, indicating unsaved changes + * @param windowhandle The handle to the window + * @param isUnsavedChanges Unsaved changes or not + * @return Indication of success. + */ +extern GHOST_TSuccess GHOST_SetWindowModifiedState(GHOST_WindowHandle windowhandle, + GHOST_TUns8 isUnsavedChanges); + /** * Sets the order of the window (bottom, top). * @param windowhandle The handle to the window diff --git a/intern/ghost/GHOST_IWindow.h b/intern/ghost/GHOST_IWindow.h index d91995e35ec..ff1484909b0 100644 --- a/intern/ghost/GHOST_IWindow.h +++ b/intern/ghost/GHOST_IWindow.h @@ -161,6 +161,19 @@ public: */ virtual GHOST_TSuccess setState(GHOST_TWindowState state) = 0; + /** + * Sets the window "modified" status, indicating unsaved changes + * @param isUnsavedChanges Unsaved changes or not + * @return Indication of success. + */ + virtual GHOST_TSuccess setModifiedState(bool isUnsavedChanges) = 0; + + /** + * Gets the window "modified" status, indicating unsaved changes + * @return True if there are unsaved changes + */ + virtual bool getModifiedState() = 0; + /** * Sets the order of the window (bottom, top). * @param order The order of the window. diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 31819f341a0..14e3c4bb5f7 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -116,6 +116,12 @@ typedef enum { } GHOST_TWindowState; +/** Constants for the answer to the blender exit request */ +typedef enum { + GHOST_kExitCancel = 0, + GHOST_kExitNow +} GHOST_TExitRequestResponse; + typedef enum { GHOST_kWindowOrderTop = 0, GHOST_kWindowOrderBottom diff --git a/intern/ghost/intern/GHOST_C-api.cpp b/intern/ghost/intern/GHOST_C-api.cpp index 5c23b6c42a3..d14945d1bf8 100644 --- a/intern/ghost/intern/GHOST_C-api.cpp +++ b/intern/ghost/intern/GHOST_C-api.cpp @@ -629,6 +629,13 @@ GHOST_TSuccess GHOST_SetWindowState(GHOST_WindowHandle windowhandle, } +GHOST_TSuccess GHOST_SetWindowModifiedState(GHOST_WindowHandle windowhandle, GHOST_TUns8 isUnsavedChanges) +{ + GHOST_IWindow* window = (GHOST_IWindow*) windowhandle; + + return window->setModifiedState(isUnsavedChanges); +} + GHOST_TSuccess GHOST_SetWindowOrder(GHOST_WindowHandle windowhandle, GHOST_TWindowOrder order) diff --git a/intern/ghost/intern/GHOST_SystemCocoa.h b/intern/ghost/intern/GHOST_SystemCocoa.h index cd2cf12daa1..3e499f3d136 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.h +++ b/intern/ghost/intern/GHOST_SystemCocoa.h @@ -133,6 +133,12 @@ public: */ virtual bool processEvents(bool waitForEvent); + /** + * Handle User request to quit, from Menu bar Quit, and Cmd+Q + * Display alert panel if changes performed since last save + */ + GHOST_TUns8 handleQuitRequest(); + /*************************************************************************************** ** Cursor management functionality ***************************************************************************************/ @@ -193,39 +199,33 @@ protected: */ virtual GHOST_TSuccess init(); - /** - * Closes the system down. - * @return A success value. - */ - virtual GHOST_TSuccess exit(); - - /** * Handles a tablet event. * @param eventPtr An NSEvent pointer (casted to void* to enable compilation in standard C++) * @return Indication whether the event was handled. */ - int handleTabletEvent(void *eventPtr); - /** + GHOST_TSuccess handleTabletEvent(void *eventPtr); + + /** * Handles a mouse event. * @param eventPtr An NSEvent pointer (casted to void* to enable compilation in standard C++) * @return Indication whether the event was handled. */ - int handleMouseEvent(void *eventPtr); + GHOST_TSuccess handleMouseEvent(void *eventPtr); /** * Handles a key event. * @param eventPtr An NSEvent pointer (casted to void* to enable compilation in standard C++) * @return Indication whether the event was handled. */ - int handleKeyEvent(void *eventPtr); + GHOST_TSuccess handleKeyEvent(void *eventPtr); /** * Handles a window event. * @param eventPtr An NSEvent pointer (casted to void* to enable compilation in standard C++) * @return Indication whether the event was handled. */ - int handleWindowEvent(void *eventPtr); + GHOST_TSuccess handleWindowEvent(void *eventPtr); /** * Handles all basic Mac application stuff for a mouse down event. diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index d0c382e1469..c66153ab670 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -414,6 +414,7 @@ extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) { } -(void)setSystemCocoa:(GHOST_SystemCocoa *)sysCocoa; - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; +- (void)applicationWillTerminate:(NSNotification *)aNotification; @end @implementation CocoaAppDelegate : NSObject @@ -424,15 +425,21 @@ extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) { - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + //TODO: implement graceful termination through Cocoa mechanism to avoid session log off to be cancelled //Note that Cmd+Q is already handled by keyhandler - //FIXME: Need an event "QuitRequest" - int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes may not have been saved. Do you really want to quit ?", - @"No", @"Yes", nil); - - if (shouldQuit == NSAlertAlternateReturn) - systemCocoa->pushEvent( new GHOST_Event(systemCocoa->getMilliSeconds(), GHOST_kEventQuit, NULL) ); - - return NSTerminateCancel; + if (systemCocoa->handleQuitRequest() == GHOST_kExitNow) + return NSTerminateCancel;//NSTerminateNow; + else + return NSTerminateCancel; +} + +// To avoid cancelling a log off process, we must use Cocoa termination process +// And this function is the only chance to perform clean up +// So WM_exit needs to be called directly, as the event loop will never run before termination +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ + /*G.afbreek = 0; //Let Cocoa perform the termination at the end + WM_exit(C);*/ } @end @@ -440,7 +447,6 @@ extern "C" int GHOST_HACK_getFirstFile(char buf[FIRSTFILEBUFLG]) { #pragma mark initialization/finalization -/***/ GHOST_SystemCocoa::GHOST_SystemCocoa() { @@ -468,6 +474,8 @@ GHOST_SystemCocoa::GHOST_SystemCocoa() GHOST_SystemCocoa::~GHOST_SystemCocoa() { + NSAutoreleasePool* pool = (NSAutoreleasePool *)m_autoReleasePool; + [pool drain]; } @@ -478,7 +486,7 @@ GHOST_TSuccess GHOST_SystemCocoa::init() if (success) { //ProcessSerialNumber psn; - //FIXME: Carbon stuff to move window & menu to foreground + //Carbon stuff to move window & menu to foreground /*if (!GetCurrentProcess(&psn)) { TransformProcessType(&psn, kProcessTransformToForegroundApplication); SetFrontProcess(&psn); @@ -566,13 +574,6 @@ GHOST_TSuccess GHOST_SystemCocoa::init() } -GHOST_TSuccess GHOST_SystemCocoa::exit() -{ - NSAutoreleasePool* pool = (NSAutoreleasePool *)m_autoReleasePool; - [pool drain]; - return GHOST_System::exit(); -} - #pragma mark window management GHOST_TUns64 GHOST_SystemCocoa::getMilliSeconds() const @@ -602,11 +603,15 @@ GHOST_TUns8 GHOST_SystemCocoa::getNumDisplays() const void GHOST_SystemCocoa::getMainDisplayDimensions(GHOST_TUns32& width, GHOST_TUns32& height) const { - //TODO: Provide visible frame or total frame, check for consistency with rest of code + //Get visible frame, that is frame excluding dock and top menu bar NSRect frame = [[NSScreen mainScreen] visibleFrame]; - width = frame.size.width; - height = frame.size.height; + //Returns max window contents (excluding title bar...) + NSRect contentRect = [NSWindow contentRectForFrameRect:frame + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)]; + + width = contentRect.size.width; + height = contentRect.size.height; } @@ -623,7 +628,16 @@ GHOST_IWindow* GHOST_SystemCocoa::createWindow( ) { GHOST_IWindow* window = 0; - + + //Get the available rect for including window contents + NSRect frame = [[NSScreen mainScreen] visibleFrame]; + NSRect contentRect = [NSWindow contentRectForFrameRect:frame + styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask)]; + + //Ensures window top left is inside this available rect + left = left > contentRect.origin.x ? left : contentRect.origin.x; + top = top > contentRect.origin.y ? top : contentRect.origin.y; + window = new GHOST_WindowCocoa (title, left, top, width, height, state, type); if (window) { @@ -847,7 +861,7 @@ bool GHOST_SystemCocoa::processEvents(bool waitForEvent) } //TODO: To be called from NSWindow delegate -int GHOST_SystemCocoa::handleWindowEvent(void *eventPtr) +GHOST_TSuccess GHOST_SystemCocoa::handleWindowEvent(void *eventPtr) { /*WindowRef windowRef; GHOST_WindowCocoa *window; @@ -900,7 +914,29 @@ int GHOST_SystemCocoa::handleWindowEvent(void *eventPtr) return GHOST_kSuccess; } -int GHOST_SystemCocoa::handleTabletEvent(void *eventPtr) +GHOST_TUns8 GHOST_SystemCocoa::handleQuitRequest() +{ + //Check open windows if some changes are not saved + if (m_windowManager->getAnyModifiedState()) + { + int shouldQuit = NSRunAlertPanel(@"Exit Blender", @"Some changes have not been saved. Do you really want to quit ?", + @"Cancel", @"Quit anyway", nil); + if (shouldQuit == NSAlertAlternateReturn) + { + pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) ); + return GHOST_kExitNow; + } + } + else { + pushEvent( new GHOST_Event(getMilliSeconds(), GHOST_kEventQuit, NULL) ); + return GHOST_kExitNow; + } + + return GHOST_kExitCancel; +} + + +GHOST_TSuccess GHOST_SystemCocoa::handleTabletEvent(void *eventPtr) { NSEvent *event = (NSEvent *)eventPtr; GHOST_IWindow* window = m_windowManager->getActiveWindow(); @@ -964,7 +1000,7 @@ int GHOST_SystemCocoa::handleTabletEvent(void *eventPtr) } -int GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) +GHOST_TSuccess GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) { NSEvent *event = (NSEvent *)eventPtr; GHOST_IWindow* window = m_windowManager->getActiveWindow(); @@ -1018,11 +1054,12 @@ int GHOST_SystemCocoa::handleMouseEvent(void *eventPtr) } -int GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) +GHOST_TSuccess GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) { NSEvent *event = (NSEvent *)eventPtr; GHOST_IWindow* window = m_windowManager->getActiveWindow(); NSUInteger modifiers; + NSString *characters; GHOST_TKey keyCode; unsigned char ascii; @@ -1036,9 +1073,16 @@ int GHOST_SystemCocoa::handleKeyEvent(void *eventPtr) switch ([event type]) { case NSKeyDown: case NSKeyUp: - keyCode = convertKey([event keyCode], - [[event charactersIgnoringModifiers] characterAtIndex:0]); - ascii= convertRomanToLatin((char)[[event characters] characterAtIndex:0]); + characters = [event characters]; + if ([characters length]) { //Check for dead keys + keyCode = convertKey([event keyCode], + [[event charactersIgnoringModifiers] characterAtIndex:0]); + ascii= convertRomanToLatin((char)[characters characterAtIndex:0]); + } else { + keyCode = convertKey([event keyCode],0); + ascii= 0; + } + if ((keyCode == GHOST_kKeyQ) && (m_modifierMask & NSCommandKeyMask)) break; //Cmd-Q is directly handled by Cocoa @@ -1223,9 +1267,12 @@ GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const GHOST_TUns8 * temp_buff; size_t pastedTextSize; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; if (pasteBoard = nil) { + [pool drain]; return NULL; } @@ -1235,7 +1282,10 @@ GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const NSString *bestType = [[NSPasteboard generalPasteboard] availableTypeFromArray:supportedTypes]; - if (bestType == nil) { return NULL; } + if (bestType == nil) { + [pool drain]; + return NULL; + } NSString * textPasted = [pasteBoard stringForType:@"public.utf8-plain-text"]; @@ -1249,6 +1299,8 @@ GHOST_TUns8* GHOST_SystemCocoa::getClipboard(bool selection) const temp_buff[pastedTextSize] = '\0'; + [pool drain]; + if(temp_buff) { return temp_buff; } else { @@ -1262,10 +1314,12 @@ void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const if(selection) {return;} // for copying the selection, used on X11 - + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; if (pasteBoard = nil) { + [pool drain]; return; } @@ -1277,8 +1331,7 @@ void GHOST_SystemCocoa::putClipboard(GHOST_TInt8 *buffer, bool selection) const [pasteBoard setString:textToCopy forType:@"public.utf8-plain-text"]; - printf("\nCopy"); - + [pool drain]; } #pragma mark Carbon stuff to remove diff --git a/intern/ghost/intern/GHOST_Window.cpp b/intern/ghost/intern/GHOST_Window.cpp index 951c3bc381d..34e9f519a07 100644 --- a/intern/ghost/intern/GHOST_Window.cpp +++ b/intern/ghost/intern/GHOST_Window.cpp @@ -52,6 +52,8 @@ GHOST_Window::GHOST_Window( m_cursorShape(GHOST_kStandardCursorDefault), m_stereoVisual(stereoVisual) { + m_isUnsavedChanges = false; + m_fullScreen = state == GHOST_kWindowStateFullScreen; if (m_fullScreen) { m_fullScreenWidth = width; @@ -137,3 +139,15 @@ GHOST_TSuccess GHOST_Window::setCustomCursorShape(GHOST_TUns8 *bitmap, GHOST_TUn } } + +GHOST_TSuccess GHOST_Window::setModifiedState(bool isUnsavedChanges) +{ + m_isUnsavedChanges = isUnsavedChanges; + + return GHOST_kSuccess; +} + +bool GHOST_Window::getModifiedState() +{ + return m_isUnsavedChanges; +} \ No newline at end of file diff --git a/intern/ghost/intern/GHOST_Window.h b/intern/ghost/intern/GHOST_Window.h index 88178bae5b3..a2d1675f6ab 100644 --- a/intern/ghost/intern/GHOST_Window.h +++ b/intern/ghost/intern/GHOST_Window.h @@ -173,6 +173,19 @@ public: */ virtual GHOST_TSuccess setCursorGrab(bool grab); + /** + * Sets the window "modified" status, indicating unsaved changes + * @param isUnsavedChanges Unsaved changes or not + * @return Indication of success. + */ + virtual GHOST_TSuccess setModifiedState(bool isUnsavedChanges); + + /** + * Gets the window "modified" status, indicating unsaved changes + * @return True if there are unsaved changes + */ + virtual bool getModifiedState(); + /** * Returns the type of drawing context used in this window. * @return The current type of drawing context. @@ -262,6 +275,9 @@ protected: /** The current shape of the cursor */ GHOST_TStandardCursor m_cursorShape; + /** Modified state : are there unsaved changes */ + bool m_isUnsavedChanges; + /** Stores wether this is a full screen window. */ bool m_fullScreen; diff --git a/intern/ghost/intern/GHOST_WindowCocoa.h b/intern/ghost/intern/GHOST_WindowCocoa.h index 146d0ff47de..f383e3a7a81 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.h +++ b/intern/ghost/intern/GHOST_WindowCocoa.h @@ -143,6 +143,13 @@ public: */ virtual GHOST_TWindowState getState() const; + /** + * Sets the window "modified" status, indicating unsaved changes + * @param isUnsavedChanges Unsaved changes or not + * @return Indication of success. + */ + virtual GHOST_TSuccess setModifiedState(bool isUnsavedChanges); + /** * Converts a point in screen coordinates to client rectangle coordinates * @param inX The x-coordinate on the screen. diff --git a/intern/ghost/intern/GHOST_WindowCocoa.mm b/intern/ghost/intern/GHOST_WindowCocoa.mm index 53d7fa9b245..092443814b0 100644 --- a/intern/ghost/intern/GHOST_WindowCocoa.mm +++ b/intern/ghost/intern/GHOST_WindowCocoa.mm @@ -378,12 +378,6 @@ GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state) case GHOST_kWindowStateMinimized: ::HideWindow(m_windowRef); break; - case GHOST_kWindowStateModified: - SetWindowModified(m_windowRef, 1); - break; - case GHOST_kWindowStateUnModified: - SetWindowModified(m_windowRef, 0); - break; case GHOST_kWindowStateMaximized: case GHOST_kWindowStateNormal: default: @@ -393,6 +387,18 @@ GHOST_TSuccess GHOST_WindowCocoa::setState(GHOST_TWindowState state) return GHOST_kSuccess; } +GHOST_TSuccess GHOST_WindowCocoa::setModifiedState(bool isUnsavedChanges) +{ + if (isUnsavedChanges) { + SetWindowModified(m_windowRef, 1); + } else { + SetWindowModified(m_windowRef, 0); + } + + return GHOST_Window::setModifiedState(isUnsavedChanges); +} + + GHOST_TSuccess GHOST_WindowCocoa::setOrder(GHOST_TWindowOrder order) { diff --git a/intern/ghost/intern/GHOST_WindowManager.cpp b/intern/ghost/intern/GHOST_WindowManager.cpp index af96653db13..15ee41e3dce 100644 --- a/intern/ghost/intern/GHOST_WindowManager.cpp +++ b/intern/ghost/intern/GHOST_WindowManager.cpp @@ -187,10 +187,21 @@ void GHOST_WindowManager::setWindowInactive(const GHOST_IWindow* window) } - std::vector & -GHOST_WindowManager:: -getWindows( -){ +std::vector &GHOST_WindowManager::getWindows() +{ return m_windows; } + +bool GHOST_WindowManager::getAnyModifiedState() +{ + bool isAnyModified = false; + std::vector::iterator iter; + + for (iter = m_windows.begin(); iter != m_windows.end(); iter++) { + if ((*iter)->getModifiedState()) + isAnyModified = true; + } + + return isAnyModified; +} \ No newline at end of file diff --git a/intern/ghost/intern/GHOST_WindowManager.h b/intern/ghost/intern/GHOST_WindowManager.h index 46e80d2c603..3690ad41e2c 100644 --- a/intern/ghost/intern/GHOST_WindowManager.h +++ b/intern/ghost/intern/GHOST_WindowManager.h @@ -133,11 +133,13 @@ public: * this vector. Please do not destroy or add windows use the * interface above for this, */ + std::vector & getWindows(); - std::vector & - getWindows( - ); - + /** + * Return true if any windows has a modified status + * @return True if any window has unsaved changes + */ + bool getAnyModifiedState(); protected: /** The list of windows managed */ diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index cb2fc92a1b6..7deac8a4aa0 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -70,6 +70,10 @@ IF(WIN32) SET(INC ${INC} ${PTHREADS_INC}) ENDIF(WIN32) +IF(WITH_COCOA) + LIST(REMOVE_ITEM SRC "${CMAKE_CURRENT_SOURCE_DIR}/intern/wm_apple.c") +ENDIF(WITH_COCOA) + # TODO buildinfo IF(BF_BUILDINFO) ADD_DEFINITIONS(-DNAN_BUILDINFO) diff --git a/source/blender/windowmanager/intern/wm_window.c b/source/blender/windowmanager/intern/wm_window.c index c853afe4507..466e5868723 100644 --- a/source/blender/windowmanager/intern/wm_window.c +++ b/source/blender/windowmanager/intern/wm_window.c @@ -71,7 +71,8 @@ static int prefsizx= 0, prefsizy= 0, prefstax= 0, prefstay= 0; /* ******** win open & close ************ */ -/* XXX this one should correctly check for apple top header... */ +/* XXX this one should correctly check for apple top header... + done for Cocoa : returns window contents (and not frame) max size*/ static void wm_get_screensize(int *width_r, int *height_r) { unsigned int uiwidth; @@ -90,7 +91,7 @@ static void wm_window_check_position(rcti *rect) wm_get_screensize(&width, &height); -#ifdef __APPLE__ +#if defined(__APPLE__) && !defined(GHOST_COCOA) height -= 70; #endif @@ -269,7 +270,12 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win) else GHOST_SetTitle(win->ghostwin, "Blender"); -#ifdef __APPLE__ + /* Informs GHOST of unsaved changes, to set window modified visual indicator (MAC OS X) + and to give hint of unsaved changes for a user warning mechanism + in case of OS application terminate request (e.g. OS Shortcut Alt+F4, Cmd+Q, (...), or session end) */ + GHOST_SetWindowModifiedState(win->ghostwin, (GHOST_TUns8)!wm->file_saved); + +#if defined(__APPLE__) && !defined(GHOST_COCOA) if(wm->file_saved) GHOST_SetWindowState(win->ghostwin, GHOST_kWindowStateUnModified); else @@ -292,7 +298,7 @@ static void wm_window_add_ghostwindow(wmWindowManager *wm, char *title, wmWindow // inital_state = GHOST_kWindowStateMaximized; inital_state = GHOST_kWindowStateNormal; -#ifdef __APPLE__ +#if defined(__APPLE__) && !defined(GHOST_COCOA) { extern int macPrefState; /* creator.c */ inital_state += macPrefState; @@ -339,7 +345,8 @@ void wm_window_add_ghostwindows(wmWindowManager *wm) if (!prefsizx) { wm_get_screensize(&prefsizx, &prefsizy); -#ifdef __APPLE__ +#if defined(__APPLE__) && !defined(GHOST_COCOA) +//Cocoa provides functions to get correct max window size { extern void wm_set_apple_prefsize(int, int); /* wm_apple.c */