diff options
Diffstat (limited to 'indra/llwindow/llwindowmacosx.cpp')
-rw-r--r-- | indra/llwindow/llwindowmacosx.cpp | 2904 |
1 files changed, 2904 insertions, 0 deletions
diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp new file mode 100644 index 0000000000..4d75a30a8e --- /dev/null +++ b/indra/llwindow/llwindowmacosx.cpp @@ -0,0 +1,2904 @@ +/** + * @file llwindowmacosx.cpp + * @brief Platform-dependent implementation of llwindow + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#if LL_DARWIN + +#include "linden_common.h" + +#include <Carbon/Carbon.h> + +#include "llwindowmacosx.h" +#include "llkeyboardmacosx.h" +#include "llerror.h" +#include "llgl.h" +#include "llstring.h" +#include "lldir.h" + +#include "llglheaders.h" + +#include "indra_constants.h" + +#include "llwindowmacosx-objc.h" + +extern BOOL gDebugWindowProc; + +// culled from winuser.h +//const S32 WHEEL_DELTA = 120; /* Value for rolling one detent */ +// On the Mac, the scroll wheel reports a delta of 1 for each detent. +// There's also acceleration for faster scrolling, based on a slider in the system preferences. +const S32 WHEEL_DELTA = 1; /* Value for rolling one detent */ +const S32 BITS_PER_PIXEL = 32; +const S32 MAX_NUM_RESOLUTIONS = 32; + + +// +// LLWindowMacOSX +// + +// Cross-platform bits: + +void show_window_creation_error(const char* title) +{ + llwarns << title << llendl; + shell_open( "help/window_creation_error.html"); + /* + OSMessageBox( + "Second Life is unable to run because it can't set up your display.\n" + "We need to be able to make a 32-bit color window at 1024x768, with\n" + "an 8 bit alpha channel.\n" + "\n" + "First, be sure your monitor is set to True Color (32-bit) in\n" + "Start -> Control Panels -> Display -> Settings.\n" + "\n" + "Otherwise, this may be due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "ATI drivers are available at http://www.ati.com/\n" + "nVidia drivers are available at http://www.nvidia.com/\n" + "\n" + "If you continue to receive this message, contact customer service.", + title, + OSMB_OK); + */ +} + +BOOL check_for_card(const char* RENDERER, const char* bad_card) +{ + if (!strnicmp(RENDERER, bad_card, strlen(bad_card))) + { + char buffer[1024]; + sprintf(buffer, + "Your video card appears to be a %s, which Second Life does not support.\n" + "\n" + "Second Life requires a video card with 32 Mb of memory or more, as well as\n" + "multitexture support. We explicitly support nVidia GeForce 2 or better, \n" + "and ATI Radeon 8500 or better.\n" + "\n" + "If you own a supported card and continue to receive this message, try \n" + "updating to the latest video card drivers. Otherwise look in the\n" + "secondlife.com support section or e-mail technical support\n" + "\n" + "You can try to run Second Life, but it will probably crash or run\n" + "very slowly. Try anyway?", + bad_card); + S32 button = OSMessageBox(buffer, "Unsupported video card", OSMB_YESNO); + if (OSBTN_YES == button) + { + return FALSE; + } + else + { + return TRUE; + } + } + + return FALSE; +} + + + +// Switch to determine whether we capture all displays, or just the main one. +// We may want to base this on the setting of _DEBUG... + +#define CAPTURE_ALL_DISPLAYS 0 +static double getDictDouble (CFDictionaryRef refDict, CFStringRef key); +static long getDictLong (CFDictionaryRef refDict, CFStringRef key); + + + + +// CarbonEvents we're interested in. +static EventTypeSpec WindowHandlerEventList[] = +{ + // Window-related events + // { kEventClassWindow, kEventWindowCollapsing }, + // { kEventClassWindow, kEventWindowCollapsed }, + // { kEventClassWindow, kEventWindowShown }, + { kEventClassWindow, kEventWindowActivated }, + { kEventClassWindow, kEventWindowDeactivated }, + { kEventClassWindow, kEventWindowShown }, + { kEventClassWindow, kEventWindowHidden }, + { kEventClassWindow, kEventWindowCollapsed }, + { kEventClassWindow, kEventWindowExpanded }, + { kEventClassWindow, kEventWindowGetClickActivation }, + { kEventClassWindow, kEventWindowClose }, + { kEventClassWindow, kEventWindowBoundsChanging }, + { kEventClassWindow, kEventWindowBoundsChanged }, + // { kEventClassWindow, kEventWindowZoomed }, + // { kEventClassWindow, kEventWindowDrawContent }, + + // Mouse events + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged }, + { kEventClassMouse, kEventMouseWheelMoved }, + { kEventClassMouse, kEventMouseMoved }, + + // Keyboard events + // No longer handle raw key down events directly. + // When text input events come in, extract the raw key events from them and process at that point. + // This allows input methods to eat keystrokes the way they're supposed to. +// { kEventClassKeyboard, kEventRawKeyDown }, +// { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + + // Text input events + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } + +}; + +static EventTypeSpec GlobalHandlerEventList[] = +{ + // Mouse events + { kEventClassMouse, kEventMouseDown }, + { kEventClassMouse, kEventMouseUp }, + { kEventClassMouse, kEventMouseDragged }, + { kEventClassMouse, kEventMouseWheelMoved }, + { kEventClassMouse, kEventMouseMoved }, + + // Keyboard events + // No longer handle raw key down events directly. + // When text input events come in, extract the raw key events from them and process at that point. + // This allows input methods to eat keystrokes the way they're supposed to. +// { kEventClassKeyboard, kEventRawKeyDown }, +// { kEventClassKeyboard, kEventRawKeyRepeat }, + { kEventClassKeyboard, kEventRawKeyUp }, + { kEventClassKeyboard, kEventRawKeyModifiersChanged }, + + // Text input events + { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } +}; + +static EventTypeSpec CommandHandlerEventList[] = +{ + { kEventClassCommand, kEventCommandProcess } +}; + +// MBW -- HACK ALERT +// On the Mac, to put up an OS dialog in full screen mode, we must first switch OUT of full screen mode. +// The proper way to do this is to bracket the dialog with calls to beforeDialog() and afterDialog(), but these +// require a pointer to the LLWindowMacOSX object. Stash it here and maintain in the constructor and destructor. +// This assumes that there will be only one object of this class at any time. Hopefully this is true. +static LLWindowMacOSX *gWindowImplementation = NULL; + + + +LLWindowMacOSX::LLWindowMacOSX(char *title, char *name, S32 x, S32 y, S32 width, + S32 height, U32 flags, + BOOL fullscreen, BOOL clearBg, + BOOL disable_vsync, BOOL use_gl, + BOOL ignore_pixel_depth) + : LLWindow(fullscreen, flags) +{ + // Voodoo for calling cocoa from carbon (see llwindowmacosx-objc.mm). + setupCocoa(); + + // Initialize the keyboard + gKeyboard = new LLKeyboardMacOSX(); + + // Ignore use_gl for now, only used for drones on PC + mWindow = NULL; + mContext = NULL; + mPixelFormat = NULL; + mDisplay = CGMainDisplayID(); + mOldDisplayMode = NULL; + mTimer = NULL; + mSimulatedRightClick = FALSE; + mLastModifiers = 0; + mHandsOffEvents = FALSE; + mCursorDecoupled = FALSE; + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + mCursorIgnoreNextDelta = FALSE; + mNeedsResize = FALSE; + mOverrideAspectRatio = 0.f; + mMinimized = FALSE; + + // For reasons that aren't clear to me, LLTimers seem to be created in the "started" state. + // Since the started state of this one is used to track whether the NMRec has been installed, it wants to start out in the "stopped" state. + mBounceTimer.stop(); + + // Get the original aspect ratio of the main device. + mOriginalAspectRatio = (double)CGDisplayPixelsWide(mDisplay) / (double)CGDisplayPixelsHigh(mDisplay); + + // Stash the window title + strcpy((char*)mWindowTitle + 1, title); + mWindowTitle[0] = strlen(title); + + mEventHandlerUPP = NewEventHandlerUPP(staticEventHandler); + mGlobalHandlerRef = NULL; + mWindowHandlerRef = NULL; + + // We're not clipping yet + SetRect( &mOldMouseClip, 0, 0, 0, 0 ); + + // Set up global event handlers (the fullscreen case needs this) + InstallStandardEventHandler(GetApplicationEventTarget()); + + // Stash an object pointer for OSMessageBox() + gWindowImplementation = this; + + // Create the GL context and set it up for windowed or fullscreen, as appropriate. + if(createContext(x, y, width, height, 32, fullscreen, disable_vsync)) + { + if(mWindow != NULL) + { + // MBW -- XXX -- I think we can now do this here? + // Constrain the window to the screen it's mostly on, resizing if necessary. + ConstrainWindowToScreen( + mWindow, + kWindowStructureRgn, + kWindowConstrainMayResize | + // kWindowConstrainStandardOptions | + 0, + NULL, + NULL); + + MacShowWindow(mWindow); + BringToFront(mWindow); + } + + if (!gGLManager.initGL()) + { + setupFailure( + "Second Life is unable to run because your video card drivers\n" + "are out of date or unsupported. Please make sure you have\n" + "the latest video card drivers installed.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return; + } + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + + stop_glerror(); +} + +BOOL LLWindowMacOSX::createContext(int x, int y, int width, int height, int bits, BOOL fullscreen, BOOL disable_vsync) +{ + OSStatus err; + BOOL glNeedsInit = FALSE; + + if(mGlobalHandlerRef == NULL) + { + InstallApplicationEventHandler(mEventHandlerUPP, GetEventTypeCount (CommandHandlerEventList), CommandHandlerEventList, (void*)this, &mGlobalHandlerRef); + } + + mFullscreen = fullscreen; + + if (mFullscreen && (mOldDisplayMode == NULL)) + { + llinfos << "createContext: setting up fullscreen " << width << "x" << height << llendl; + + // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays. Plan accordingly. + double refresh = getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate); + + // If the requested width or height is 0, find the best default for the monitor. + if((width == 0) || (height == 0)) + { + // Scan through the list of modes, looking for one which has: + // height between 700 and 800 + // aspect ratio closest to the user's original mode + S32 resolutionCount = 0; + LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); + + if(resolutionList != NULL) + { + F32 closestAspect = 0; + U32 closestHeight = 0; + U32 closestWidth = 0; + int i; + + llinfos << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << llendl; + + for(i=0; i < resolutionCount; i++) + { + F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; + + llinfos << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << llendl; + + if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && + (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) + { + llinfos << " (new closest mode) " << llendl; + + // This is the closest mode we've seen yet. + closestWidth = resolutionList[i].mWidth; + closestHeight = resolutionList[i].mHeight; + closestAspect = aspect; + } + } + + width = closestWidth; + height = closestHeight; + } + } + + if((width == 0) || (height == 0)) + { + // Mode search failed for some reason. Use the old-school default. + width = 1024; + height = 768; + } + + if (true) + { + // Fullscreen support + CFDictionaryRef refDisplayMode = 0; + boolean_t exactMatch = false; + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + CGCaptureAllDisplays (); +#else + // Capture only the main display (useful for debugging) + CGDisplayCapture (mDisplay); +#endif + + // Switch the display to the desired resolution and refresh + refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate( + mDisplay, + BITS_PER_PIXEL, + width, + height, + refresh, + &exactMatch); + + if (refDisplayMode) + { + llinfos << "createContext: switching display resolution" << llendl; + mOldDisplayMode = CGDisplayCurrentMode (mDisplay); + CGDisplaySwitchToMode (mDisplay, refDisplayMode); + // CFRelease(refDisplayMode); + + AddEventTypesToHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList); + } + + + mFullscreen = TRUE; + mFullscreenWidth = CGDisplayPixelsWide(mDisplay); + mFullscreenHeight = CGDisplayPixelsHigh(mDisplay); + mFullscreenBits = CGDisplayBitsPerPixel(mDisplay); + mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate)); + + llinfos << "Running at " << mFullscreenWidth + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << llendl; + } + else + { + // No fullscreen support + mFullscreen = FALSE; + mFullscreenWidth = -1; + mFullscreenHeight = -1; + mFullscreenBits = -1; + mFullscreenRefresh = -1; + + char error[256]; + sprintf(error, "Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); + OSMessageBox(error, "Error", OSMB_OK); + } + } + + if(!mFullscreen && (mWindow == NULL)) + { + Rect window_rect; + //int displayWidth = CGDisplayPixelsWide(mDisplay); + //int displayHeight = CGDisplayPixelsHigh(mDisplay); + //const int menuBarPlusTitleBar = 44; // Ugly magic number. + + llinfos << "createContext: creating window" << llendl; + + window_rect.left = (long) x; + window_rect.right = (long) x + width; + window_rect.top = (long) y; + window_rect.bottom = (long) y + height; + + //----------------------------------------------------------------------- + // Create the window + //----------------------------------------------------------------------- + mWindow = NewCWindow( + NULL, + &window_rect, + mWindowTitle, + false, // Create the window invisible. Whoever calls createContext() should show it after any moving/resizing. + // noGrowDocProc, // Window with no grow box and no zoom box + zoomDocProc, // Window with a grow box and a zoom box + // zoomNoGrow, // Window with a zoom box but no grow box + kFirstWindowOfClass, + true, + (long)this); + + + if (!mWindow) + { + setupFailure("Window creation error", "Error", OSMB_OK); + return FALSE; + } + + // Turn on live resize. + // For this to work correctly, we need to be able to call LLViewerWindow::draw from + // the event handler for kEventWindowBoundsChanged. It's not clear that we have access from here. + // err = ChangeWindowAttributes(mWindow, kWindowLiveResizeAttribute, 0); + + // Set up window event handlers (some window-related events ONLY go to window handlers.) + InstallStandardEventHandler(GetWindowEventTarget(mWindow)); + InstallWindowEventHandler (mWindow, mEventHandlerUPP, GetEventTypeCount (WindowHandlerEventList), WindowHandlerEventList, (void*)this, &mWindowHandlerRef); // add event handler + + } + + if(mContext == NULL) + { + AGLRendererInfo rendererInfo = NULL; + + //----------------------------------------------------------------------- + // Create GL drawing context + //----------------------------------------------------------------------- + + if(mPixelFormat == NULL) + { + if(mFullscreen) + { + GLint fullscreenAttrib[] = + { + AGL_RGBA, + AGL_FULLSCREEN, + // AGL_NO_RECOVERY, // MBW -- XXX -- Not sure if we want this attribute + AGL_DOUBLEBUFFER, + AGL_CLOSEST_POLICY, + AGL_ACCELERATED, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 24, + AGL_STENCIL_SIZE, 8, + AGL_NONE + }; + + llinfos << "createContext: creating fullscreen pixelformat" << llendl; + + GDHandle gdhDisplay = NULL; + err = DMGetGDeviceByDisplayID ((DisplayIDType)mDisplay, &gdhDisplay, false); + + mPixelFormat = aglChoosePixelFormat(&gdhDisplay, 1, fullscreenAttrib); + rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1); + } + else + { + GLint windowedAttrib[] = + { + AGL_RGBA, + AGL_DOUBLEBUFFER, + AGL_CLOSEST_POLICY, + AGL_ACCELERATED, + AGL_RED_SIZE, 8, + AGL_GREEN_SIZE, 8, + AGL_BLUE_SIZE, 8, + AGL_ALPHA_SIZE, 8, + AGL_DEPTH_SIZE, 24, + AGL_STENCIL_SIZE, 8, + AGL_NONE + }; + + llinfos << "createContext: creating windowed pixelformat" << llendl; + + mPixelFormat = aglChoosePixelFormat(NULL, 0, windowedAttrib); + + GDHandle gdhDisplay = GetMainDevice(); + rendererInfo = aglQueryRendererInfo(&gdhDisplay, 1); + } + + // May want to get the real error text like this: + // (char *) aglErrorString(aglGetError()); + + if(aglGetError() != AGL_NO_ERROR) + { + setupFailure("Can't find suitable pixel format", "Error", OSMB_OK); + return FALSE; + } + } + + if(mPixelFormat) + { + llinfos << "createContext: creating GL context" << llendl; + mContext = aglCreateContext(mPixelFormat, NULL); + } + + if(mContext == NULL) + { + setupFailure("Can't make GL context", "Error", OSMB_OK); + return FALSE; + } + + gGLManager.mVRAM = 0; + + if(rendererInfo != NULL) + { + GLint result; + + if(aglDescribeRenderer(rendererInfo, AGL_VIDEO_MEMORY, &result)) + { + // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) returned " << result << llendl; + gGLManager.mVRAM = result / (1024 * 1024); + } + else + { + // llinfos << "createContext: aglDescribeRenderer(AGL_VIDEO_MEMORY) failed." << llendl; + } + + // This could be useful at some point, if it takes into account the memory already used by screen buffers, etc... + if(aglDescribeRenderer(rendererInfo, AGL_TEXTURE_MEMORY, &result)) + { + // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) returned " << result << llendl; + } + else + { + // llinfos << "createContext: aglDescribeRenderer(AGL_TEXTURE_MEMORY) failed." << llendl; + } + + aglDestroyRendererInfo(rendererInfo); + } + + // Since we just created the context, it needs to be set up. + glNeedsInit = TRUE; + } + + // Hook up the context to a drawable + if (mFullscreen && (mOldDisplayMode != NULL)) + { + // We successfully captured the display. Use a fullscreen drawable + + llinfos << "createContext: attaching fullscreen drawable" << llendl; + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + aglDisable (mContext, AGL_FS_CAPTURE_SINGLE); +#else + // Capture only the main display (useful for debugging) + aglEnable (mContext, AGL_FS_CAPTURE_SINGLE); +#endif + + if (!aglSetFullScreen (mContext, 0, 0, 0, 0)) + { + setupFailure("Can't set GL fullscreen", "Error", OSMB_OK); + return FALSE; + } + } + else if(!mFullscreen && (mWindow != NULL)) + { + llinfos << "createContext: attaching windowed drawable" << llendl; + + // We created a window. Use it as the drawable. + if(!aglSetDrawable(mContext, GetWindowPort (mWindow))) + { + setupFailure("Can't set GL drawable", "Error", OSMB_OK); + return FALSE; + } + } + else + { + setupFailure("Can't get fullscreen or windowed drawable.", "Error", OSMB_OK); + return FALSE; + } + + if(mContext != NULL) + { + llinfos << "createContext: setting current context" << llendl; + + if (!aglSetCurrentContext(mContext)) + { + setupFailure("Can't activate GL rendering context", "Error", OSMB_OK); + return FALSE; + } + } + + if(glNeedsInit) + { + // Check for some explicitly unsupported cards. + const char* RENDERER = (const char*) glGetString(GL_RENDERER); + + const char* CARD_LIST[] = + { "RAGE 128", + "RIVA TNT2", + "Intel 810", + "3Dfx/Voodoo3", + "Radeon 7000", + "Radeon 7200", + "Radeon 7500", + "Radeon DDR", + "Radeon VE", + "GDI Generic" }; + const S32 CARD_COUNT = sizeof(CARD_LIST)/sizeof(char*); + + // Future candidates: + // ProSavage/Twister + // SuperSavage + + S32 i; + for (i = 0; i < CARD_COUNT; i++) + { + if (check_for_card(RENDERER, CARD_LIST[i])) + { + close(); + shell_open( "help/unsupported_card.html" ); + return FALSE; + } + } + } + + GLint colorBits, alphaBits, depthBits, stencilBits; + + if( !aglDescribePixelFormat(mPixelFormat, AGL_BUFFER_SIZE, &colorBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_ALPHA_SIZE, &alphaBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_DEPTH_SIZE, &depthBits) || + !aglDescribePixelFormat(mPixelFormat, AGL_STENCIL_SIZE, &stencilBits)) + { + close(); + setupFailure("Can't get pixel format description", "Error", OSMB_OK); + return FALSE; + } + + llinfos << "GL buffer: Color Bits " << S32(colorBits) + << " Alpha Bits " << S32(alphaBits) + << " Depth Bits " << S32(depthBits) + << " Stencil Bits" << S32(stencilBits) + << llendl; + + if (colorBits < 32) + { + close(); + setupFailure( + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); + return FALSE; + } + + if (alphaBits < 8) + { + close(); + setupFailure( + "Second Life is unable to run because it can't get an 8 bit alpha\n" + "channel. Usually this is due to video card driver issues.\n" + "Please make sure you have the latest video card drivers installed.\n" + "Also be sure your monitor is set to True Color (32-bit) in\n" + "Control Panels -> Display -> Settings.\n" + "If you continue to receive this message, contact customer service.", + "Error", + OSMB_OK); + return FALSE; + } + + // Disable vertical sync for swap + GLint frames_per_swap = 0; + if (disable_vsync) + { + llinfos << "Disabling vertical sync" << llendl; + frames_per_swap = 0; + } + else + { + llinfos << "Keeping vertical sync" << llendl; + frames_per_swap = 1; + } + aglSetInteger(mContext, AGL_SWAP_INTERVAL, &frames_per_swap); + + // Don't need to get the current gamma, since there's a call that restores it to the system defaults. + return TRUE; +} + + +// changing fullscreen resolution, or switching between windowed and fullscreen mode. +BOOL LLWindowMacOSX::switchContext(BOOL fullscreen, LLCoordScreen size, BOOL disable_vsync) +{ + BOOL needsRebuild = FALSE; + BOOL result = true; + + if(fullscreen) + { + if(mFullscreen) + { + // Switching resolutions in fullscreen mode. Don't need to rebuild for this. + // Fullscreen support + CFDictionaryRef refDisplayMode = 0; + boolean_t exactMatch = false; + + // Switch the display to the desired resolution and refresh + refDisplayMode = CGDisplayBestModeForParametersAndRefreshRate( + mDisplay, + BITS_PER_PIXEL, + size.mX, + size.mY, + getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate), + &exactMatch); + + if (refDisplayMode) + { + CGDisplaySwitchToMode (mDisplay, refDisplayMode); + // CFRelease(refDisplayMode); + } + + mFullscreenWidth = CGDisplayPixelsWide(mDisplay); + mFullscreenHeight = CGDisplayPixelsHigh(mDisplay); + mFullscreenBits = CGDisplayBitsPerPixel(mDisplay); + mFullscreenRefresh = llround(getDictDouble (CGDisplayCurrentMode (mDisplay), kCGDisplayRefreshRate)); + + llinfos << "Switched resolution to " << mFullscreenWidth + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << llendl; + + // Update the GL context to the new screen size + if (!aglUpdateContext(mContext)) + { + setupFailure("Can't set GL fullscreen", "Error", OSMB_OK); + result = FALSE; + } + } + else + { + // Switching from windowed to fullscreen + needsRebuild = TRUE; + } + } + else + { + if(mFullscreen) + { + // Switching from fullscreen to windowed + needsRebuild = TRUE; + } + else + { + // Windowed to windowed -- not sure why we would be called like this. Just change the window size. + // The bounds changed event handler will do the rest. + if(mWindow != NULL) + { + ::SizeWindow(mWindow, size.mX, size.mY, true); + } + } + } + + stop_glerror(); + if(needsRebuild) + { + destroyContext(); + result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, disable_vsync); + if (result) + { + if(mWindow != NULL) + { + MacShowWindow(mWindow); + BringToFront(mWindow); + } + + llverify(gGLManager.initGL()); + + //start with arrow cursor + initCursors(); + setCursor( UI_CURSOR_ARROW ); + } + } + + stop_glerror(); + + return result; +} + +void LLWindowMacOSX::destroyContext() +{ + if (!mContext) + { + // We don't have a context + return; + } + // Unhook the GL context from any drawable it may have + if(mContext != NULL) + { + llinfos << "destroyContext: unhooking drawable " << llendl; + + aglSetCurrentContext (NULL); + aglSetDrawable(mContext, NULL); + } + + // Make sure the display resolution gets restored + if(mOldDisplayMode != NULL) + { + llinfos << "destroyContext: restoring display resolution " << llendl; + + CGDisplaySwitchToMode (mDisplay, mOldDisplayMode); + +#if CAPTURE_ALL_DISPLAYS + // Uncapture all displays (may want to do this for final build) + CGReleaseAllDisplays (); +#else + // Uncapture only the main display (useful for debugging) + CGDisplayRelease (mDisplay); +#endif + + // CFRelease(mOldDisplayMode); + + mOldDisplayMode = NULL; + + // Remove the global event handlers the fullscreen case needed + RemoveEventTypesFromHandler(mGlobalHandlerRef, GetEventTypeCount (GlobalHandlerEventList), GlobalHandlerEventList); + } + + // Clean up remaining GL state before blowing away window + gGLManager.shutdownGL(); + + // Clean up the pixel format + if(mPixelFormat != NULL) + { + llinfos << "destroyContext: destroying pixel format " << llendl; + aglDestroyPixelFormat(mPixelFormat); + mPixelFormat = NULL; + } + + // Remove any Carbon Event handlers we installed + if(mGlobalHandlerRef != NULL) + { + llinfos << "destroyContext: removing global event handler" << llendl; + RemoveEventHandler(mGlobalHandlerRef); + mGlobalHandlerRef = NULL; + } + + if(mWindowHandlerRef != NULL) + { + llinfos << "destroyContext: removing window event handler" << llendl; + RemoveEventHandler(mWindowHandlerRef); + mWindowHandlerRef = NULL; + } + + // Close the window + if(mWindow != NULL) + { + llinfos << "destroyContext: disposing window" << llendl; + DisposeWindow(mWindow); + mWindow = NULL; + } + + // Clean up the GL context + if(mContext != NULL) + { + llinfos << "destroyContext: destroying GL context" << llendl; + aglDestroyContext(mContext); + mContext = NULL; + } + +} + +LLWindowMacOSX::~LLWindowMacOSX() +{ + destroyContext(); + + if(mSupportedResolutions != NULL) + { + delete []mSupportedResolutions; + } + + gWindowImplementation = NULL; + +} + + +void LLWindowMacOSX::show() +{ + if(IsWindowCollapsed(mWindow)) + CollapseWindow(mWindow, false); + + MacShowWindow(mWindow); + BringToFront(mWindow); +} + +void LLWindowMacOSX::hide() +{ + setMouseClipping(FALSE); + HideWindow(mWindow); +} + +void LLWindowMacOSX::minimize() +{ + setMouseClipping(FALSE); + showCursor(); + CollapseWindow(mWindow, true); +} + +void LLWindowMacOSX::restore() +{ + show(); +} + + +// close() destroys all OS-specific code associated with a window. +// Usually called from LLWindowManager::destroyWindow() +void LLWindowMacOSX::close() +{ + // Is window is already closed? + // if (!mWindow) + // { + // return; + // } + + // Make sure cursor is visible and we haven't mangled the clipping state. + setMouseClipping(FALSE); + showCursor(); + + destroyContext(); +} + +BOOL LLWindowMacOSX::isValid() +{ + if(mFullscreen) + { + return(TRUE); + } + + return (mWindow != NULL); +} + +BOOL LLWindowMacOSX::getVisible() +{ + BOOL result = FALSE; + + if(mFullscreen) + { + result = TRUE; + }if (mWindow) + { + if(MacIsWindowVisible(mWindow)) + result = TRUE; + } + + return(result); +} + +BOOL LLWindowMacOSX::getMinimized() +{ + BOOL result = FALSE; + + // Since the set of states where we want to act "minimized" is non-trivial, it's easier to + // track things locally than to try and retrieve the state from the window manager. + result = mMinimized; + + return(result); +} + +BOOL LLWindowMacOSX::getMaximized() +{ + BOOL result = FALSE; + + if (mWindow) + { + // TODO + } + + return(result); +} + +BOOL LLWindowMacOSX::maximize() +{ + // TODO + return FALSE; +} + +BOOL LLWindowMacOSX::getFullscreen() +{ + return mFullscreen; +} + +void LLWindowMacOSX::gatherInput() +{ + // stop bouncing icon after fixed period of time + if (mBounceTimer.getStarted() && mBounceTimer.getElapsedTimeF32() > mBounceTime) + { + stopDockTileBounce(); + } + + // Use the old-school version so we get AppleEvent handler dispatch and menuselect handling. + // Anything that has an event handler will get processed inside WaitNextEvent, so we only need to handle + // the odd stuff here. + EventRecord evt; + while(WaitNextEvent(everyEvent, &evt, 0, NULL)) + { + // printf("WaitNextEvent returned true, event is %d.\n", evt.what); + switch(evt.what) + { + case mouseDown: + { + short part; + WindowRef window; + long selectResult; + part = FindWindow(evt.where, &window); + switch ( part ) + { + case inMenuBar: + selectResult = MenuSelect(evt.where); + + HiliteMenu(0); + break; + } + } + break; + + case kHighLevelEvent: + AEProcessAppleEvent (&evt); + break; + + case updateEvt: + // We shouldn't be getting these regularly (since our window will be buffered), but we need to handle them correctly... + BeginUpdate((WindowRef)evt.message); + EndUpdate((WindowRef)evt.message); + break; + + } + } +} + +BOOL LLWindowMacOSX::getPosition(LLCoordScreen *position) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + position->mX = 0; + position->mY = 0; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + position->mX = window_rect.left; + position->mY = window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::getSize(LLCoordScreen *size) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + size->mX = mFullscreenWidth; + size->mY = mFullscreenHeight; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + size->mX = window_rect.right - window_rect.left; + size->mY = window_rect.bottom - window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::getSize(LLCoordWindow *size) +{ + Rect window_rect; + OSStatus err = -1; + + if(mFullscreen) + { + size->mX = mFullscreenWidth; + size->mY = mFullscreenHeight; + err = noErr; + } + else if(mWindow) + { + err = GetWindowBounds(mWindow, kWindowContentRgn, &window_rect); + + size->mX = window_rect.right - window_rect.left; + size->mY = window_rect.bottom - window_rect.top; + } + else + { + llerrs << "LLWindowMacOSX::getPosition(): no window and not fullscreen!" << llendl; + } + + return (err == noErr); +} + +BOOL LLWindowMacOSX::setPosition(const LLCoordScreen position) +{ + if(mWindow) + { + MacMoveWindow(mWindow, position.mX, position.mY, false); + } + + return TRUE; +} + +BOOL LLWindowMacOSX::setSize(const LLCoordScreen size) +{ + if(mWindow) + { + SizeWindow(mWindow, size.mX, size.mY, true); + } + + return TRUE; +} + +void LLWindowMacOSX::swapBuffers() +{ + aglSwapBuffers(mContext); +} + +F32 LLWindowMacOSX::getGamma() +{ + F32 result = 1.8; // Default to something sane + + CGGammaValue redMin; + CGGammaValue redMax; + CGGammaValue redGamma; + CGGammaValue greenMin; + CGGammaValue greenMax; + CGGammaValue greenGamma; + CGGammaValue blueMin; + CGGammaValue blueMax; + CGGammaValue blueGamma; + + if(CGGetDisplayTransferByFormula( + mDisplay, + &redMin, + &redMax, + &redGamma, + &greenMin, + &greenMax, + &greenGamma, + &blueMin, + &blueMax, + &blueGamma) == noErr) + { + // So many choices... + // Let's just return the green channel gamma for now. + result = greenGamma; + } + + return result; +} + +BOOL LLWindowMacOSX::restoreGamma() +{ + CGDisplayRestoreColorSyncSettings(); + return true; +} + +BOOL LLWindowMacOSX::setGamma(const F32 gamma) +{ + CGGammaValue redMin; + CGGammaValue redMax; + CGGammaValue redGamma; + CGGammaValue greenMin; + CGGammaValue greenMax; + CGGammaValue greenGamma; + CGGammaValue blueMin; + CGGammaValue blueMax; + CGGammaValue blueGamma; + + // MBW -- XXX -- Should we allow this in windowed mode? + + if(CGGetDisplayTransferByFormula( + mDisplay, + &redMin, + &redMax, + &redGamma, + &greenMin, + &greenMax, + &greenGamma, + &blueMin, + &blueMax, + &blueGamma) != noErr) + { + return false; + } + + if(CGSetDisplayTransferByFormula( + mDisplay, + redMin, + redMax, + gamma, + greenMin, + greenMax, + gamma, + blueMin, + blueMax, + gamma) != noErr) + { + return false; + } + + + return true; +} + +BOOL LLWindowMacOSX::isCursorHidden() +{ + return mCursorHidden; +} + + + +// Constrains the mouse to the window. +void LLWindowMacOSX::setMouseClipping( BOOL b ) +{ + // Just stash the requested state. We'll simulate this when the cursor is hidden by decoupling. + mIsMouseClipping = b; + + if(b) + { + // llinfos << "setMouseClipping(TRUE)" << llendl + } + else + { + // llinfos << "setMouseClipping(FALSE)" << llendl + } + + adjustCursorDecouple(); +} + +BOOL LLWindowMacOSX::setCursorPosition(const LLCoordWindow position) +{ + BOOL result = FALSE; + LLCoordScreen screen_pos; + + if (!convertCoords(position, &screen_pos)) + { + return FALSE; + } + + CGPoint newPosition; + + // llinfos << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << llendl + + newPosition.x = screen_pos.mX; + newPosition.y = screen_pos.mY; + + CGSetLocalEventsSuppressionInterval(0.0); + if(CGWarpMouseCursorPosition(newPosition) == noErr) + { + result = TRUE; + } + + // Under certain circumstances, this will trigger us to decouple the cursor. + adjustCursorDecouple(true); + + return result; +} + +static void fixOrigin(void) +{ + GrafPtr port; + Rect portrect; + + ::GetPort(&port); + ::GetPortBounds(port, &portrect); + if((portrect.left != 0) || (portrect.top != 0)) + { + // Mozilla sometimes changes our port origin. Fuckers. + ::SetOrigin(0,0); + } +} + +BOOL LLWindowMacOSX::getCursorPosition(LLCoordWindow *position) +{ + Point cursor_point; + LLCoordScreen screen_pos; + GrafPtr save; + + if(mWindow == NULL) + return FALSE; + + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + // gets the mouse location in local coordinates + ::GetMouse(&cursor_point); + +// lldebugs << "getCursorPosition(): cursor is at " << cursor_point.h << ", " << cursor_point.v << " port origin: " << portrect.left << ", " << portrect.top << llendl; + + ::SetPort(save); + + if(mCursorDecoupled) + { + // CGMouseDelta x, y; + + // If the cursor's decoupled, we need to read the latest movement delta as well. + // CGGetLastMouseDelta( &x, &y ); + // cursor_point.h += x; + // cursor_point.v += y; + + // CGGetLastMouseDelta may behave strangely when the cursor's first captured. + // Stash in the event handler instead. + cursor_point.h += mCursorLastEventDeltaX; + cursor_point.v += mCursorLastEventDeltaY; + } + + position->mX = cursor_point.h; + position->mY = cursor_point.v; + + return TRUE; +} + +void LLWindowMacOSX::adjustCursorDecouple(bool warpingMouse) +{ + if(mIsMouseClipping && mCursorHidden) + { + if(warpingMouse) + { + // The cursor should be decoupled. Make sure it is. + if(!mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: decoupling cursor" << llendl; + CGAssociateMouseAndMouseCursorPosition(false); + mCursorDecoupled = true; + mCursorIgnoreNextDelta = TRUE; + } + } + } + else + { + // The cursor should not be decoupled. Make sure it isn't. + if(mCursorDecoupled) + { + // llinfos << "adjustCursorDecouple: recoupling cursor" << llendl; + CGAssociateMouseAndMouseCursorPosition(true); + mCursorDecoupled = false; + } + } +} + +F32 LLWindowMacOSX::getNativeAspectRatio() +{ + if (mFullscreen) + { + return (F32)mFullscreenWidth / (F32)mFullscreenHeight; + } + else + { + // The constructor for this class grabs the aspect ratio of the monitor before doing any resolution + // switching, and stashes it in mOriginalAspectRatio. Here, we just return it. + + if (mOverrideAspectRatio > 0.f) + { + return mOverrideAspectRatio; + } + + return mOriginalAspectRatio; + } +} + +F32 LLWindowMacOSX::getPixelAspectRatio() +{ + //OS X always enforces a 1:1 pixel aspect ratio, regardless of video mode + return 1.f; +} + +//static SInt32 oldWindowLevel; + +// MBW -- XXX -- There's got to be a better way than this. Find it, please... + +void LLWindowMacOSX::beforeDialog() +{ + if(mFullscreen) + { + +#if CAPTURE_ALL_DISPLAYS + // Uncapture all displays (may want to do this for final build) + CGReleaseAllDisplays (); +#else + // Uncapture only the main display (useful for debugging) + CGDisplayRelease (mDisplay); +#endif + // kDocumentWindowClass + // kMovableModalWindowClass + // kAllWindowClasses + + // GLint order = 0; + // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order); + aglSetDrawable(mContext, NULL); + // GetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), &oldWindowLevel); + // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), CGShieldingWindowLevel()); + + mHandsOffEvents = TRUE; + + } +} + +void LLWindowMacOSX::afterDialog() +{ + if(mFullscreen) + { + mHandsOffEvents = FALSE; + + // SetWindowGroupLevel(GetWindowGroupOfClass(kAllWindowClasses), oldWindowLevel); + aglSetFullScreen(mContext, 0, 0, 0, 0); + // GLint order = 1; + // aglSetInteger(mContext, AGL_ORDER_CONTEXT_TO_FRONT, &order); + +#if CAPTURE_ALL_DISPLAYS + // Capture all displays (may want to do this for final build) + CGCaptureAllDisplays (); +#else + // Capture only the main display (useful for debugging) + CGDisplayCapture (mDisplay); +#endif + } +} + + +S32 LLWindowMacOSX::stat(const char* file_name, struct stat* stat_info) +{ + return ::stat( file_name, stat_info ); +} + +void LLWindowMacOSX::flashIcon(F32 seconds) +{ + // Don't do this if we're already started, since this would try to install the NMRec twice. + if(!mBounceTimer.getStarted()) + { + OSErr err; + + mBounceTime = seconds; + memset(&mBounceRec, sizeof(mBounceRec), 0); + mBounceRec.qType = nmType; + mBounceRec.nmMark = 1; + err = NMInstall(&mBounceRec); + if(err == noErr) + { + mBounceTimer.start(); + } + else + { + // This is very not-fatal (only problem is the icon will not bounce), but we'd like to find out about it somehow... + llinfos << "NMInstall failed with error code " << err << llendl; + } + } +} + +BOOL LLWindowMacOSX::isClipboardTextAvailable() +{ + OSStatus err; + ScrapRef scrap; + ScrapFlavorFlags flags; + BOOL result = false; + + err = GetCurrentScrap(&scrap); + + if(err == noErr) + { + err = GetScrapFlavorFlags(scrap, kScrapFlavorTypeUnicode, &flags); + } + + if(err == noErr) + result = true; + + return result; +} + +BOOL LLWindowMacOSX::pasteTextFromClipboard(LLWString &dst) +{ + OSStatus err; + ScrapRef scrap; + Size len; + BOOL result = false; + + err = GetCurrentScrap(&scrap); + + if(err == noErr) + { + err = GetScrapFlavorSize(scrap, kScrapFlavorTypeUnicode, &len); + } + + if((err == noErr) && (len > 0)) + { + int u16len = len / sizeof(U16); + U16 *temp = new U16[u16len + 1]; + if (temp) + { + memset(temp, 0, (u16len + 1) * sizeof(temp[0])); + err = GetScrapFlavorData(scrap, kScrapFlavorTypeUnicode, &len, temp); + if (err == noErr) + { + // convert \r\n to \n and \r to \n in the incoming text. + U16 *s, *d; + for(s = d = temp; s[0] != '\0'; s++, d++) + { + if(s[0] == '\r') + { + if(s[1] == '\n') + { + // CRLF, a.k.a. DOS newline. Collapse to a single '\n'. + s++; + } + + d[0] = '\n'; + } + else + { + d[0] = s[0]; + } + } + + d[0] = '\0'; + + dst = utf16str_to_wstring(temp); + + result = true; + } + delete[] temp; + } + } + + return result; +} + +BOOL LLWindowMacOSX::copyTextToClipboard(const LLWString &s) +{ + OSStatus err; + ScrapRef scrap; + //Size len; + //char *temp; + BOOL result = false; + + if (!s.empty()) + { + err = GetCurrentScrap(&scrap); + if (err == noErr) + err = ClearScrap(&scrap); + + if (err == noErr) + { + llutf16string utf16str = wstring_to_utf16str(s); + size_t u16len = utf16str.length() * sizeof(U16); + err = PutScrapFlavor(scrap, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, u16len, utf16str.data()); + if (err == noErr) + result = true; + } + } + + return result; +} + + +BOOL LLWindowMacOSX::sendEmail(const char* address, const char* subject, const char* body_text, + const char* attachment, const char* attachment_displayed_name ) +{ + // MBW -- XXX -- Um... yeah. I'll get to this later. + + return false; +} + + +// protected +BOOL LLWindowMacOSX::resetDisplayResolution() +{ + // This is only called from elsewhere in this class, and it's not used by the Mac implementation. + return true; +} + + +LLWindow::LLWindowResolution* LLWindowMacOSX::getSupportedResolutions(S32 &num_resolutions) +{ + if (!mSupportedResolutions) + { + CFArrayRef modes = CGDisplayAvailableModes(mDisplay); + + if(modes != NULL) + { + CFIndex index, cnt; + + mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS]; + mNumSupportedResolutions = 0; + + // Examine each mode + cnt = CFArrayGetCount( modes ); + + for ( index = 0; (index < cnt) && (mNumSupportedResolutions < MAX_NUM_RESOLUTIONS); index++ ) + { + // Pull the mode dictionary out of the CFArray + CFDictionaryRef mode = (CFDictionaryRef)CFArrayGetValueAtIndex( modes, index ); + long width = getDictLong(mode, kCGDisplayWidth); + long height = getDictLong(mode, kCGDisplayHeight); + long bits = getDictLong(mode, kCGDisplayBitsPerPixel); + + if(bits == BITS_PER_PIXEL && width >= 800 && height >= 600) + { + BOOL resolution_exists = FALSE; + for(S32 i = 0; i < mNumSupportedResolutions; i++) + { + if (mSupportedResolutions[i].mWidth == width && + mSupportedResolutions[i].mHeight == height) + { + resolution_exists = TRUE; + } + } + if (!resolution_exists) + { + mSupportedResolutions[mNumSupportedResolutions].mWidth = width; + mSupportedResolutions[mNumSupportedResolutions].mHeight = height; + mNumSupportedResolutions++; + } + } + } + } + } + + num_resolutions = mNumSupportedResolutions; + return mSupportedResolutions; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordWindow *to) +{ + S32 client_height; + Rect client_rect; + + if(mFullscreen) + { + // In the fullscreen case, the "window" is the entire screen. + client_rect.left = 0; + client_rect.top = 0; + client_rect.right = mFullscreenWidth; + client_rect.bottom = mFullscreenHeight; + } + else if (!mWindow || + (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordGL* to) +{ + S32 client_height; + Rect client_rect; + + if(mFullscreen) + { + // In the fullscreen case, the "window" is the entire screen. + client_rect.left = 0; + client_rect.top = 0; + client_rect.right = mFullscreenWidth; + client_rect.bottom = mFullscreenHeight; + } + else if (!mWindow || + (GetWindowBounds(mWindow, kWindowContentRgn, &client_rect) != noErr) || + NULL == to) + { + return FALSE; + } + + to->mX = from.mX; + client_height = client_rect.bottom - client_rect.top; + to->mY = client_height - from.mY - 1; + + return TRUE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordWindow* to) +{ + if(mFullscreen) + { + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return TRUE; + } + else if(mWindow) + { + GrafPtr save; + Point mouse_point; + + mouse_point.h = from.mX; + mouse_point.v = from.mY; + + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + ::GlobalToLocal(&mouse_point); + + to->mX = mouse_point.h; + to->mY = mouse_point.v; + + ::SetPort(save); + + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordWindow from, LLCoordScreen *to) +{ + if(mFullscreen) + { + // In the fullscreen case, window and screen coordinates are the same. + to->mX = from.mX; + to->mY = from.mY; + return TRUE; + } + else if(mWindow) + { + GrafPtr save; + Point mouse_point; + + mouse_point.h = from.mX; + mouse_point.v = from.mY; + ::GetPort(&save); + ::SetPort(GetWindowPort(mWindow)); + fixOrigin(); + + LocalToGlobal(&mouse_point); + + to->mX = mouse_point.h; + to->mY = mouse_point.v; + + ::SetPort(save); + + return TRUE; + } + + return FALSE; +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordScreen from, LLCoordGL *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + +BOOL LLWindowMacOSX::convertCoords(LLCoordGL from, LLCoordScreen *to) +{ + LLCoordWindow window_coord; + + return(convertCoords(from, &window_coord) && convertCoords(window_coord, to)); +} + + + + +void LLWindowMacOSX::setupFailure(const char* text, const char* caption, U32 type) +{ + destroyContext(); + + OSMessageBox(text, caption, type); +} + +pascal OSStatus LLWindowMacOSX::staticEventHandler(EventHandlerCallRef myHandler, EventRef event, void* userData) +{ + LLWindowMacOSX *self = (LLWindowMacOSX*)userData; + + return(self->eventHandler(myHandler, event)); +} + +OSStatus LLWindowMacOSX::eventHandler (EventHandlerCallRef myHandler, EventRef event) +{ + OSStatus result = eventNotHandledErr; + UInt32 evtClass = GetEventClass (event); + UInt32 evtKind = GetEventKind (event); + + // Always handle command events, even in hands-off mode. + if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) + { + HICommand command; + GetEventParameter (event, kEventParamDirectObject, typeHICommand, NULL, sizeof(command), NULL, &command); + + switch(command.commandID) + { + case kHICommandQuit: + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + result = noErr; + break; + + default: + // MBW -- XXX -- Should we handle other events here? + break; + } + } + + if(mHandsOffEvents) + { + return(result); + } + + switch (evtClass) + { + case kEventClassTextInput: + { + switch (evtKind) + { + case kEventTextInputUnicodeForKeyEvent: + { + UInt32 modifiers = 0; + + // First, process the raw event. + { + EventRef rawEvent; + + // Get the original event and extract the modifier keys, so we can ignore command-key events. + if (GetEventParameter(event, kEventParamTextInputSendKeyboardEvent, typeEventRef, NULL, sizeof(rawEvent), NULL, &rawEvent) == noErr) + { + // Grab the modifiers for later use in this function... + GetEventParameter (rawEvent, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); + + // and call this function recursively to handle the raw key event. + eventHandler (myHandler, rawEvent); + } + } + + OSStatus err = noErr; + EventParamType actualType = typeUnicodeText; + UInt32 actualSize = 0; + size_t actualCount = 0; + U16 *buffer = NULL; + + // Get the size of the unicode data + err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, 0, &actualSize, NULL); + if(err == noErr) + { + // allocate a buffer and get the actual data. + actualCount = actualSize / sizeof(U16); + buffer = new U16[actualCount]; + err = GetEventParameter (event, kEventParamTextInputSendText, typeUnicodeText, &actualType, actualSize, &actualSize, buffer); + } + + if(err == noErr) + { + if(modifiers & (cmdKey | controlKey)) + { + // This was a menu key equivalent. Ignore it. + } + else + { + MASK mask = 0; + if(modifiers & shiftKey) { mask |= MASK_SHIFT; } + if(modifiers & (cmdKey | controlKey)) { mask |= MASK_CONTROL; } + if(modifiers & optionKey) { mask |= MASK_ALT; } + + llassert( actualType == typeUnicodeText ); + + // The result is a UTF16 buffer. Pass the characters in turn to handleUnicodeChar. + + // Convert to UTF32 and go character-by-character. + llutf16string utf16(buffer, actualCount); + LLWString utf32 = utf16str_to_wstring(utf16); + LLWString::iterator iter; + + for(iter = utf32.begin(); iter != utf32.end(); iter++) + { + mCallbacks->handleUnicodeChar(*iter, mask); + } + } + } + + if(buffer != NULL) + { + delete[] buffer; + } + + result = err; + } + break; + } + } + break; + + case kEventClassKeyboard: + { + UInt32 keyCode = 0; + char charCode = 0; + UInt32 modifiers = 0; + + // Some of these may fail for some event types. That's fine. + GetEventParameter (event, kEventParamKeyCode, typeUInt32, NULL, sizeof(UInt32), NULL, &keyCode); + GetEventParameter (event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(UInt32), NULL, &modifiers); + + // printf("key event, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", keyCode, charCode, (char)charCode, modifiers); + // fflush(stdout); + + switch (evtKind) + { + case kEventRawKeyDown: + case kEventRawKeyRepeat: + if (gDebugWindowProc) + { + printf("key down, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", + (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers); + fflush(stdout); + } + gKeyboard->handleKeyDown(keyCode, modifiers); + result = eventNotHandledErr; + break; + + case kEventRawKeyUp: + if (gDebugWindowProc) + { + printf("key up, key code = 0x%08x, char code = 0x%02x (%c), modifiers = 0x%08x\n", + (unsigned int)keyCode, charCode, (char)charCode, (unsigned int)modifiers); + fflush(stdout); + } + gKeyboard->handleKeyUp(keyCode, modifiers); + result = eventNotHandledErr; + break; + + case kEventRawKeyModifiersChanged: + // The keyboard input system wants key up/down events for modifier keys. + // Mac OS doesn't supply these directly, but can supply events when the collective modifier state changes. + // Use these events to generate up/down events for the modifiers. + + if((modifiers & shiftKey) && !(mLastModifiers & shiftKey)) + { + if (gDebugWindowProc) printf("Shift key down event\n"); + gKeyboard->handleKeyDown(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000)); + } + else if(!(modifiers & shiftKey) && (mLastModifiers & shiftKey)) + { + if (gDebugWindowProc) printf("Shift key up event\n"); + gKeyboard->handleKeyUp(0x38, (modifiers & 0x00FFFFFF) | ((0x38 << 24) & 0xFF000000)); + } + + if((modifiers & alphaLock) && !(mLastModifiers & alphaLock)) + { + if (gDebugWindowProc) printf("Caps lock down event\n"); + gKeyboard->handleKeyDown(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000)); + } + else if(!(modifiers & alphaLock) && (mLastModifiers & alphaLock)) + { + if (gDebugWindowProc) printf("Caps lock up event\n"); + gKeyboard->handleKeyUp(0x39, (modifiers & 0x00FFFFFF) | ((0x39 << 24) & 0xFF000000)); + } + + if((modifiers & controlKey) && !(mLastModifiers & controlKey)) + { + if (gDebugWindowProc) printf("Control key down event\n"); + gKeyboard->handleKeyDown(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000)); + } + else if(!(modifiers & controlKey) && (mLastModifiers & controlKey)) + { + if (gDebugWindowProc) printf("Control key up event\n"); + gKeyboard->handleKeyUp(0x3b, (modifiers & 0x00FFFFFF) | ((0x3b << 24) & 0xFF000000)); + } + + if((modifiers & optionKey) && !(mLastModifiers & optionKey)) + { + if (gDebugWindowProc) printf("Option key down event\n"); + gKeyboard->handleKeyDown(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000)); + } + else if(!(modifiers & optionKey) && (mLastModifiers & optionKey)) + { + if (gDebugWindowProc) printf("Option key up event\n"); + gKeyboard->handleKeyUp(0x3a, (modifiers & 0x00FFFFFF) | ((0x3a << 24) & 0xFF000000)); + } + + // When the state of the 'Fn' key (the one that changes some of the mappings on a powerbook/macbook keyboard + // to an embedded keypad) changes, it may subsequently cause a key up event to be lost, which may lead to + // a movement key getting "stuck" down. This is bad. + // This is an OS bug -- even the GetKeys() API doesn't tell you the key has been released. + // This workaround causes all held-down keys to be reset whenever the state of the Fn key changes. This isn't + // exactly what we want, but it does avoid the case where you get stuck running forward. + if((modifiers & kEventKeyModifierFnMask) != (mLastModifiers & kEventKeyModifierFnMask)) + { + if (gDebugWindowProc) printf("Fn key state change event\n"); + gKeyboard->resetKeys(); + } + + if (gDebugWindowProc) fflush(stdout); + + mLastModifiers = modifiers; + result = eventNotHandledErr; + break; + } + } + break; + + case kEventClassMouse: + { + result = CallNextEventHandler(myHandler, event); + if (eventNotHandledErr == result) + { // only handle events not already handled (prevents wierd resize interaction) + EventMouseButton button = kEventMouseButtonPrimary; + HIPoint location = {0.0f, 0.0f}; + UInt32 modifiers = 0; + UInt32 clickCount = 1; + long wheelDelta = 0; + LLCoordScreen inCoords; + LLCoordGL outCoords; + MASK mask = 0; + + GetEventParameter(event, kEventParamMouseButton, typeMouseButton, NULL, sizeof(button), NULL, &button); + GetEventParameter(event, kEventParamMouseLocation, typeHIPoint, NULL, sizeof(location), NULL, &location); + GetEventParameter(event, kEventParamKeyModifiers, typeUInt32, NULL, sizeof(modifiers), NULL, &modifiers); + GetEventParameter(event, kEventParamMouseWheelDelta, typeLongInteger, NULL, sizeof(wheelDelta), NULL, &wheelDelta); + GetEventParameter(event, kEventParamClickCount, typeUInt32, NULL, sizeof(clickCount), NULL, &clickCount); + + inCoords.mX = llround(location.x); + inCoords.mY = llround(location.y); + + if(modifiers & shiftKey) { mask |= MASK_SHIFT; } + if(modifiers & controlKey) { mask |= MASK_CONTROL; } + if(modifiers & optionKey) { mask |= MASK_ALT; } + + if(mCursorDecoupled) + { + CGMouseDelta x, y; + + // If the cursor's decoupled, we need to read the latest movement delta as well. + CGGetLastMouseDelta( &x, &y ); + mCursorLastEventDeltaX = x; + mCursorLastEventDeltaY = y; + + if(mCursorIgnoreNextDelta) + { + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + mCursorIgnoreNextDelta = FALSE; + } + } + else + { + mCursorLastEventDeltaX = 0; + mCursorLastEventDeltaY = 0; + } + + inCoords.mX += mCursorLastEventDeltaX; + inCoords.mY += mCursorLastEventDeltaY; + + convertCoords(inCoords, &outCoords); + + // printf("coords in: %d, %d; coords out: %d, %d\n", inCoords.mX, inCoords.mY, outCoords.mX, outCoords.mY); + // fflush(stdout); + + + switch (evtKind) + { + case kEventMouseDown: + switch(button) + { + case kEventMouseButtonPrimary: + if(modifiers & cmdKey) + { + // Simulate a right click + mSimulatedRightClick = true; + mCallbacks->handleRightMouseDown(this, outCoords, mask); + } + else if(clickCount == 2) + { + // Windows double-click events replace the second mousedown event in a double-click. + mCallbacks->handleDoubleClick(this, outCoords, mask); + } + else + { + mCallbacks->handleMouseDown(this, outCoords, mask); + } + break; + case kEventMouseButtonSecondary: + mCallbacks->handleRightMouseDown(this, outCoords, mask); + break; + } + result = noErr; + break; + case kEventMouseUp: + + switch(button) + { + case kEventMouseButtonPrimary: + if(mSimulatedRightClick) + { + // End of simulated right click + mSimulatedRightClick = false; + mCallbacks->handleRightMouseUp(this, outCoords, mask); + } + else + { + mCallbacks->handleMouseUp(this, outCoords, mask); + } + break; + case kEventMouseButtonSecondary: + mCallbacks->handleRightMouseUp(this, outCoords, mask); + break; + } + result = noErr; + break; + + case kEventMouseWheelMoved: + { + static S32 z_delta = 0; + + z_delta += wheelDelta; + + if (z_delta <= -WHEEL_DELTA || WHEEL_DELTA <= z_delta) + { + mCallbacks->handleScrollWheel(this, -z_delta / WHEEL_DELTA); + z_delta = 0; + } + } + result = noErr; + break; + + case kEventMouseDragged: + case kEventMouseMoved: + mCallbacks->handleMouseMove(this, outCoords, mask); + result = noErr; + break; + + } + } + } + break; + + case kEventClassWindow: + switch(evtKind) + { + case kEventWindowBoundsChanging: + { + Rect currentBounds; + Rect previousBounds; + + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, ¤tBounds); + GetEventParameter(event, kEventParamPreviousBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &previousBounds); + + // This is where we would constrain move/resize to a particular screen + if(0) + { + SetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), ¤tBounds); + } + } + break; + + case kEventWindowBoundsChanged: + { + Rect newBounds; + + GetEventParameter(event, kEventParamCurrentBounds, typeQDRectangle, NULL, sizeof(Rect), NULL, &newBounds); + aglUpdateContext(mContext); + mCallbacks->handleResize(this, newBounds.right - newBounds.left, newBounds.bottom - newBounds.top); + + + } + break; + + case kEventWindowClose: + if(mCallbacks->handleCloseRequest(this)) + { + // Get the app to initiate cleanup. + mCallbacks->handleQuit(this); + // The app is responsible for calling destroyWindow when done with GL + } + result = noErr; + break; + + case kEventWindowHidden: + // llinfos << "LLWindowMacOSX: Deactivating on hide" << llendl; + mMinimized = TRUE; + mCallbacks->handleActivate(this, false); + // result = noErr; + break; + + case kEventWindowShown: + // llinfos << "LLWindowMacOSX: Activating on show" << llendl; + mMinimized = FALSE; + mCallbacks->handleActivate(this, true); + // result = noErr; + break; + + case kEventWindowCollapsed: + // llinfos << "LLWindowMacOSX: Deactivating on collapse" << llendl; + mMinimized = TRUE; + mCallbacks->handleActivate(this, false); + // result = noErr; + break; + + case kEventWindowExpanded: + // llinfos << "LLWindowMacOSX: Activating on expand" << llendl; + mMinimized = FALSE; + mCallbacks->handleActivate(this, true); + // result = noErr; + break; + + case kEventWindowGetClickActivation: + // BringToFront(mWindow); + // result = noErr; + break; + } + break; + } + return result; +} + +const char* cursorIDToName(int id) +{ + switch (id) + { + case UI_CURSOR_ARROW: return "UI_CURSOR_ARROW"; + case UI_CURSOR_WAIT: return "UI_CURSOR_WAIT"; + case UI_CURSOR_HAND: return "UI_CURSOR_HAND"; + case UI_CURSOR_IBEAM: return "UI_CURSOR_IBEAM"; + case UI_CURSOR_CROSS: return "UI_CURSOR_CROSS"; + case UI_CURSOR_SIZENWSE: return "UI_CURSOR_SIZENWSE"; + case UI_CURSOR_SIZENESW: return "UI_CURSOR_SIZENESW"; + case UI_CURSOR_SIZEWE: return "UI_CURSOR_SIZEWE"; + case UI_CURSOR_SIZENS: return "UI_CURSOR_SIZENS"; + case UI_CURSOR_NO: return "UI_CURSOR_NO"; + case UI_CURSOR_WORKING: return "UI_CURSOR_WORKING"; + case UI_CURSOR_TOOLGRAB: return "UI_CURSOR_TOOLGRAB"; + case UI_CURSOR_TOOLLAND: return "UI_CURSOR_TOOLLAND"; + case UI_CURSOR_TOOLFOCUS: return "UI_CURSOR_TOOLFOCUS"; + case UI_CURSOR_TOOLCREATE: return "UI_CURSOR_TOOLCREATE"; + case UI_CURSOR_ARROWDRAG: return "UI_CURSOR_ARROWDRAG"; + case UI_CURSOR_ARROWCOPY: return "UI_CURSOR_ARROWCOPY"; + case UI_CURSOR_ARROWDRAGMULTI: return "UI_CURSOR_ARROWDRAGMULTI"; + case UI_CURSOR_ARROWCOPYMULTI: return "UI_CURSOR_ARROWCOPYMULTI"; + case UI_CURSOR_NOLOCKED: return "UI_CURSOR_NOLOCKED"; + case UI_CURSOR_ARROWLOCKED: return "UI_CURSOR_ARROWLOCKED"; + case UI_CURSOR_GRABLOCKED: return "UI_CURSOR_GRABLOCKED"; + case UI_CURSOR_TOOLTRANSLATE: return "UI_CURSOR_TOOLTRANSLATE"; + case UI_CURSOR_TOOLROTATE: return "UI_CURSOR_TOOLROTATE"; + case UI_CURSOR_TOOLSCALE: return "UI_CURSOR_TOOLSCALE"; + case UI_CURSOR_TOOLCAMERA: return "UI_CURSOR_TOOLCAMERA"; + case UI_CURSOR_TOOLPAN: return "UI_CURSOR_TOOLPAN"; + case UI_CURSOR_TOOLZOOMIN: return "UI_CURSOR_TOOLZOOMIN"; + case UI_CURSOR_TOOLPICKOBJECT3: return "UI_CURSOR_TOOLPICKOBJECT3"; + case UI_CURSOR_TOOLSIT: return "UI_CURSOR_TOOLSIT"; + case UI_CURSOR_TOOLBUY: return "UI_CURSOR_TOOLBUY"; + case UI_CURSOR_TOOLPAY: return "UI_CURSOR_TOOLPAY"; + case UI_CURSOR_TOOLOPEN: return "UI_CURSOR_TOOLOPEN"; + case UI_CURSOR_PIPETTE: return "UI_CURSOR_PIPETTE"; + } + + llerrs << "cursorIDToName: unknown cursor id" << id << llendl; + + return "UI_CURSOR_ARROW"; +} + +static CursorRef gCursors[UI_CURSOR_COUNT]; + + +static void initPixmapCursor(int cursorid, int hotspotX, int hotspotY) +{ + // cursors are in <Application Bundle>/Contents/Resources/cursors_mac/UI_CURSOR_FOO.tif + std::string fullpath = gDirUtilp->getAppRODataDir(); + fullpath += gDirUtilp->getDirDelimiter(); + fullpath += "cursors_mac"; + fullpath += gDirUtilp->getDirDelimiter(); + fullpath += cursorIDToName(cursorid); + fullpath += ".tif"; + + gCursors[cursorid] = createImageCursor(fullpath.c_str(), hotspotX, hotspotY); +} + +void LLWindowMacOSX::setCursor(ECursorType cursor) +{ + OSStatus result = noErr; + + if (cursor == UI_CURSOR_ARROW + && mBusyCount > 0) + { + cursor = UI_CURSOR_WORKING; + } + + if(mCurrentCursor == cursor) + return; + + // RN: replace multi-drag cursors with single versions + if (cursor == UI_CURSOR_ARROWDRAGMULTI) + { + cursor = UI_CURSOR_ARROWDRAG; + } + else if (cursor == UI_CURSOR_ARROWCOPYMULTI) + { + cursor = UI_CURSOR_ARROWCOPY; + } + + switch(cursor) + { + default: + case UI_CURSOR_ARROW: + InitCursor(); + if(mCursorHidden) + { + // Since InitCursor resets the hide level, correct for it here. + ::HideCursor(); + } + break; + + // MBW -- XXX -- Some of the standard Windows cursors have no standard Mac equivalents. + // Find out what they look like and replicate them. + + // These are essentially correct + case UI_CURSOR_WAIT: SetThemeCursor(kThemeWatchCursor); break; + case UI_CURSOR_IBEAM: SetThemeCursor(kThemeIBeamCursor); break; + case UI_CURSOR_CROSS: SetThemeCursor(kThemeCrossCursor); break; + case UI_CURSOR_HAND: SetThemeCursor(kThemePointingHandCursor); break; + // case UI_CURSOR_NO: SetThemeCursor(kThemeNotAllowedCursor); break; + case UI_CURSOR_ARROWCOPY: SetThemeCursor(kThemeCopyArrowCursor); break; + + // Double-check these + case UI_CURSOR_NO: + case UI_CURSOR_SIZEWE: + case UI_CURSOR_SIZENS: + case UI_CURSOR_SIZENWSE: + case UI_CURSOR_SIZENESW: + case UI_CURSOR_WORKING: + case UI_CURSOR_TOOLGRAB: + case UI_CURSOR_TOOLLAND: + case UI_CURSOR_TOOLFOCUS: + case UI_CURSOR_TOOLCREATE: + case UI_CURSOR_ARROWDRAG: + case UI_CURSOR_NOLOCKED: + case UI_CURSOR_ARROWLOCKED: + case UI_CURSOR_GRABLOCKED: + case UI_CURSOR_TOOLTRANSLATE: + case UI_CURSOR_TOOLROTATE: + case UI_CURSOR_TOOLSCALE: + case UI_CURSOR_TOOLCAMERA: + case UI_CURSOR_TOOLPAN: + case UI_CURSOR_TOOLZOOMIN: + case UI_CURSOR_TOOLPICKOBJECT3: + case UI_CURSOR_TOOLSIT: + case UI_CURSOR_TOOLBUY: + case UI_CURSOR_TOOLPAY: + case UI_CURSOR_TOOLOPEN: + result = setImageCursor(gCursors[cursor]); + break; + + } + + if(result != noErr) + { + InitCursor(); + } + + mCurrentCursor = cursor; +} + +ECursorType LLWindowMacOSX::getCursor() +{ + return mCurrentCursor; +} + +void LLWindowMacOSX::initCursors() +{ + initPixmapCursor(UI_CURSOR_NO, 8, 8); + initPixmapCursor(UI_CURSOR_WORKING, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLGRAB, 2, 14); + initPixmapCursor(UI_CURSOR_TOOLLAND, 13, 8); + initPixmapCursor(UI_CURSOR_TOOLFOCUS, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLCREATE, 7, 7); + initPixmapCursor(UI_CURSOR_ARROWDRAG, 1, 1); + initPixmapCursor(UI_CURSOR_ARROWCOPY, 1, 1); + initPixmapCursor(UI_CURSOR_NOLOCKED, 8, 8); + initPixmapCursor(UI_CURSOR_ARROWLOCKED, 1, 1); + initPixmapCursor(UI_CURSOR_GRABLOCKED, 2, 14); + initPixmapCursor(UI_CURSOR_TOOLTRANSLATE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLROTATE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLSCALE, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLCAMERA, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLPAN, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLZOOMIN, 7, 6); + initPixmapCursor(UI_CURSOR_TOOLPICKOBJECT3, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLSIT, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLBUY, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLPAY, 1, 1); + initPixmapCursor(UI_CURSOR_TOOLOPEN, 1, 1); + + initPixmapCursor(UI_CURSOR_SIZENWSE, 10, 10); + initPixmapCursor(UI_CURSOR_SIZENESW, 10, 10); + initPixmapCursor(UI_CURSOR_SIZEWE, 10, 10); + initPixmapCursor(UI_CURSOR_SIZENS, 10, 10); + +} + +void LLWindowMacOSX::captureMouse() +{ + // By registering a global CarbonEvent handler for mouse move events, we ensure that + // mouse events are always processed. Thus, capture and release are unnecessary. +} + +void LLWindowMacOSX::releaseMouse() +{ + // By registering a global CarbonEvent handler for mouse move events, we ensure that + // mouse events are always processed. Thus, capture and release are unnecessary. +} + +void LLWindowMacOSX::hideCursor() +{ + if(!mCursorHidden) + { + // llinfos << "hideCursor: hiding" << llendl; + mCursorHidden = TRUE; + mHideCursorPermanent = TRUE; + ::HideCursor(); + } + else + { + // llinfos << "hideCursor: already hidden" << llendl; + } + + adjustCursorDecouple(); +} + +void LLWindowMacOSX::showCursor() +{ + if(mCursorHidden) + { + // llinfos << "showCursor: showing" << llendl; + mCursorHidden = FALSE; + mHideCursorPermanent = FALSE; + ::ShowCursor(); + } + else + { + // llinfos << "showCursor: already visible" << llendl; + } + + adjustCursorDecouple(); +} + +void LLWindowMacOSX::showCursorFromMouseMove() +{ + if (!mHideCursorPermanent) + { + showCursor(); + } +} + +void LLWindowMacOSX::hideCursorUntilMouseMove() +{ + if (!mHideCursorPermanent) + { + hideCursor(); + mHideCursorPermanent = FALSE; + } +} + + + +// +// LLSplashScreenMacOSX +// +LLSplashScreenMacOSX::LLSplashScreenMacOSX() +{ + mWindow = NULL; +} + +LLSplashScreenMacOSX::~LLSplashScreenMacOSX() +{ +} + +void LLSplashScreenMacOSX::showImpl() +{ + // This code _could_ be used to display a spash screen... +#if 0 + IBNibRef nib = NULL; + OSStatus err; + + err = CreateNibReference(CFSTR("SecondLife"), &nib); + + if(err == noErr) + { + CreateWindowFromNib(nib, CFSTR("Splash Screen"), &mWindow); + + DisposeNibReference(nib); + } + + if(mWindow != NULL) + { + ShowWindow(mWindow); + } +#endif +} + +void LLSplashScreenMacOSX::updateImpl(const char* mesg) +{ + if(mWindow != NULL) + { + CFStringRef string = NULL; + + if(mesg != NULL) + { + string = CFStringCreateWithCString(NULL, mesg, kCFStringEncodingUTF8); + } + else + { + string = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + if(string != NULL) + { + ControlRef progressText = NULL; + ControlID id; + OSStatus err; + + id.signature = 'what'; + id.id = 0; + + err = GetControlByID(mWindow, &id, &progressText); + if(err == noErr) + { + err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&string); + Draw1Control(progressText); + } + + CFRelease(string); + } + } +} + + +void LLSplashScreenMacOSX::hideImpl() +{ + if(mWindow != NULL) + { + DisposeWindow(mWindow); + mWindow = NULL; + } +} + + + +S32 OSMessageBoxMacOSX(const char* text, const char* caption, U32 type) +{ + S32 result = OSBTN_CANCEL; + SInt16 retval_mac = 1; + AlertStdCFStringAlertParamRec params; + CFStringRef errorString = NULL; + CFStringRef explanationString = NULL; + DialogRef alert = NULL; + AlertType alertType = kAlertCautionAlert; + OSStatus err; + + if(text != NULL) + { + explanationString = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); + } + else + { + explanationString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + if(caption != NULL) + { + errorString = CFStringCreateWithCString(NULL, caption, kCFStringEncodingUTF8); + } + else + { + errorString = CFStringCreateWithCString(NULL, "", kCFStringEncodingUTF8); + } + + params.version = kStdCFStringAlertVersionOne; + params.movable = false; + params.helpButton = false; + params.defaultText = (CFStringRef)kAlertDefaultOKText; + params.cancelText = 0; + params.otherText = 0; + params.defaultButton = 1; + params.cancelButton = 0; + params.position = kWindowDefaultPosition; + params.flags = 0; + + switch(type) + { + case OSMB_OK: + default: + break; + case OSMB_OKCANCEL: + params.cancelText = (CFStringRef)kAlertDefaultCancelText; + params.cancelButton = 2; + break; + case OSMB_YESNO: + alertType = kAlertNoteAlert; + params.defaultText = CFSTR("Yes"); + params.cancelText = CFSTR("No"); + params.cancelButton = 2; + break; + } + + if(gWindowImplementation != NULL) + gWindowImplementation->beforeDialog(); + + err = CreateStandardAlert( + alertType, + errorString, + explanationString, + ¶ms, + &alert); + + if(err == noErr) + { + err = RunStandardAlert( + alert, + NULL, + &retval_mac); + } + + if(gWindowImplementation != NULL) + gWindowImplementation->afterDialog(); + + switch(type) + { + case OSMB_OK: + case OSMB_OKCANCEL: + default: + if(retval_mac == 1) + result = OSBTN_OK; + else + result = OSBTN_CANCEL; + break; + case OSMB_YESNO: + if(retval_mac == 1) + result = OSBTN_YES; + else + result = OSBTN_NO; + break; + } + + if(errorString != NULL) + { + CFRelease(errorString); + } + + if(explanationString != NULL) + { + CFRelease(explanationString); + } + + return result; +} + +// Open a URL with the user's default web browser. +// Must begin with protocol identifier. +void spawn_web_browser(const char* escaped_url) +{ + bool found = false; + S32 i; + for (i = 0; i < gURLProtocolWhitelistCount; i++) + { + S32 len = strlen(gURLProtocolWhitelist[i]); + if (!strncmp(escaped_url, gURLProtocolWhitelist[i], len) + && escaped_url[len] == ':') + { + found = true; + break; + } + } + + if (!found) + { + llwarns << "spawn_web_browser() called for url with protocol not on whitelist: " << escaped_url << llendl; + return; + } + + OSStatus result = noErr; + CFURLRef urlRef = NULL; + + llinfos << "Opening URL " << escaped_url << llendl; + + CFStringRef stringRef = CFStringCreateWithCString(NULL, escaped_url, kCFStringEncodingUTF8); + if (stringRef) + { + // This will succeed if the string is a full URL, including the http:// + // Note that URLs specified this way need to be properly percent-escaped. + urlRef = CFURLCreateWithString(NULL, stringRef, NULL); + + // Don't use CRURLCreateWithFileSystemPath -- only want valid URLs + + CFRelease(stringRef); + } + + if (urlRef) + { + result = LSOpenCFURLRef(urlRef, NULL); + + if (result != noErr) + { + llinfos << "Error " << result << " on open." << llendl; + } + + CFRelease(urlRef); + } + else + { + llinfos << "Error: couldn't create URL." << llendl; + } +} + +void shell_open( const char* file_path ) +{ + OSStatus result = noErr; + + llinfos << "Opening " << file_path << llendl; + CFURLRef urlRef = NULL; + + CFStringRef stringRef = CFStringCreateWithCString(NULL, file_path, kCFStringEncodingUTF8); + if (stringRef) + { + // This will succeed if the string is a full URL, including the http:// + // Note that URLs specified this way need to be properly percent-escaped. + urlRef = CFURLCreateWithString(NULL, stringRef, NULL); + + if(urlRef == NULL) + { + // This will succeed if the string is a full or partial posix path. + // This will work even if the path contains characters that would need to be percent-escaped + // in the URL (such as spaces). + urlRef = CFURLCreateWithFileSystemPath(NULL, stringRef, kCFURLPOSIXPathStyle, false); + } + + CFRelease(stringRef); + } + + if (urlRef) + { + result = LSOpenCFURLRef(urlRef, NULL); + + if (result != noErr) + { + llinfos << "Error " << result << " on open." << llendl; + } + CFRelease(urlRef); + } + else + { + llinfos << "Error: couldn't create URL." << llendl; + } +} + +BOOL LLWindowMacOSX::dialog_color_picker ( F32 *r, F32 *g, F32 *b) +{ + BOOL retval = FALSE; + OSErr error = noErr; + NColorPickerInfo info; + + memset(&info, 0, sizeof(info)); + info.theColor.color.rgb.red = (UInt16)(*r * 65535.f); + info.theColor.color.rgb.green = (UInt16)(*g * 65535.f); + info.theColor.color.rgb.blue = (UInt16)(*b * 65535.f); + info.placeWhere = kCenterOnMainScreen; + + if(gWindowImplementation != NULL) + gWindowImplementation->beforeDialog(); + + error = NPickColor(&info); + + if(gWindowImplementation != NULL) + gWindowImplementation->afterDialog(); + + if (error == noErr) + { + retval = info.newColorChosen; + if (info.newColorChosen) + { + *r = ((float) info.theColor.color.rgb.red) / 65535.0; + *g = ((float) info.theColor.color.rgb.green) / 65535.0; + *b = ((float) info.theColor.color.rgb.blue) / 65535.0; + } + } + return (retval); +} + +static WindowRef dummywindowref = NULL; + +void *LLWindowMacOSX::getPlatformWindow() +{ + if(mWindow != NULL) + return (void*)mWindow; + + // If we're in fullscreen mode, there's no window pointer available. + // Since Mozilla needs one to function, create a dummy window here. + // Note that we will never destroy it, but since only one will be created per run of the application, that's okay. + + if(dummywindowref == NULL) + { + Rect window_rect = {100, 100, 200, 200}; + + dummywindowref = NewCWindow( + NULL, + &window_rect, + "\p", + false, // Create the window invisible. + zoomDocProc, // Window with a grow box and a zoom box + kLastWindowOfClass, // create it behind other windows + false, // no close box + 0); + } + + return (void*)dummywindowref; +} + +void LLWindowMacOSX::stopDockTileBounce() +{ + NMRemove(&mBounceRec); + mBounceTimer.stop(); +} + +// get a double value from a dictionary +static double getDictDouble (CFDictionaryRef refDict, CFStringRef key) +{ + double double_value; + CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key); + if (!number_value) // if can't get a number for the dictionary + return -1; // fail + if (!CFNumberGetValue(number_value, kCFNumberDoubleType, &double_value)) // or if cant convert it + return -1; // fail + return double_value; // otherwise return the long value +} + +// get a long value from a dictionary +static long getDictLong (CFDictionaryRef refDict, CFStringRef key) +{ + long int_value; + CFNumberRef number_value = (CFNumberRef) CFDictionaryGetValue(refDict, key); + if (!number_value) // if can't get a number for the dictionary + return -1; // fail + if (!CFNumberGetValue(number_value, kCFNumberLongType, &int_value)) // or if cant convert it + return -1; // fail + return int_value; // otherwise return the long value +} + +#endif // LL_DARWIN |