/* ============================================================================== This file is part of the JUCE library - "Jules' Utility Class Extensions" Copyright 2004-7 by Raw Material Software ltd. ------------------------------------------------------------------------------ JUCE can be redistributed and/or modified under the terms of the GNU General Public License, as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with JUCE; if not, visit www.gnu.org/licenses or write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------------ If you'd like to release a closed-source product which uses JUCE, commercial licenses are also available: visit www.rawmaterialsoftware.com/juce for more information. ============================================================================== */ #include "../../../src/juce_core/basics/juce_StandardHeader.h" #include #include #include #include #include #include #if JUCE_OPENGL #include #endif BEGIN_JUCE_NAMESPACE #include "../../../src/juce_appframework/events/juce_Timer.h" #include "../../../src/juce_appframework/application/juce_DeletedAtShutdown.h" #include "../../../src/juce_appframework/events/juce_AsyncUpdater.h" #include "../../../src/juce_appframework/events/juce_MessageManager.h" #include "../../../src/juce_core/basics/juce_Singleton.h" #include "../../../src/juce_core/basics/juce_Random.h" #include "../../../src/juce_core/threads/juce_Process.h" #include "../../../src/juce_appframework/application/juce_SystemClipboard.h" #include "../../../src/juce_appframework/gui/components/keyboard/juce_KeyPress.h" #include "../../../src/juce_appframework/gui/components/windows/juce_AlertWindow.h" #include "../../../src/juce_appframework/gui/graphics/geometry/juce_RectangleList.h" #include "../../../src/juce_appframework/gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" #include "../../../src/juce_appframework/gui/components/juce_Desktop.h" #include "../../../src/juce_appframework/gui/components/menus/juce_MenuBarModel.h" #include "../../../src/juce_core/misc/juce_PlatformUtilities.h" #include "../../../src/juce_appframework/application/juce_Application.h" #include "../../../src/juce_appframework/gui/components/special/juce_OpenGLComponent.h" #include "../../../src/juce_appframework/gui/components/mouse/juce_DragAndDropContainer.h" #include "../../../src/juce_appframework/gui/components/keyboard/juce_KeyPressMappingSet.h" #include "../../../src/juce_appframework/gui/graphics/imaging/juce_ImageFileFormat.h" #undef Point const WindowRegionCode windowRegionToUse = kWindowContentRgn; static HIObjectClassRef viewClassRef = 0; static CFStringRef juceHiViewClassNameCFString = 0; static ComponentPeer* juce_currentMouseTrackingPeer = 0; //============================================================================== static VoidArray keysCurrentlyDown; bool KeyPress::isKeyCurrentlyDown (const int keyCode) throw() { if (keysCurrentlyDown.contains ((void*) keyCode)) return true; if (keyCode >= 'A' && keyCode <= 'Z' && keysCurrentlyDown.contains ((void*) (int) CharacterFunctions::toLowerCase ((tchar) keyCode))) return true; if (keyCode >= 'a' && keyCode <= 'z' && keysCurrentlyDown.contains ((void*) (int) CharacterFunctions::toUpperCase ((tchar) keyCode))) return true; return false; } //============================================================================== static VoidArray minimisedWindows; static void setWindowMinimised (WindowRef ref, const bool isMinimised) { if (isMinimised != minimisedWindows.contains (ref)) CollapseWindow (ref, isMinimised); } void juce_maximiseAllMinimisedWindows() { const VoidArray minWin (minimisedWindows); for (int i = minWin.size(); --i >= 0;) setWindowMinimised ((WindowRef) (minWin[i]), false); } //============================================================================== class HIViewComponentPeer; static HIViewComponentPeer* currentlyFocusedPeer = 0; //============================================================================== static int currentModifiers = 0; static void updateModifiers (EventRef theEvent) { currentModifiers &= ~ (ModifierKeys::shiftModifier | ModifierKeys::ctrlModifier | ModifierKeys::altModifier | ModifierKeys::commandModifier); UInt32 m; if (theEvent != 0) GetEventParameter (theEvent, kEventParamKeyModifiers, typeUInt32, 0, sizeof(m), 0, &m); else m = GetCurrentEventKeyModifiers(); if ((m & (shiftKey | rightShiftKey)) != 0) currentModifiers |= ModifierKeys::shiftModifier; if ((m & (controlKey | rightControlKey)) != 0) currentModifiers |= ModifierKeys::ctrlModifier; if ((m & (optionKey | rightOptionKey)) != 0) currentModifiers |= ModifierKeys::altModifier; if ((m & cmdKey) != 0) currentModifiers |= ModifierKeys::commandModifier; } void ModifierKeys::updateCurrentModifiers() throw() { currentModifierFlags = currentModifiers; } static int64 getEventTime (EventRef event) { const int64 millis = (int64) (1000.0 * (event != 0 ? GetEventTime (event) : GetCurrentEventTime())); static int64 offset = 0; if (offset == 0) offset = Time::currentTimeMillis() - millis; return offset + millis; } //============================================================================== class MacBitmapImage : public Image { public: //============================================================================== CGColorSpaceRef colourspace; CGDataProviderRef provider; //============================================================================== MacBitmapImage (const PixelFormat format_, const int w, const int h, const bool clearImage) : Image (format_, w, h) { jassert (format_ == RGB || format_ == ARGB); pixelStride = (format_ == RGB) ? 3 : 4; lineStride = (w * pixelStride + 3) & ~3; const int imageSize = lineStride * h; if (clearImage) imageData = (uint8*) juce_calloc (imageSize); else imageData = (uint8*) juce_malloc (imageSize); //colourspace = CGColorSpaceCreateWithName (kCGColorSpaceUserRGB); CMProfileRef prof; CMGetSystemProfile (&prof); colourspace = CGColorSpaceCreateWithPlatformColorSpace (prof); provider = CGDataProviderCreateWithData (0, imageData, h * lineStride, 0); } MacBitmapImage::~MacBitmapImage() { CGDataProviderRelease (provider); CGColorSpaceRelease (colourspace); juce_free (imageData); imageData = 0; // to stop the base class freeing this } void blitToContext (CGContextRef context, const float dx, const float dy) { CGImageRef tempImage = CGImageCreate (getWidth(), getHeight(), 8, pixelStride << 3, lineStride, colourspace, #if MACOS_10_3_OR_EARLIER || JUCE_BIG_ENDIAN hasAlphaChannel() ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone, #else hasAlphaChannel() ? kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst : kCGImageAlphaNone, #endif provider, 0, false, kCGRenderingIntentDefault); HIRect r; r.origin.x = dx; r.origin.y = dy; r.size.width = (float) getWidth(); r.size.height = (float) getHeight(); HIViewDrawCGImage (context, &r, tempImage); CGImageRelease (tempImage); } juce_UseDebuggingNewOperator }; //============================================================================== class MouseCheckTimer : private Timer, private DeletedAtShutdown { public: MouseCheckTimer() : lastX (0), lastY (0) { lastPeerUnderMouse = 0; resetMouseMoveChecker(); #if ! MACOS_10_2_OR_EARLIER // Just putting this in here because it's a convenient object that'll get deleted at shutdown CGDisplayRegisterReconfigurationCallback (&displayChangeCallback, 0); #endif } ~MouseCheckTimer() { #if ! MACOS_10_2_OR_EARLIER CGDisplayRemoveReconfigurationCallback (&displayChangeCallback, 0); #endif clearSingletonInstance(); } juce_DeclareSingleton_SingleThreaded_Minimal (MouseCheckTimer) bool hasEverHadAMouseMove; void moved (HIViewComponentPeer* const peer) { if (hasEverHadAMouseMove) startTimer (200); lastPeerUnderMouse = peer; } void resetMouseMoveChecker() { hasEverHadAMouseMove = false; startTimer (1000 / 16); } void timerCallback(); private: HIViewComponentPeer* lastPeerUnderMouse; int lastX, lastY; #if ! MACOS_10_2_OR_EARLIER static void displayChangeCallback (CGDirectDisplayID, CGDisplayChangeSummaryFlags flags, void*) { Desktop::getInstance().refreshMonitorSizes(); } #endif }; juce_ImplementSingleton_SingleThreaded (MouseCheckTimer) //============================================================================== #if JUCE_QUICKTIME extern void OfferMouseClickToQuickTime (WindowRef window, ::Point where, long when, long modifiers, Component* topLevelComp); #endif //============================================================================== class HIViewComponentPeer : public ComponentPeer, private Timer { public: //============================================================================== HIViewComponentPeer (Component* const component, const int windowStyleFlags, HIViewRef viewToAttachTo) : ComponentPeer (component, windowStyleFlags), fullScreen (false), isCompositingWindow (false), windowRef (0), viewRef (0) { repainter = new RepaintManager (this); eventHandlerRef = 0; if (viewToAttachTo != 0) { isSharedWindow = true; } else { isSharedWindow = false; WindowRef newWindow = createNewWindow (windowStyleFlags); GetRootControl (newWindow, (ControlRef*) &viewToAttachTo); jassert (viewToAttachTo != 0); HIViewRef growBox = 0; HIViewFindByID (HIViewGetRoot (newWindow), kHIViewWindowGrowBoxID, &growBox); if (growBox != 0) HIGrowBoxViewSetTransparent (growBox, true); } createNewHIView(); HIViewAddSubview (viewToAttachTo, viewRef); HIViewSetVisible (viewRef, component->isVisible()); setTitle (component->getName()); if (component->isVisible() && ! isSharedWindow) { ShowWindow (windowRef); ActivateWindow (windowRef, component->getWantsKeyboardFocus()); } } ~HIViewComponentPeer() { minimisedWindows.removeValue (windowRef); if (IsValidWindowPtr (windowRef)) { if (! isSharedWindow) { CFRelease (viewRef); viewRef = 0; DisposeWindow (windowRef); } else { if (eventHandlerRef != 0) RemoveEventHandler (eventHandlerRef); CFRelease (viewRef); viewRef = 0; } windowRef = 0; } if (currentlyFocusedPeer == this) currentlyFocusedPeer = 0; delete repainter; } //============================================================================== void* getNativeHandle() const { return windowRef; } void setVisible (bool shouldBeVisible) { HIViewSetVisible (viewRef, shouldBeVisible); if ((! isSharedWindow) && IsValidWindowPtr (windowRef)) { if (shouldBeVisible) ShowWindow (windowRef); else HideWindow (windowRef); resizeViewToFitWindow(); // If nothing else is focused, then grab the focus too if (shouldBeVisible && Component::getCurrentlyFocusedComponent() == 0 && Process::isForegroundProcess()) { component->toFront (true); } } } void setTitle (const String& title) { if ((! isSharedWindow) && IsValidWindowPtr (windowRef)) { CFStringRef t = PlatformUtilities::juceStringToCFString (title); SetWindowTitleWithCFString (windowRef, t); CFRelease (t); } } void setPosition (int x, int y) { if (isSharedWindow) { HIViewPlaceInSuperviewAt (viewRef, x, y); } else if (IsValidWindowPtr (windowRef)) { Rect r; GetWindowBounds (windowRef, windowRegionToUse, &r); r.right += x - r.left; r.bottom += y - r.top; r.left = x; r.top = y; SetWindowBounds (windowRef, windowRegionToUse, &r); } } void setSize (int w, int h) { w = jmax (0, w); h = jmax (0, h); if (w != getComponent()->getWidth() || h != getComponent()->getHeight()) { repainter->repaint (0, 0, w, h); } if (isSharedWindow) { HIRect r; HIViewGetFrame (viewRef, &r); r.size.width = (float) w; r.size.height = (float) h; HIViewSetFrame (viewRef, &r); } else if (IsValidWindowPtr (windowRef)) { Rect r; GetWindowBounds (windowRef, windowRegionToUse, &r); r.right = r.left + w; r.bottom = r.top + h; SetWindowBounds (windowRef, windowRegionToUse, &r); } } void setBounds (int x, int y, int w, int h, const bool isNowFullScreen) { fullScreen = isNowFullScreen; w = jmax (0, w); h = jmax (0, h); if (w != getComponent()->getWidth() || h != getComponent()->getHeight()) { repainter->repaint (0, 0, w, h); } if (isSharedWindow) { HIRect r; r.origin.x = (float) x; r.origin.y = (float) y; r.size.width = (float) w; r.size.height = (float) h; HIViewSetFrame (viewRef, &r); } else if (IsValidWindowPtr (windowRef)) { Rect r; r.left = x; r.top = y; r.right = x + w; r.bottom = y + h; SetWindowBounds (windowRef, windowRegionToUse, &r); } } void getBounds (int& x, int& y, int& w, int& h, const bool global) const { HIRect hiViewPos; HIViewGetFrame (viewRef, &hiViewPos); if (global) { HIViewRef content = 0; HIViewFindByID (HIViewGetRoot (windowRef), kHIViewWindowContentID, &content); HIPoint p = { 0.0f, 0.0f }; HIViewConvertPoint (&p, viewRef, content); x = (int) p.x; y = (int) p.y; if (IsValidWindowPtr (windowRef)) { Rect windowPos; GetWindowBounds (windowRef, kWindowContentRgn, &windowPos); x += windowPos.left; y += windowPos.top; } } else { x = (int) hiViewPos.origin.x; y = (int) hiViewPos.origin.y; } w = (int) hiViewPos.size.width; h = (int) hiViewPos.size.height; } void getBounds (int& x, int& y, int& w, int& h) const { getBounds (x, y, w, h, ! isSharedWindow); } int getScreenX() const { int x, y, w, h; getBounds (x, y, w, h, true); return x; } int getScreenY() const { int x, y, w, h; getBounds (x, y, w, h, true); return y; } void relativePositionToGlobal (int& x, int& y) { int wx, wy, ww, wh; getBounds (wx, wy, ww, wh, true); x += wx; y += wy; } void globalPositionToRelative (int& x, int& y) { int wx, wy, ww, wh; getBounds (wx, wy, ww, wh, true); x -= wx; y -= wy; } void setMinimised (bool shouldBeMinimised) { if (! isSharedWindow) setWindowMinimised (windowRef, shouldBeMinimised); } bool isMinimised() const { return minimisedWindows.contains (windowRef); } void setFullScreen (bool shouldBeFullScreen) { if (! isSharedWindow) { Rectangle r (lastNonFullscreenBounds); setMinimised (false); if (fullScreen != shouldBeFullScreen) { if (shouldBeFullScreen) r = Desktop::getInstance().getMainMonitorArea(); // (can't call the component's setBounds method because that'll reset our fullscreen flag) if (r != getComponent()->getBounds() && ! r.isEmpty()) setBounds (r.getX(), r.getY(), r.getWidth(), r.getHeight(), shouldBeFullScreen); } } } bool isFullScreen() const { return fullScreen; } bool contains (int x, int y, bool trueIfInAChildWindow) const { if (((unsigned int) x) >= (unsigned int) component->getWidth() || ((unsigned int) y) >= (unsigned int) component->getHeight() || ! IsValidWindowPtr (windowRef)) return false; Rect r; GetWindowBounds (windowRef, windowRegionToUse, &r); ::Point p; p.h = r.left + x; p.v = r.top + y; WindowRef ref2 = 0; FindWindow (p, &ref2); if (windowRef != ref2) return false; if (trueIfInAChildWindow) return true; HIPoint p2; p2.x = (float) x; p2.y = (float) y; HIViewRef hit; HIViewGetSubviewHit (viewRef, &p2, true, &hit); return hit == 0 || hit == viewRef; } const BorderSize getFrameSize() const { return BorderSize(); } bool setAlwaysOnTop (bool alwaysOnTop) { // can't do this so return false and let the component create a new window return false; } void toFront (bool makeActiveWindow) { makeActiveWindow = makeActiveWindow && component->isValidComponent() && (component->getWantsKeyboardFocus() || component->isCurrentlyModal()); if (windowRef != FrontWindow() || (makeActiveWindow && ! IsWindowActive (windowRef)) || ! Process::isForegroundProcess()) { if (! Process::isForegroundProcess()) { ProcessSerialNumber psn; GetCurrentProcess (&psn); SetFrontProcessWithOptions (&psn, kSetFrontProcessFrontWindowOnly); } if (IsValidWindowPtr (windowRef)) { if (makeActiveWindow) { SelectWindow (windowRef); SetUserFocusWindow (windowRef); HIViewAdvanceFocus (viewRef, 0); } else { BringToFront (windowRef); } handleBroughtToFront(); } } } void toBehind (ComponentPeer* other) { HIViewComponentPeer* const otherWindow = dynamic_cast (other); if (other != 0 && windowRef != 0 && otherWindow->windowRef != 0) { if (windowRef == otherWindow->windowRef) { HIViewSetZOrder (viewRef, kHIViewZOrderBelow, otherWindow->viewRef); } else { SendBehind (windowRef, otherWindow->windowRef); } } } void setIcon (const Image& /*newIcon*/) { // to do.. } //============================================================================== void viewFocusGain() { const MessageManagerLock messLock; if (currentlyFocusedPeer != this) { if (ComponentPeer::isValidPeer (currentlyFocusedPeer)) currentlyFocusedPeer->handleFocusLoss(); currentlyFocusedPeer = this; handleFocusGain(); } } void viewFocusLoss() { if (currentlyFocusedPeer == this) { currentlyFocusedPeer = 0; handleFocusLoss(); } } bool isFocused() const { return windowRef == GetUserFocusWindow() && HIViewSubtreeContainsFocus (viewRef); } void grabFocus() { if ((! isFocused()) && IsValidWindowPtr (windowRef)) { SetUserFocusWindow (windowRef); HIViewAdvanceFocus (viewRef, 0); } } //============================================================================== void repaint (int x, int y, int w, int h) { if (Rectangle::intersectRectangles (x, y, w, h, 0, 0, getComponent()->getWidth(), getComponent()->getHeight())) { if ((getStyleFlags() & windowRepaintedExplictly) == 0) { if (isCompositingWindow) { #if MACOS_10_3_OR_EARLIER RgnHandle rgn = NewRgn(); SetRectRgn (rgn, x, y, x + w, y + h); HIViewSetNeedsDisplayInRegion (viewRef, rgn, true); DisposeRgn (rgn); #else HIRect r; r.origin.x = x; r.origin.y = y; r.size.width = w; r.size.height = h; HIViewSetNeedsDisplayInRect (viewRef, &r, true); #endif } else { if (! isTimerRunning()) startTimer (20); } } repainter->repaint (x, y, w, h); } } void timerCallback() { performAnyPendingRepaintsNow(); } void performAnyPendingRepaintsNow() { stopTimer(); if (component->isVisible()) { #if MACOS_10_2_OR_EARLIER if (! isCompositingWindow) { Rect w; GetWindowBounds (windowRef, windowRegionToUse, &w); RgnHandle rgn = NewRgn(); SetRectRgn (rgn, 0, 0, w.right - w.left, w.bottom - w.top); UpdateControls (windowRef, rgn); DisposeRgn (rgn); } else { EventRef theEvent; EventTypeSpec eventTypes[1]; eventTypes[0].eventClass = kEventClassControl; eventTypes[0].eventKind = kEventControlDraw; int n = 3; while (--n >= 0 && ReceiveNextEvent (1, eventTypes, kEventDurationNoWait, true, &theEvent) == noErr) { if (GetEventClass (theEvent) == kEventClassAppleEvent) { EventRecord eventRec; if (ConvertEventRefToEventRecord (theEvent, &eventRec)) AEProcessAppleEvent (&eventRec); } else { EventTargetRef theTarget = GetEventDispatcherTarget(); SendEventToEventTarget (theEvent, theTarget); } ReleaseEvent (theEvent); } } #else HIViewRender (viewRef); #endif } } //============================================================================== juce_UseDebuggingNewOperator WindowRef windowRef; HIViewRef viewRef; private: EventHandlerRef eventHandlerRef; bool fullScreen, isSharedWindow, isCompositingWindow; StringArray dragAndDropFiles; //============================================================================== class RepaintManager : public Timer { public: RepaintManager (HIViewComponentPeer* const peer_) : peer (peer_), image (0) { } ~RepaintManager() { delete image; } void timerCallback() { stopTimer(); deleteAndZero (image); } void repaint (int x, int y, int w, int h) { regionsNeedingRepaint.add (x, y, w, h); } void repaintAnyRemainingRegions() { // if any regions have been invaldated during the paint callback, // we need to repaint them explicitly because the mac throws this // stuff away for (RectangleList::Iterator i (regionsNeedingRepaint); i.next();) { const Rectangle& r = *i.getRectangle(); peer->repaint (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } } void paint (CGContextRef cgContext, int x, int y, int w, int h) { if (w > 0 && h > 0) { bool refresh = false; int imW = image != 0 ? image->getWidth() : 0; int imH = image != 0 ? image->getHeight() : 0; if (imW < w || imH < h) { imW = jmin (peer->getComponent()->getWidth(), (w + 31) & ~31); imH = jmin (peer->getComponent()->getHeight(), (h + 31) & ~31); delete image; image = new MacBitmapImage (peer->getComponent()->isOpaque() ? Image::RGB : Image::ARGB, imW, imH, false); refresh = true; } else if (imageX > x || imageY > y || imageX + imW < x + w || imageY + imH < y + h) { refresh = true; } if (refresh) { regionsNeedingRepaint.clear(); regionsNeedingRepaint.addWithoutMerging (Rectangle (x, y, imW, imH)); imageX = x; imageY = y; } LowLevelGraphicsSoftwareRenderer context (*image); context.setOrigin (-imageX, -imageY); if (context.reduceClipRegion (regionsNeedingRepaint)) { regionsNeedingRepaint.clear(); if (! peer->getComponent()->isOpaque()) { for (RectangleList::Iterator i (*context.getRawClipRegion()); i.next();) { const Rectangle& r = *i.getRectangle(); image->clear (r.getX(), r.getY(), r.getWidth(), r.getHeight()); } } regionsNeedingRepaint.clear(); peer->clearMaskedRegion(); peer->handlePaint (context); } else { regionsNeedingRepaint.clear(); } if (! peer->maskedRegion.isEmpty()) { RectangleList total (Rectangle (x, y, w, h)); total.subtract (peer->maskedRegion); CGRect* rects = (CGRect*) juce_malloc (sizeof (CGRect) * total.getNumRectangles()); int n = 0; for (RectangleList::Iterator i (total); i.next();) { const Rectangle& r = *i.getRectangle(); rects[n].origin.x = (int) r.getX(); rects[n].origin.y = (int) r.getY(); rects[n].size.width = roundFloatToInt (r.getWidth()); rects[n++].size.height = roundFloatToInt (r.getHeight()); } CGContextClipToRects (cgContext, rects, n); juce_free (rects); } if (peer->isSharedWindow) { CGRect clip; clip.origin.x = x; clip.origin.y = y; clip.size.width = jmin (w, peer->getComponent()->getWidth() - x); clip.size.height = jmin (h, peer->getComponent()->getHeight() - y); CGContextClipToRect (cgContext, clip); } image->blitToContext (cgContext, imageX, imageY); } startTimer (3000); } private: HIViewComponentPeer* const peer; MacBitmapImage* image; int imageX, imageY; RectangleList regionsNeedingRepaint; RepaintManager (const RepaintManager&); const RepaintManager& operator= (const RepaintManager&); }; RepaintManager* repainter; friend class RepaintManager; //============================================================================== static OSStatus handleFrameRepaintEvent (EventHandlerCallRef myHandler, EventRef theEvent, void* userData) { // don't draw the frame.. return noErr; } //============================================================================== OSStatus handleKeyEvent (EventRef theEvent, juce_wchar textCharacter) { updateModifiers (theEvent); UniChar unicodeChars [4]; zeromem (unicodeChars, sizeof (unicodeChars)); GetEventParameter (theEvent, kEventParamKeyUnicodes, typeUnicodeText, 0, sizeof (unicodeChars), 0, unicodeChars); int keyCode = (int) (unsigned int) unicodeChars[0]; UInt32 rawKey = 0; GetEventParameter (theEvent, kEventParamKeyCode, typeUInt32, 0, sizeof (UInt32), 0, &rawKey); if ((currentModifiers & ModifierKeys::ctrlModifier) != 0 && keyCode >= 1 && keyCode <= 26) { keyCode += ('A' - 1); } else { static const int keyTranslations[] = { 0, 's', 'd', 'f', 'h', 'g', 'z', 'x', 'c', 'v', 0xa7, 'b', 'q', 'w', 'e', 'r', 'y', 't', '1', '2', '3', '4', '6', '5', '=', '9', '7', '-', '8', '0', ']', 'o', 'u', '[', 'i', 'p', KeyPress::returnKey, 'l', 'j', '\'', 'k', ';', '\\', ',', '/', 'n', 'm', '.', 0, KeyPress::spaceKey, '`', KeyPress::backspaceKey, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KeyPress::numberPadDecimalPoint, 0, KeyPress::numberPadMultiply, 0, KeyPress::numberPadAdd, 0, KeyPress::numberPadDelete, 0, 0, 0, KeyPress::numberPadDivide, KeyPress::returnKey, 0, KeyPress::numberPadSubtract, 0, 0, KeyPress::numberPadEquals, KeyPress::numberPad0, KeyPress::numberPad1, KeyPress::numberPad2, KeyPress::numberPad3, KeyPress::numberPad4, KeyPress::numberPad5, KeyPress::numberPad6, KeyPress::numberPad7, 0, KeyPress::numberPad8, KeyPress::numberPad9, 0, 0, 0, KeyPress::F5Key, KeyPress::F6Key, KeyPress::F7Key, KeyPress::F3Key, KeyPress::F8Key, KeyPress::F9Key, 0, KeyPress::F11Key, 0, KeyPress::F13Key, KeyPress::F16Key, KeyPress::F14Key, 0, KeyPress::F10Key, 0, KeyPress::F12Key, 0, KeyPress::F15Key, 0, KeyPress::homeKey, KeyPress::pageUpKey, 0, KeyPress::F4Key, KeyPress::endKey, KeyPress::F2Key, KeyPress::pageDownKey, KeyPress::F1Key, KeyPress::leftKey, KeyPress::rightKey, KeyPress::downKey, KeyPress::upKey, 0 }; if (((unsigned int) rawKey) < (unsigned int) numElementsInArray (keyTranslations) && keyTranslations [rawKey] != 0) { keyCode = keyTranslations [rawKey]; } if ((rawKey == 0 && textCharacter != 0) || (CharacterFunctions::isLetterOrDigit ((juce_wchar) keyCode) && CharacterFunctions::isLetterOrDigit (textCharacter))) // correction for azerty-type layouts.. { keyCode = CharacterFunctions::toLowerCase (textCharacter); } } if ((currentModifiers & (ModifierKeys::commandModifier | ModifierKeys::ctrlModifier)) != 0) textCharacter = 0; static juce_wchar lastTextCharacter = 0; switch (GetEventKind (theEvent)) { case kEventRawKeyDown: { keysCurrentlyDown.addIfNotAlreadyThere ((void*) keyCode); lastTextCharacter = textCharacter; const bool used1 = handleKeyUpOrDown(); const bool used2 = handleKeyPress (keyCode, textCharacter); if (used1 || used2) return noErr; break; } case kEventRawKeyUp: keysCurrentlyDown.removeValue ((void*) keyCode); lastTextCharacter = 0; if (handleKeyUpOrDown()) return noErr; break; case kEventRawKeyRepeat: if (handleKeyPress (keyCode, lastTextCharacter)) return noErr; break; case kEventRawKeyModifiersChanged: handleModifierKeysChange(); break; default: jassertfalse break; } return eventNotHandledErr; } OSStatus handleTextInputEvent (EventRef theEvent) { UniChar uc; GetEventParameter (theEvent, kEventParamTextInputSendText, typeUnicodeText, 0, sizeof (uc), 0, &uc); EventRef originalEvent; GetEventParameter (theEvent, kEventParamTextInputSendKeyboardEvent, typeEventRef, 0, sizeof (originalEvent), 0, &originalEvent); return handleKeyEvent (originalEvent, (juce_wchar) uc); } //============================================================================== OSStatus handleMouseEvent (EventHandlerCallRef callRef, EventRef theEvent) { MouseCheckTimer::getInstance()->moved (this); ::Point where; GetEventParameter (theEvent, kEventParamMouseLocation, typeQDPoint, 0, sizeof (::Point), 0, &where); int x = where.h; int y = where.v; globalPositionToRelative (x, y); int64 time = getEventTime (theEvent); switch (GetEventKind (theEvent)) { case kEventMouseMoved: MouseCheckTimer::getInstance()->hasEverHadAMouseMove = true; updateModifiers (theEvent); handleMouseMove (x, y, time); break; case kEventMouseDragged: updateModifiers (theEvent); handleMouseDrag (x, y, time); break; case kEventMouseDown: { if (! Process::isForegroundProcess()) { ProcessSerialNumber psn; GetCurrentProcess (&psn); SetFrontProcessWithOptions (&psn, kSetFrontProcessFrontWindowOnly); toFront (true); } #if JUCE_QUICKTIME { long mods; GetEventParameter (theEvent, kEventParamKeyModifiers, typeUInt32, 0, sizeof (mods), 0, &mods); ::Point where; GetEventParameter (theEvent, kEventParamMouseLocation, typeQDPoint, 0, sizeof (::Point), 0, &where); OfferMouseClickToQuickTime (windowRef, where, EventTimeToTicks (GetEventTime (theEvent)), mods, component); } #endif if (component->isBroughtToFrontOnMouseClick() && ! component->isCurrentlyBlockedByAnotherModalComponent()) { //ActivateWindow (windowRef, true); SelectWindow (windowRef); } EventMouseButton button; GetEventParameter (theEvent, kEventParamMouseButton, typeMouseButton, 0, sizeof (EventMouseButton), 0, &button); // need to clear all these flags because sometimes the mac can swallow (right) mouse-up events and // this makes a button get stuck down. Since there's no other way to tell what buttons are down, // this is all I can think of doing about it.. currentModifiers &= ~(ModifierKeys::leftButtonModifier | ModifierKeys::rightButtonModifier | ModifierKeys::middleButtonModifier); if (button == kEventMouseButtonPrimary) currentModifiers |= ModifierKeys::leftButtonModifier; else if (button == kEventMouseButtonSecondary) currentModifiers |= ModifierKeys::rightButtonModifier; else if (button == kEventMouseButtonTertiary) currentModifiers |= ModifierKeys::middleButtonModifier; updateModifiers (theEvent); juce_currentMouseTrackingPeer = this; // puts the message dispatcher into mouse-tracking mode.. handleMouseDown (x, y, time); break; } case kEventMouseUp: { const int oldModifiers = currentModifiers; EventMouseButton button; GetEventParameter (theEvent, kEventParamMouseButton, typeMouseButton, 0, sizeof (EventMouseButton), 0, &button); if (button == kEventMouseButtonPrimary) currentModifiers &= ~ModifierKeys::leftButtonModifier; else if (button == kEventMouseButtonSecondary) currentModifiers &= ~ModifierKeys::rightButtonModifier; updateModifiers (theEvent); juce_currentMouseTrackingPeer = 0; handleMouseUp (oldModifiers, x, y, time); break; } case kEventMouseWheelMoved: { EventMouseWheelAxis axis; GetEventParameter (theEvent, kEventParamMouseWheelAxis, typeMouseWheelAxis, 0, sizeof (axis), 0, &axis); SInt32 delta; GetEventParameter (theEvent, kEventParamMouseWheelDelta, typeLongInteger, 0, sizeof (delta), 0, &delta); updateModifiers (theEvent); handleMouseWheel (axis == kEventMouseWheelAxisX ? delta * 10 : 0, axis == kEventMouseWheelAxisX ? 0 : delta * 10, time); break; } } return noErr; } //============================================================================== void doDragDropEnter (EventRef theEvent) { updateDragAndDropFileList (theEvent); if (dragAndDropFiles.size() > 0) { int x, y; component->getMouseXYRelative (x, y); handleFileDragMove (dragAndDropFiles, x, y); } } void doDragDropMove (EventRef theEvent) { if (dragAndDropFiles.size() > 0) { int x, y; component->getMouseXYRelative (x, y); handleFileDragMove (dragAndDropFiles, x, y); } } void doDragDropExit (EventRef theEvent) { if (dragAndDropFiles.size() > 0) handleFileDragExit (dragAndDropFiles); } void doDragDrop (EventRef theEvent) { updateDragAndDropFileList (theEvent); if (dragAndDropFiles.size() > 0) { int x, y; component->getMouseXYRelative (x, y); handleFileDragDrop (dragAndDropFiles, x, y); } } void updateDragAndDropFileList (EventRef theEvent) { dragAndDropFiles.clear(); DragRef dragRef; if (GetEventParameter (theEvent, kEventParamDragRef, typeDragRef, 0, sizeof (dragRef), 0, &dragRef) == noErr) { UInt16 numItems = 0; if (CountDragItems (dragRef, &numItems) == noErr) { for (int i = 0; i < (int) numItems; ++i) { DragItemRef ref; if (GetDragItemReferenceNumber (dragRef, i + 1, &ref) == noErr) { const FlavorType flavorType = kDragFlavorTypeHFS; Size size = 0; if (GetFlavorDataSize (dragRef, ref, flavorType, &size) == noErr) { void* data = juce_calloc (size); if (GetFlavorData (dragRef, ref, flavorType, data, &size, 0) == noErr) { HFSFlavor* f = (HFSFlavor*) data; FSRef fsref; if (FSpMakeFSRef (&f->fileSpec, &fsref) == noErr) { const String path (PlatformUtilities::makePathFromFSRef (&fsref)); if (path.isNotEmpty()) dragAndDropFiles.add (path); } } juce_free (data); } } } dragAndDropFiles.trim(); dragAndDropFiles.removeEmptyStrings(); } } } //============================================================================== void resizeViewToFitWindow() { HIRect r; if (isSharedWindow) { HIViewGetFrame (viewRef, &r); r.size.width = (float) component->getWidth(); r.size.height = (float) component->getHeight(); } else { r.origin.x = 0; r.origin.y = 0; Rect w; GetWindowBounds (windowRef, windowRegionToUse, &w); r.size.width = (float) (w.right - w.left); r.size.height = (float) (w.bottom - w.top); } HIViewSetFrame (viewRef, &r); #if MACOS_10_3_OR_EARLIER component->repaint(); #endif } //============================================================================== OSStatus hiViewDraw (EventRef theEvent) { CGContextRef context = 0; GetEventParameter (theEvent, kEventParamCGContextRef, typeCGContextRef, 0, sizeof (CGContextRef), 0, &context); CGrafPtr oldPort; CGrafPtr port = 0; if (context == 0) { GetEventParameter (theEvent, kEventParamGrafPort, typeGrafPtr, 0, sizeof (CGrafPtr), 0, &port); GetPort (&oldPort); SetPort (port); if (port != 0) QDBeginCGContext (port, &context); if (! isCompositingWindow) { Rect bounds; GetWindowBounds (windowRef, windowRegionToUse, &bounds); CGContextTranslateCTM (context, 0, bounds.bottom - bounds.top); CGContextScaleCTM (context, 1.0, -1.0); } if (isSharedWindow) { // NB - Had terrible problems trying to correctly get the position // of this view relative to the window, and this seems wrong, but // works better than any other method I've tried.. HIRect hiViewPos; HIViewGetFrame (viewRef, &hiViewPos); CGContextTranslateCTM (context, hiViewPos.origin.x, hiViewPos.origin.y); } } #if MACOS_10_2_OR_EARLIER RgnHandle rgn = 0; GetEventParameter (theEvent, kEventParamRgnHandle, typeQDRgnHandle, 0, sizeof (RgnHandle), 0, &rgn); CGRect clip; // (avoid doing this in plugins because of some strange redraw bugs..) if (rgn != 0 && JUCEApplication::getInstance() != 0) { Rect bounds; GetRegionBounds (rgn, &bounds); clip.origin.x = bounds.left; clip.origin.y = bounds.top; clip.size.width = bounds.right - bounds.left; clip.size.height = bounds.bottom - bounds.top; } else { HIViewGetBounds (viewRef, &clip); clip.origin.x = 0; clip.origin.y = 0; } #else CGRect clip (CGContextGetClipBoundingBox (context)); #endif clip = CGRectIntegral (clip); if (clip.origin.x < 0) { clip.size.width += clip.origin.x; clip.origin.x = 0; } if (clip.origin.y < 0) { clip.size.height += clip.origin.y; clip.origin.y = 0; } if (! component->isOpaque()) CGContextClearRect (context, clip); repainter->paint (context, (int) clip.origin.x, (int) clip.origin.y, (int) clip.size.width, (int) clip.size.height); if (port != 0) { CGContextFlush (context); QDEndCGContext (port, &context); SetPort (oldPort); } repainter->repaintAnyRemainingRegions(); return noErr; } //============================================================================== OSStatus handleWindowClassEvent (EventRef theEvent) { switch (GetEventKind (theEvent)) { case kEventWindowBoundsChanged: resizeViewToFitWindow(); break; // allow other handlers in the event chain to also get a look at the events case kEventWindowBoundsChanging: if ((styleFlags & (windowIsResizable | windowHasTitleBar)) == (windowIsResizable | windowHasTitleBar)) { UInt32 atts = 0; GetEventParameter (theEvent, kEventParamAttributes, typeUInt32, 0, sizeof (UInt32), 0, &atts); if ((atts & (kWindowBoundsChangeUserDrag | kWindowBoundsChangeUserResize)) != 0) { if (component->isCurrentlyBlockedByAnotherModalComponent()) { Component* const modal = Component::getCurrentlyModalComponent(); if (modal != 0) modal->inputAttemptWhenModal(); } if ((atts & kWindowBoundsChangeUserResize) != 0 && constrainer != 0 && ! isSharedWindow) { Rect current; GetEventParameter (theEvent, kEventParamCurrentBounds, typeQDRectangle, 0, sizeof (Rect), 0, ¤t); int x = current.left; int y = current.top; int w = current.right - current.left; int h = current.bottom - current.top; const Rectangle currentRect (getComponent()->getBounds()); constrainer->checkBounds (x, y, w, h, currentRect, Desktop::getInstance().getAllMonitorDisplayAreas().getBounds(), y != currentRect.getY() && y + h == currentRect.getBottom(), x != currentRect.getX() && x + w == currentRect.getRight(), y == currentRect.getY() && y + h != currentRect.getBottom(), x == currentRect.getX() && x + w != currentRect.getRight()); current.left = x; current.top = y; current.right = x + w; current.bottom = y + h; SetEventParameter (theEvent, kEventParamCurrentBounds, typeQDRectangle, sizeof (Rect), ¤t); return noErr; } } } break; case kEventWindowFocusAcquired: keysCurrentlyDown.clear(); if ((! isSharedWindow) || HIViewSubtreeContainsFocus (viewRef)) viewFocusGain(); break; // allow other handlers in the event chain to also get a look at the events case kEventWindowFocusRelinquish: keysCurrentlyDown.clear(); viewFocusLoss(); break; // allow other handlers in the event chain to also get a look at the events case kEventWindowCollapsed: minimisedWindows.addIfNotAlreadyThere (windowRef); handleMovedOrResized(); break; // allow other handlers in the event chain to also get a look at the events case kEventWindowExpanded: minimisedWindows.removeValue (windowRef); handleMovedOrResized(); break; // allow other handlers in the event chain to also get a look at the events case kEventWindowShown: break; // allow other handlers in the event chain to also get a look at the events case kEventWindowClose: if (isSharedWindow) break; // break to let the OS delete the window handleUserClosingWindow(); return noErr; // avoids letting the OS to delete the window, we'll do that ourselves. default: break; } return eventNotHandledErr; } OSStatus handleWindowEventForPeer (EventHandlerCallRef callRef, EventRef theEvent) { switch (GetEventClass (theEvent)) { case kEventClassMouse: { static HIViewComponentPeer* lastMouseDownPeer = 0; const UInt32 eventKind = GetEventKind (theEvent); HIViewRef view = 0; if (eventKind == kEventMouseDragged) { view = viewRef; } else { HIViewGetViewForMouseEvent (HIViewGetRoot (windowRef), theEvent, &view); if (view != viewRef) { if ((eventKind == kEventMouseUp || eventKind == kEventMouseExited) && ComponentPeer::isValidPeer (lastMouseDownPeer)) { return lastMouseDownPeer->handleMouseEvent (callRef, theEvent); } return eventNotHandledErr; } } if (eventKind == kEventMouseDown || eventKind == kEventMouseDragged || eventKind == kEventMouseEntered) { lastMouseDownPeer = this; } return handleMouseEvent (callRef, theEvent); } break; case kEventClassWindow: return handleWindowClassEvent (theEvent); case kEventClassKeyboard: if (isFocused()) return handleKeyEvent (theEvent, 0); break; case kEventClassTextInput: if (isFocused()) return handleTextInputEvent (theEvent); break; default: break; } return eventNotHandledErr; } static pascal OSStatus handleWindowEvent (EventHandlerCallRef callRef, EventRef theEvent, void* userData) { MessageManager::delayWaitCursor(); HIViewComponentPeer* const peer = (HIViewComponentPeer*) userData; const MessageManagerLock messLock; if (ComponentPeer::isValidPeer (peer)) return peer->handleWindowEventForPeer (callRef, theEvent); return eventNotHandledErr; } //============================================================================== static pascal OSStatus hiViewEventHandler (EventHandlerCallRef myHandler, EventRef theEvent, void* userData) { MessageManager::delayWaitCursor(); const UInt32 eventKind = GetEventKind (theEvent); const UInt32 eventClass = GetEventClass (theEvent); if (eventClass == kEventClassHIObject) { switch (eventKind) { case kEventHIObjectConstruct: { void* data = juce_calloc (sizeof (void*)); SetEventParameter (theEvent, kEventParamHIObjectInstance, typeVoidPtr, sizeof (void*), &data); return noErr; } case kEventHIObjectInitialize: GetEventParameter (theEvent, 'peer', typeVoidPtr, 0, sizeof (void*), 0, (void**) userData); return noErr; case kEventHIObjectDestruct: juce_free (userData); return noErr; default: break; } } else if (eventClass == kEventClassControl) { HIViewComponentPeer* const peer = *(HIViewComponentPeer**) userData; const MessageManagerLock messLock; if (! ComponentPeer::isValidPeer (peer)) return eventNotHandledErr; switch (eventKind) { case kEventControlDraw: return peer->hiViewDraw (theEvent); case kEventControlBoundsChanged: { HIRect bounds; HIViewGetBounds (peer->viewRef, &bounds); peer->repaint (0, 0, roundFloatToInt (bounds.size.width), roundFloatToInt (bounds.size.height)); peer->handleMovedOrResized(); return noErr; } case kEventControlHitTest: { HIPoint where; GetEventParameter (theEvent, kEventParamMouseLocation, typeHIPoint, 0, sizeof (HIPoint), 0, &where); HIRect bounds; HIViewGetBounds (peer->viewRef, &bounds); ControlPartCode part = kControlNoPart; if (CGRectContainsPoint (bounds, where)) part = 1; SetEventParameter (theEvent, kEventParamControlPart, typeControlPartCode, sizeof (ControlPartCode), &part); return noErr; } break; case kEventControlSetFocusPart: { ControlPartCode desiredFocus; if (GetEventParameter (theEvent, kEventParamControlPart, typeControlPartCode, 0, sizeof (ControlPartCode), 0, &desiredFocus) != noErr) break; if (desiredFocus == kControlNoPart) peer->viewFocusLoss(); else peer->viewFocusGain(); return noErr; } break; case kEventControlDragEnter: { #if MACOS_10_2_OR_EARLIER enum { kEventParamControlWouldAcceptDrop = 'cldg' }; #endif Boolean accept = true; SetEventParameter (theEvent, kEventParamControlWouldAcceptDrop, typeBoolean, sizeof (accept), &accept); peer->doDragDropEnter (theEvent); return noErr; } case kEventControlDragWithin: peer->doDragDropMove (theEvent); return noErr; case kEventControlDragLeave: peer->doDragDropExit (theEvent); return noErr; case kEventControlDragReceive: peer->doDragDrop (theEvent); return noErr; case kEventControlOwningWindowChanged: return peer->ownerWindowChanged (theEvent); #if ! MACOS_10_2_OR_EARLIER case kEventControlGetFrameMetrics: { CallNextEventHandler (myHandler, theEvent); HIViewFrameMetrics metrics; GetEventParameter (theEvent, kEventParamControlFrameMetrics, typeControlFrameMetrics, 0, sizeof (metrics), 0, &metrics); metrics.top = metrics.bottom = 0; SetEventParameter (theEvent, kEventParamControlFrameMetrics, typeControlFrameMetrics, sizeof (metrics), &metrics); return noErr; } #endif case kEventControlInitialize: { UInt32 features = kControlSupportsDragAndDrop | kControlSupportsFocus | kControlHandlesTracking | kControlSupportsEmbedding | (1 << 8) /*kHIViewFeatureGetsFocusOnClick*/; SetEventParameter (theEvent, kEventParamControlFeatures, typeUInt32, sizeof (UInt32), &features); return noErr; } default: break; } } return eventNotHandledErr; } //============================================================================== WindowRef createNewWindow (const int windowStyleFlags) { jassert (windowRef == 0); static ToolboxObjectClassRef customWindowClass = 0; if (customWindowClass == 0) { // Register our window class const EventTypeSpec customTypes[] = { { kEventClassWindow, kEventWindowDrawFrame } }; UnsignedWide t; Microseconds (&t); const String randomString ((int) (t.lo & 0x7ffffff)); const String juceWindowClassName (T("JUCEWindowClass_") + randomString); CFStringRef juceWindowClassNameCFString = PlatformUtilities::juceStringToCFString (juceWindowClassName); RegisterToolboxObjectClass (juceWindowClassNameCFString, 0, 1, customTypes, NewEventHandlerUPP (handleFrameRepaintEvent), 0, &customWindowClass); CFRelease (juceWindowClassNameCFString); } Rect pos; pos.left = getComponent()->getX(); pos.top = getComponent()->getY(); pos.right = getComponent()->getRight(); pos.bottom = getComponent()->getBottom(); int attributes = kWindowStandardHandlerAttribute | kWindowCompositingAttribute; if ((windowStyleFlags & windowHasDropShadow) == 0) attributes |= kWindowNoShadowAttribute; if ((windowStyleFlags & windowIgnoresMouseClicks) != 0) attributes |= kWindowIgnoreClicksAttribute; #if ! MACOS_10_3_OR_EARLIER if ((windowStyleFlags & windowIsTemporary) != 0) attributes |= kWindowDoesNotCycleAttribute; #endif WindowRef newWindow = 0; if ((windowStyleFlags & windowHasTitleBar) == 0) { attributes |= kWindowCollapseBoxAttribute; WindowDefSpec customWindowSpec; customWindowSpec.defType = kWindowDefObjectClass; customWindowSpec.u.classRef = customWindowClass; CreateCustomWindow (&customWindowSpec, ((windowStyleFlags & windowIsTemporary) != 0) ? kUtilityWindowClass : (getComponent()->isAlwaysOnTop() ? kUtilityWindowClass : kDocumentWindowClass), attributes, &pos, &newWindow); } else { if ((windowStyleFlags & windowHasCloseButton) != 0) attributes |= kWindowCloseBoxAttribute; if ((windowStyleFlags & windowHasMinimiseButton) != 0) attributes |= kWindowCollapseBoxAttribute; if ((windowStyleFlags & windowHasMaximiseButton) != 0) attributes |= kWindowFullZoomAttribute; if ((windowStyleFlags & windowIsResizable) != 0) attributes |= kWindowResizableAttribute | kWindowLiveResizeAttribute; CreateNewWindow (kDocumentWindowClass, attributes, &pos, &newWindow); } jassert (newWindow != 0); if (newWindow != 0) { HideWindow (newWindow); SetAutomaticControlDragTrackingEnabledForWindow (newWindow, true); if (! getComponent()->isOpaque()) SetWindowAlpha (newWindow, 0.9999999f); // to fool it into giving the window an alpha-channel } return newWindow; } OSStatus ownerWindowChanged (EventRef theEvent) { WindowRef newWindow = 0; GetEventParameter (theEvent, kEventParamControlCurrentOwningWindow, typeWindowRef, 0, sizeof (newWindow), 0, &newWindow); if (windowRef != newWindow) { if (eventHandlerRef != 0) { RemoveEventHandler (eventHandlerRef); eventHandlerRef = 0; } windowRef = newWindow; if (windowRef != 0) { const EventTypeSpec eventTypes[] = { { kEventClassWindow, kEventWindowBoundsChanged }, { kEventClassWindow, kEventWindowBoundsChanging }, { kEventClassWindow, kEventWindowFocusAcquired }, { kEventClassWindow, kEventWindowFocusRelinquish }, { kEventClassWindow, kEventWindowCollapsed }, { kEventClassWindow, kEventWindowExpanded }, { kEventClassWindow, kEventWindowShown }, { kEventClassWindow, kEventWindowClose }, { kEventClassMouse, kEventMouseDown }, { kEventClassMouse, kEventMouseUp }, { kEventClassMouse, kEventMouseMoved }, { kEventClassMouse, kEventMouseDragged }, { kEventClassMouse, kEventMouseEntered }, { kEventClassMouse, kEventMouseExited }, { kEventClassMouse, kEventMouseWheelMoved }, { kEventClassKeyboard, kEventRawKeyUp }, { kEventClassKeyboard, kEventRawKeyRepeat }, { kEventClassKeyboard, kEventRawKeyModifiersChanged }, { kEventClassTextInput, kEventTextInputUnicodeForKeyEvent } }; static EventHandlerUPP handleWindowEventUPP = 0; if (handleWindowEventUPP == 0) handleWindowEventUPP = NewEventHandlerUPP (handleWindowEvent); InstallWindowEventHandler (windowRef, handleWindowEventUPP, GetEventTypeCount (eventTypes), eventTypes, (void*) this, (EventHandlerRef*) &eventHandlerRef); WindowAttributes attributes; GetWindowAttributes (windowRef, &attributes); #if MACOS_10_3_OR_EARLIER isCompositingWindow = ((attributes & kWindowCompositingAttribute) != 0); #else isCompositingWindow = HIViewIsCompositingEnabled (viewRef); #endif MouseCheckTimer::getInstance()->resetMouseMoveChecker(); } } resizeViewToFitWindow(); return noErr; } void createNewHIView() { jassert (viewRef == 0); if (viewClassRef == 0) { // Register our HIView class EventTypeSpec viewEvents[] = { { kEventClassHIObject, kEventHIObjectConstruct }, { kEventClassHIObject, kEventHIObjectInitialize }, { kEventClassHIObject, kEventHIObjectDestruct }, { kEventClassControl, kEventControlInitialize }, { kEventClassControl, kEventControlDraw }, { kEventClassControl, kEventControlBoundsChanged }, { kEventClassControl, kEventControlSetFocusPart }, { kEventClassControl, kEventControlHitTest }, { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragReceive }, { kEventClassControl, kEventControlOwningWindowChanged } }; UnsignedWide t; Microseconds (&t); const String randomString ((int) (t.lo & 0x7ffffff)); const String juceHiViewClassName (T("JUCEHIViewClass_") + randomString); juceHiViewClassNameCFString = PlatformUtilities::juceStringToCFString (juceHiViewClassName); HIObjectRegisterSubclass (juceHiViewClassNameCFString, kHIViewClassID, 0, NewEventHandlerUPP (hiViewEventHandler), GetEventTypeCount (viewEvents), viewEvents, 0, &viewClassRef); } EventRef event; CreateEvent (0, kEventClassHIObject, kEventHIObjectInitialize, GetCurrentEventTime(), kEventAttributeNone, &event); void* thisPointer = this; SetEventParameter (event, 'peer', typeVoidPtr, sizeof (void*), &thisPointer); HIObjectCreate (juceHiViewClassNameCFString, event, (HIObjectRef*) &viewRef); SetControlDragTrackingEnabled (viewRef, true); if (isSharedWindow) { setBounds (component->getX(), component->getY(), component->getWidth(), component->getHeight(), false); } } }; //============================================================================== bool juce_isHIViewCreatedByJuce (HIViewRef view) { return juceHiViewClassNameCFString != 0 && HIObjectIsOfClass ((HIObjectRef) view, juceHiViewClassNameCFString); } bool juce_isWindowCreatedByJuce (WindowRef window) { for (int i = ComponentPeer::getNumPeers(); --i >= 0;) if (ComponentPeer::getPeer(i)->getNativeHandle() == window) return true; return false; } static void trackNextMouseEvent() { UInt32 mods; MouseTrackingResult result; ::Point where; if (TrackMouseLocationWithOptions ((GrafPtr) -1, 0, 0.01, //kEventDurationForever, &where, &mods, &result) != noErr || ! ComponentPeer::isValidPeer (juce_currentMouseTrackingPeer)) { juce_currentMouseTrackingPeer = 0; return; } if (result == kMouseTrackingTimedOut) return; #if MACOS_10_3_OR_EARLIER const int x = where.h - juce_currentMouseTrackingPeer->getScreenX(); const int y = where.v - juce_currentMouseTrackingPeer->getScreenY(); #else HIPoint p; p.x = where.h; p.y = where.v; HIPointConvert (&p, kHICoordSpaceScreenPixel, 0, kHICoordSpaceView, ((HIViewComponentPeer*) juce_currentMouseTrackingPeer)->viewRef); const int x = p.x; const int y = p.y; #endif if (result == kMouseTrackingMouseDragged) { updateModifiers (0); juce_currentMouseTrackingPeer->handleMouseDrag (x, y, getEventTime (0)); if (! ComponentPeer::isValidPeer (juce_currentMouseTrackingPeer)) { juce_currentMouseTrackingPeer = 0; return; } } else if (result == kMouseTrackingMouseUp || result == kMouseTrackingUserCancelled || result == kMouseTrackingMouseMoved) { ComponentPeer* const oldPeer = juce_currentMouseTrackingPeer; juce_currentMouseTrackingPeer = 0; if (ComponentPeer::isValidPeer (oldPeer)) { const int oldModifiers = currentModifiers; currentModifiers &= ~(ModifierKeys::leftButtonModifier | ModifierKeys::rightButtonModifier | ModifierKeys::middleButtonModifier); updateModifiers (0); oldPeer->handleMouseUp (oldModifiers, x, y, getEventTime (0)); } } } bool juce_dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMessages) { if (juce_currentMouseTrackingPeer != 0) trackNextMouseEvent(); EventRef theEvent; if (ReceiveNextEvent (0, 0, (returnIfNoPendingMessages) ? kEventDurationNoWait : kEventDurationForever, true, &theEvent) == noErr) { if (GetEventClass (theEvent) == kEventClassAppleEvent) { EventRecord eventRec; if (ConvertEventRefToEventRecord (theEvent, &eventRec)) AEProcessAppleEvent (&eventRec); } else { EventTargetRef theTarget = GetEventDispatcherTarget(); SendEventToEventTarget (theEvent, theTarget); } ReleaseEvent (theEvent); return true; } return false; } //============================================================================== ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) { return new HIViewComponentPeer (this, styleFlags, (HIViewRef) windowToAttachTo); } //============================================================================== void MouseCheckTimer::timerCallback() { if (ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) return; if (Process::isForegroundProcess()) { bool stillOver = false; int x = 0, y = 0, w = 0, h = 0; int mx = 0, my = 0; const bool validWindow = ComponentPeer::isValidPeer (lastPeerUnderMouse); if (validWindow) { lastPeerUnderMouse->getBounds (x, y, w, h, true); Desktop::getMousePosition (mx, my); stillOver = (mx >= x && my >= y && mx < x + w && my < y + h); if (stillOver) { // check if it's over an embedded HIView int rx = mx, ry = my; lastPeerUnderMouse->globalPositionToRelative (rx, ry); HIPoint hipoint; hipoint.x = rx; hipoint.y = ry; HIViewRef root; GetRootControl ((WindowRef) lastPeerUnderMouse->getNativeHandle(), &root); HIViewRef hitview; if (HIViewGetSubviewHit (root, &hipoint, true, &hitview) == noErr && hitview != 0) { stillOver = HIObjectIsOfClass ((HIObjectRef) hitview, juceHiViewClassNameCFString); } } } if (! stillOver) { // mouse is outside our windows so set a normal cursor (only // if we're running as an app, not a plugin) if (JUCEApplication::getInstance() != 0) SetThemeCursor (kThemeArrowCursor); if (validWindow) lastPeerUnderMouse->handleMouseExit (mx - x, my - y, Time::currentTimeMillis()); if (hasEverHadAMouseMove) stopTimer(); } if ((! hasEverHadAMouseMove) && validWindow && (mx != lastX || my != lastY)) { lastX = mx; lastY = my; if (stillOver) lastPeerUnderMouse->handleMouseMove (mx - x, my - y, Time::currentTimeMillis()); } } } //============================================================================== // called from juce_Messaging.cpp void juce_HandleProcessFocusChange() { keysCurrentlyDown.clear(); if (HIViewComponentPeer::isValidPeer (currentlyFocusedPeer)) { if (Process::isForegroundProcess()) currentlyFocusedPeer->handleFocusGain(); else currentlyFocusedPeer->handleFocusLoss(); } } static bool performDrag (DragRef drag) { EventRecord event; event.what = mouseDown; event.message = 0; event.when = TickCount(); int x, y; Desktop::getMousePosition (x, y); event.where.h = x; event.where.v = y; event.modifiers = GetCurrentKeyModifiers(); RgnHandle rgn = NewRgn(); RgnHandle rgn2 = NewRgn(); SetRectRgn (rgn, event.where.h - 8, event.where.v - 8, event.where.h + 8, event.where.v + 8); CopyRgn (rgn, rgn2); InsetRgn (rgn2, 1, 1); DiffRgn (rgn, rgn2, rgn); DisposeRgn (rgn2); bool result = TrackDrag (drag, &event, rgn) == noErr; DisposeRgn (rgn); return result; } bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMoveFiles) { for (int i = ComponentPeer::getNumPeers(); --i >= 0;) ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); DragRef drag; bool result = false; if (NewDrag (&drag) == noErr) { for (int i = 0; i < files.size(); ++i) { HFSFlavor hfsData; if (PlatformUtilities::makeFSSpecFromPath (&hfsData.fileSpec, files[i])) { FInfo info; if (FSpGetFInfo (&hfsData.fileSpec, &info) == noErr) { hfsData.fileType = info.fdType; hfsData.fileCreator = info.fdCreator; hfsData.fdFlags = info.fdFlags; AddDragItemFlavor (drag, i + 1, kDragFlavorTypeHFS, &hfsData, sizeof (hfsData), 0); result = true; } } } SetDragAllowableActions (drag, canMoveFiles ? kDragActionAll : kDragActionCopy, false); if (result) result = performDrag (drag); DisposeDrag (drag); } return result; } bool DragAndDropContainer::performExternalDragDropOfText (const String& text) { jassertfalse // not implemented! return false; } //============================================================================== bool Process::isForegroundProcess() throw() { ProcessSerialNumber psn, front; GetCurrentProcess (&psn); GetFrontProcess (&front); Boolean b; return (SameProcess (&psn, &front, &b) == noErr) && b; } //============================================================================== bool Desktop::canUseSemiTransparentWindows() throw() { return true; } //============================================================================== void Desktop::getMousePosition (int& x, int& y) throw() { CGrafPtr currentPort; GetPort (¤tPort); if (! IsValidPort (currentPort)) { WindowRef front = FrontWindow(); if (front != 0) { SetPortWindowPort (front); } else { x = y = 0; return; } } ::Point p; GetMouse (&p); LocalToGlobal (&p); x = p.h; y = p.v; SetPort (currentPort); } void Desktop::setMousePosition (int x, int y) throw() { // this rubbish needs to be done around the warp call, to avoid causing a // bizarre glitch.. CGAssociateMouseAndMouseCursorPosition (false); CGSetLocalEventsSuppressionInterval (0); CGPoint pos = { x, y }; CGWarpMouseCursorPosition (pos); CGAssociateMouseAndMouseCursorPosition (true); } const ModifierKeys ModifierKeys::getCurrentModifiersRealtime() throw() { return ModifierKeys (currentModifiers); } //============================================================================== class ScreenSaverDefeater : public Timer, public DeletedAtShutdown { public: ScreenSaverDefeater() throw() { startTimer (10000); timerCallback(); } ~ScreenSaverDefeater() { } void timerCallback() { if (Process::isForegroundProcess()) UpdateSystemActivity (UsrActivity); } }; static ScreenSaverDefeater* screenSaverDefeater = 0; void Desktop::setScreenSaverEnabled (const bool isEnabled) throw() { if (screenSaverDefeater == 0) screenSaverDefeater = new ScreenSaverDefeater(); } bool Desktop::isScreenSaverEnabled() throw() { return screenSaverDefeater == 0; } //============================================================================== void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool clipToWorkArea) throw() { int mainMonitorIndex = 0; CGDirectDisplayID mainDisplayID = CGMainDisplayID(); CGDisplayCount count = 0; CGDirectDisplayID disps [8]; if (CGGetOnlineDisplayList (numElementsInArray (disps), disps, &count) == noErr) { for (int i = 0; i < count; ++i) { if (mainDisplayID == disps[i]) mainMonitorIndex = monitorCoords.size(); GDHandle hGDevice; if (clipToWorkArea && DMGetGDeviceByDisplayID ((DisplayIDType) disps[i], &hGDevice, false) == noErr) { Rect rect; GetAvailableWindowPositioningBounds (hGDevice, &rect); monitorCoords.add (Rectangle (rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)); } else { const CGRect r (CGDisplayBounds (disps[i])); monitorCoords.add (Rectangle ((int) r.origin.x, (int) r.origin.y, (int) r.size.width, (int) r.size.height)); } } } // make sure the first in the list is the main monitor if (mainMonitorIndex > 0) monitorCoords.swap (mainMonitorIndex, 0); jassert (monitorCoords.size() > 0); if (monitorCoords.size() == 0) monitorCoords.add (Rectangle (0, 0, 1024, 768)); } //============================================================================== struct CursorWrapper { Cursor* cursor; ThemeCursor themeCursor; }; void* juce_createMouseCursorFromImage (const Image& image, int hotspotX, int hotspotY) throw() { const int maxW = 16; const int maxH = 16; const Image* im = ℑ Image* newIm = 0; if (image.getWidth() > maxW || image.getHeight() > maxH) { im = newIm = image.createCopy (maxW, maxH); hotspotX = (hotspotX * maxW) / image.getWidth(); hotspotY = (hotspotY * maxH) / image.getHeight(); } Cursor* const c = new Cursor(); c->hotSpot.h = hotspotX; c->hotSpot.v = hotspotY; for (int y = 0; y < maxH; ++y) { c->data[y] = 0; c->mask[y] = 0; for (int x = 0; x < maxW; ++x) { const Colour pixelColour (im->getPixelAt (15 - x, y)); if (pixelColour.getAlpha() > 0.5f) { c->mask[y] |= (1 << x); if (pixelColour.getBrightness() < 0.5f) c->data[y] |= (1 << x); } } c->data[y] = CFSwapInt16BigToHost (c->data[y]); c->mask[y] = CFSwapInt16BigToHost (c->mask[y]); } if (newIm != 0) delete newIm; CursorWrapper* const cw = new CursorWrapper(); cw->cursor = c; cw->themeCursor = kThemeArrowCursor; return (void*) cw; } static void* cursorFromData (const unsigned char* data, const int size, int hx, int hy) throw() { Image* const im = ImageFileFormat::loadFrom ((const char*) data, size); jassert (im != 0); void* curs = juce_createMouseCursorFromImage (*im, hx, hy); delete im; return curs; } const unsigned int kSpecialNoCursor = 'nocr'; void* juce_createStandardMouseCursor (MouseCursor::StandardCursorType type) throw() { ThemeCursor id = kThemeArrowCursor; switch (type) { case MouseCursor::NormalCursor: id = kThemeArrowCursor; break; case MouseCursor::NoCursor: id = kSpecialNoCursor; break; case MouseCursor::DraggingHandCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,16,0,16,0,145,2,0,0,0,0,255,255,255,0, 0,0,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,52,148,47,0,200,185,16,130,90,12,74,139,107,84,123,39, 132,117,151,116,132,146,248,60,209,138,98,22,203,114,34,236,37,52,77,217, 247,154,191,119,110,240,193,128,193,95,163,56,60,234,98,135,2,0,59 }; const int cursDataSize = 99; return cursorFromData (cursData, cursDataSize, 8, 8); } break; case MouseCursor::CopyingCursor: id = kThemeCopyArrowCursor; break; case MouseCursor::WaitCursor: id = kThemeWatchCursor; break; case MouseCursor::IBeamCursor: id = kThemeIBeamCursor; break; case MouseCursor::PointingHandCursor: id = kThemePointingHandCursor; break; case MouseCursor::LeftRightResizeCursor: case MouseCursor::LeftEdgeResizeCursor: case MouseCursor::RightEdgeResizeCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,16,0,16,0,145,0,0,255,255,255,0,0,0,255, 255,255,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,38,148,143,169,203,237,15,19,0,106,202,64,111,22,32,224, 9,78,30,213,121,230,121,146,99,8,142,71,183,189,152,20,27,86,132,231, 58,83,0,0,59 }; const int cursDataSize = 85; return cursorFromData (cursData, cursDataSize, 8, 8); } case MouseCursor::UpDownResizeCursor: case MouseCursor::TopEdgeResizeCursor: case MouseCursor::BottomEdgeResizeCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,16,0,16,0,145,0,0,255,255,255,0,0,0,255, 255,255,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,38,148,111,128,187,16,202,90,152,48,10,55,169,189,192,245, 106,121,27,34,142,201,99,158,224,86,154,109,216,61,29,155,105,180,61,190, 121,84,0,0,59 }; const int cursDataSize = 85; return cursorFromData (cursData, cursDataSize, 8, 8); } case MouseCursor::TopLeftCornerResizeCursor: case MouseCursor::BottomRightCornerResizeCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,16,0,16,0,145,0,0,255,255,255,0,0,0,255, 255,255,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,43,132,15,162,187,16,255,18,99,14,202,217,44,158,213,221, 237,9,225,38,94,35,73,5,31,42,170,108,106,174,112,43,195,209,91,185, 104,174,131,208,77,66,28,10,0,59 }; const int cursDataSize = 90; return cursorFromData (cursData, cursDataSize, 8, 8); } case MouseCursor::TopRightCornerResizeCursor: case MouseCursor::BottomLeftCornerResizeCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,16,0,16,0,145,0,0,255,255,255,0,0,0,255, 255,255,0,0,0,33,249,4,1,0,0,2,0,44,0,0,0,0,16,0, 16,0,0,2,45,148,127,160,11,232,16,98,108,14,65,73,107,194,122,223, 92,65,141,216,145,134,162,153,221,25,128,73,166,62,173,16,203,237,188,94, 120,46,237,105,239,123,48,80,157,2,0,59 }; const int cursDataSize = 92; return cursorFromData (cursData, cursDataSize, 8, 8); } case MouseCursor::UpDownLeftRightResizeCursor: { static const unsigned char cursData[] = {71,73,70,56,57,97,15,0,15,0,145,0,0,0,0,0,255,255,255,0, 128,128,255,255,255,33,249,4,1,0,0,3,0,44,0,0,0,0,15,0, 15,0,0,2,46,156,63,129,139,1,202,26,152,48,186,73,109,114,65,85, 195,37,143,88,93,29,215,101,23,198,178,30,149,158,25,56,134,97,179,61, 158,213,126,203,234,99,220,34,56,70,1,0,59,0,0 }; const int cursDataSize = 93; return cursorFromData (cursData, cursDataSize, 7, 7); } case MouseCursor::CrosshairCursor: id = kThemeCrossCursor; break; } CursorWrapper* cw = new CursorWrapper(); cw->cursor = 0; cw->themeCursor = id; return (void*) cw; } void juce_deleteMouseCursor (void* const cursorHandle, const bool isStandard) throw() { CursorWrapper* const cw = (CursorWrapper*) cursorHandle; if (cw != 0) { delete cw->cursor; delete cw; } } void MouseCursor::showInAllWindows() const throw() { showInWindow (0); } void MouseCursor::showInWindow (ComponentPeer*) const throw() { const CursorWrapper* const cw = (CursorWrapper*) getHandle(); if (cw != 0) { static bool isCursorHidden = false; static bool showingWaitCursor = false; const bool shouldShowWaitCursor = (cw->themeCursor == kThemeWatchCursor); const bool shouldHideCursor = (cw->themeCursor == kSpecialNoCursor); if (shouldShowWaitCursor != showingWaitCursor && Process::isForegroundProcess()) { showingWaitCursor = shouldShowWaitCursor; QDDisplayWaitCursor (shouldShowWaitCursor); } if (shouldHideCursor != isCursorHidden) { isCursorHidden = shouldHideCursor; if (shouldHideCursor) HideCursor(); else ShowCursor(); } if (cw->cursor != 0) SetCursor (cw->cursor); else if (! (shouldShowWaitCursor || shouldHideCursor)) SetThemeCursor (cw->themeCursor); } } //============================================================================== Image* juce_createIconForFile (const File& file) { return 0; } //============================================================================== class MainMenuHandler; static MainMenuHandler* mainMenu = 0; class MainMenuHandler : private MenuBarModelListener, private DeletedAtShutdown { public: MainMenuHandler() throw() : currentModel (0) { } ~MainMenuHandler() throw() { setMenu (0); jassert (mainMenu == this); mainMenu = 0; } void setMenu (MenuBarModel* const newMenuBarModel) throw() { if (currentModel != newMenuBarModel) { if (currentModel != 0) currentModel->removeListener (this); currentModel = newMenuBarModel; if (currentModel != 0) currentModel->addListener (this); menuBarItemsChanged (0); } } void menuBarItemsChanged (MenuBarModel*) { ClearMenuBar(); if (currentModel != 0) { int id = 1000; const StringArray menuNames (currentModel->getMenuBarNames()); for (int i = 0; i < menuNames.size(); ++i) { const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames [i])); MenuRef m = createMenu (menu, menuNames [i], id, i); InsertMenu (m, 0); CFRelease (m); } } } void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) { MenuRef menu = 0; MenuItemIndex index = 0; GetIndMenuItemWithCommandID (0, info.commandID, 1, &menu, &index); FlashMenuBar (GetMenuID (menu)); FlashMenuBar (GetMenuID (menu)); } void invoke (const int id, ApplicationCommandManager* const commandManager, const int topLevelIndex) const { if (currentModel != 0) { if (commandManager != 0) { ApplicationCommandTarget::InvocationInfo info (id); info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; commandManager->invoke (info, true); } currentModel->menuItemSelected (id, topLevelIndex); } } MenuBarModel* currentModel; private: static MenuRef createMenu (const PopupMenu menu, const String& menuName, int& id, const int topLevelIndex) { MenuRef m = 0; if (CreateNewMenu (id++, kMenuAttrAutoDisable, &m) == noErr) { CFStringRef name = PlatformUtilities::juceStringToCFString (menuName); SetMenuTitleWithCFString (m, name); CFRelease (name); PopupMenu::MenuItemIterator iter (menu); while (iter.next()) { MenuItemIndex index = 0; int flags = kMenuAttrAutoDisable | kMenuItemAttrIgnoreMeta | kMenuItemAttrNotPreviousAlternate; if (! iter.isEnabled) flags |= kMenuItemAttrDisabled; CFStringRef text = PlatformUtilities::juceStringToCFString (iter.itemName.upToFirstOccurrenceOf (T(""), false, true)); if (iter.isSeparator) { AppendMenuItemTextWithCFString (m, text, kMenuItemAttrSeparator, 0, &index); } else if (iter.isSectionHeader) { AppendMenuItemTextWithCFString (m, text, kMenuItemAttrSectionHeader, 0, &index); } else if (iter.subMenu != 0) { AppendMenuItemTextWithCFString (m, text, flags, id++, &index); MenuRef sub = createMenu (*iter.subMenu, iter.itemName, id, topLevelIndex); SetMenuItemHierarchicalMenu (m, index, sub); CFRelease (sub); } else { AppendMenuItemTextWithCFString (m, text, flags, iter.itemId, &index); if (iter.isTicked) CheckMenuItem (m, index, true); SetMenuItemProperty (m, index, 'juce', 'apcm', sizeof (void*), &iter.commandManager); SetMenuItemProperty (m, index, 'juce', 'topi', sizeof (int), &topLevelIndex); if (iter.commandManager != 0) { const Array keyPresses (iter.commandManager->getKeyMappings() ->getKeyPressesAssignedToCommand (iter.itemId)); if (keyPresses.size() > 0) { const KeyPress& kp = keyPresses.getReference(0); int mods = 0; if (kp.getModifiers().isShiftDown()) mods |= kMenuShiftModifier; if (kp.getModifiers().isCtrlDown()) mods |= kMenuControlModifier; if (kp.getModifiers().isAltDown()) mods |= kMenuOptionModifier; if (! kp.getModifiers().isCommandDown()) mods |= kMenuNoCommandModifier; tchar keyCode = (tchar) kp.getKeyCode(); if (kp.getKeyCode() >= KeyPress::numberPad0 && kp.getKeyCode() <= KeyPress::numberPad9) { keyCode = (tchar) ((T('0') - KeyPress::numberPad0) + kp.getKeyCode()); } SetMenuItemCommandKey (m, index, true, 255); if (CharacterFunctions::isLetterOrDigit (keyCode) || CharacterFunctions::indexOfChar (T(",.;/\\'[]=-+_<>?{}\":"), keyCode, false) >= 0) { SetMenuItemModifiers (m, index, mods); SetMenuItemCommandKey (m, index, false, CharacterFunctions::toUpperCase (keyCode)); } else { const SInt16 glyph = getGlyphForKeyCode (kp.getKeyCode()); if (glyph != 0) { SetMenuItemModifiers (m, index, mods); SetMenuItemKeyGlyph (m, index, glyph); } } // if we set the key glyph to be a text char, and enable virtual // key triggering, it stops the menu automatically triggering the callback ChangeMenuItemAttributes (m, index, kMenuItemAttrUseVirtualKey, 0); } } } CFRelease (text); } } return m; } static SInt16 getGlyphForKeyCode (const int keyCode) throw() { if (keyCode == KeyPress::spaceKey) return kMenuSpaceGlyph; else if (keyCode == KeyPress::returnKey) return kMenuReturnGlyph; else if (keyCode == KeyPress::escapeKey) return kMenuEscapeGlyph; else if (keyCode == KeyPress::backspaceKey) return kMenuDeleteLeftGlyph; else if (keyCode == KeyPress::leftKey) return kMenuLeftArrowGlyph; else if (keyCode == KeyPress::rightKey) return kMenuRightArrowGlyph; else if (keyCode == KeyPress::upKey) return kMenuUpArrowGlyph; else if (keyCode == KeyPress::downKey) return kMenuDownArrowGlyph; else if (keyCode == KeyPress::pageUpKey) return kMenuPageUpGlyph; else if (keyCode == KeyPress::pageDownKey) return kMenuPageDownGlyph; else if (keyCode == KeyPress::endKey) return kMenuSoutheastArrowGlyph; else if (keyCode == KeyPress::homeKey) return kMenuNorthwestArrowGlyph; else if (keyCode == KeyPress::deleteKey) return kMenuDeleteRightGlyph; else if (keyCode == KeyPress::tabKey) return kMenuTabRightGlyph; else if (keyCode == KeyPress::F1Key) return kMenuF1Glyph; else if (keyCode == KeyPress::F2Key) return kMenuF2Glyph; else if (keyCode == KeyPress::F3Key) return kMenuF3Glyph; else if (keyCode == KeyPress::F4Key) return kMenuF4Glyph; else if (keyCode == KeyPress::F5Key) return kMenuF5Glyph; else if (keyCode == KeyPress::F6Key) return kMenuF6Glyph; else if (keyCode == KeyPress::F7Key) return kMenuF7Glyph; else if (keyCode == KeyPress::F8Key) return kMenuF8Glyph; else if (keyCode == KeyPress::F9Key) return kMenuF9Glyph; else if (keyCode == KeyPress::F10Key) return kMenuF10Glyph; else if (keyCode == KeyPress::F11Key) return kMenuF11Glyph; else if (keyCode == KeyPress::F12Key) return kMenuF12Glyph; else if (keyCode == KeyPress::F13Key) return kMenuF13Glyph; else if (keyCode == KeyPress::F14Key) return kMenuF14Glyph; else if (keyCode == KeyPress::F15Key) return kMenuF15Glyph; return 0; } }; void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel) throw() { if (getMacMainMenu() != newMenuBarModel) { if (newMenuBarModel == 0) { delete mainMenu; jassert (mainMenu == 0); // should be zeroed in the destructor } else { if (mainMenu == 0) mainMenu = new MainMenuHandler(); mainMenu->setMenu (newMenuBarModel); } } } MenuBarModel* MenuBarModel::getMacMainMenu() throw() { return mainMenu != 0 ? mainMenu->currentModel : 0; } // these functions are called externally from the message handling code void juce_MainMenuAboutToBeUsed() { // force an update of the items just before the menu appears.. if (mainMenu != 0) mainMenu->menuBarItemsChanged (0); } void juce_InvokeMainMenuCommand (const HICommand& command) { if (mainMenu != 0) { ApplicationCommandManager* commandManager = 0; int topLevelIndex = 0; if (GetMenuItemProperty (command.menu.menuRef, command.menu.menuItemIndex, 'juce', 'apcm', sizeof (commandManager), 0, &commandManager) == noErr && GetMenuItemProperty (command.menu.menuRef, command.menu.menuItemIndex, 'juce', 'topi', sizeof (topLevelIndex), 0, &topLevelIndex) == noErr) { mainMenu->invoke (command.commandID, commandManager, topLevelIndex); } } } //============================================================================== void PlatformUtilities::beep() { SysBeep (30); } //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) throw() { ClearCurrentScrap(); ScrapRef ref; GetCurrentScrap (&ref); const int len = text.length(); const int numBytes = sizeof (UniChar) * len; UniChar* const temp = (UniChar*) juce_calloc (numBytes); for (int i = 0; i < len; ++i) temp[i] = (UniChar) text[i]; PutScrapFlavor (ref, kScrapFlavorTypeUnicode, kScrapFlavorMaskNone, numBytes, temp); juce_free (temp); } const String SystemClipboard::getTextFromClipboard() throw() { String result; ScrapRef ref; GetCurrentScrap (&ref); Size size = 0; if (GetScrapFlavorSize (ref, kScrapFlavorTypeUnicode, &size) == noErr && size > 0) { void* const data = juce_calloc (size + 8); if (GetScrapFlavorData (ref, kScrapFlavorTypeUnicode, &size, data) == noErr) { result = PlatformUtilities::convertUTF16ToString ((UniChar*) data); } juce_free (data); } return result; } //============================================================================== bool AlertWindow::showNativeDialogBox (const String& title, const String& bodyText, bool isOkCancel) { Str255 tit, txt; PlatformUtilities::copyToStr255 (tit, title); PlatformUtilities::copyToStr255 (txt, bodyText); AlertStdAlertParamRec ar; ar.movable = true; ar.helpButton = false; ar.filterProc = 0; ar.defaultText = (const unsigned char*)-1; ar.cancelText = (const unsigned char*)((isOkCancel) ? -1 : 0); ar.otherText = 0; ar.defaultButton = kAlertStdAlertOKButton; ar.cancelButton = 0; ar.position = kWindowDefaultPosition; SInt16 result; StandardAlert (kAlertNoteAlert, tit, txt, &ar, &result); return result == kAlertStdAlertOKButton; } //============================================================================== const int KeyPress::spaceKey = ' '; const int KeyPress::returnKey = kReturnCharCode; const int KeyPress::escapeKey = kEscapeCharCode; const int KeyPress::backspaceKey = kBackspaceCharCode; const int KeyPress::leftKey = kLeftArrowCharCode; const int KeyPress::rightKey = kRightArrowCharCode; const int KeyPress::upKey = kUpArrowCharCode; const int KeyPress::downKey = kDownArrowCharCode; const int KeyPress::pageUpKey = kPageUpCharCode; const int KeyPress::pageDownKey = kPageDownCharCode; const int KeyPress::endKey = kEndCharCode; const int KeyPress::homeKey = kHomeCharCode; const int KeyPress::deleteKey = kDeleteCharCode; const int KeyPress::insertKey = -1; const int KeyPress::tabKey = kTabCharCode; const int KeyPress::F1Key = 0x10110; const int KeyPress::F2Key = 0x10111; const int KeyPress::F3Key = 0x10112; const int KeyPress::F4Key = 0x10113; const int KeyPress::F5Key = 0x10114; const int KeyPress::F6Key = 0x10115; const int KeyPress::F7Key = 0x10116; const int KeyPress::F8Key = 0x10117; const int KeyPress::F9Key = 0x10118; const int KeyPress::F10Key = 0x10119; const int KeyPress::F11Key = 0x1011a; const int KeyPress::F12Key = 0x1011b; const int KeyPress::F13Key = 0x1011c; const int KeyPress::F14Key = 0x1011d; const int KeyPress::F15Key = 0x1011e; const int KeyPress::F16Key = 0x1011f; const int KeyPress::numberPad0 = 0x30020; const int KeyPress::numberPad1 = 0x30021; const int KeyPress::numberPad2 = 0x30022; const int KeyPress::numberPad3 = 0x30023; const int KeyPress::numberPad4 = 0x30024; const int KeyPress::numberPad5 = 0x30025; const int KeyPress::numberPad6 = 0x30026; const int KeyPress::numberPad7 = 0x30027; const int KeyPress::numberPad8 = 0x30028; const int KeyPress::numberPad9 = 0x30029; const int KeyPress::numberPadAdd = 0x3002a; const int KeyPress::numberPadSubtract = 0x3002b; const int KeyPress::numberPadMultiply = 0x3002c; const int KeyPress::numberPadDivide = 0x3002d; const int KeyPress::numberPadSeparator = 0x3002e; const int KeyPress::numberPadDecimalPoint = 0x3002f; const int KeyPress::numberPadEquals = 0x30030; const int KeyPress::numberPadDelete = 0x30031; const int KeyPress::playKey = 0x30000; const int KeyPress::stopKey = 0x30001; const int KeyPress::fastForwardKey = 0x30002; const int KeyPress::rewindKey = 0x30003; //============================================================================== AppleRemoteDevice::AppleRemoteDevice() : device (0), queue (0), remoteId (0) { } AppleRemoteDevice::~AppleRemoteDevice() { stop(); } static io_object_t getAppleRemoteDevice() throw() { CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController"); io_iterator_t iter = 0; io_object_t iod = 0; if (IOServiceGetMatchingServices (kIOMasterPortDefault, dict, &iter) == kIOReturnSuccess && iter != 0) { iod = IOIteratorNext (iter); } IOObjectRelease (iter); return iod; } static bool createAppleRemoteInterface (io_object_t iod, void** device) throw() { jassert (*device == 0); io_name_t classname; if (IOObjectGetClass (iod, classname) == kIOReturnSuccess) { IOCFPlugInInterface** cfPlugInInterface = 0; SInt32 score = 0; if (IOCreatePlugInInterfaceForService (iod, kIOHIDDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &cfPlugInInterface, &score) == kIOReturnSuccess) { HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface, CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID), device); (void) hr; (*cfPlugInInterface)->Release (cfPlugInInterface); } } return *device != 0; } bool AppleRemoteDevice::start (const bool inExclusiveMode) throw() { if (queue != 0) return true; stop(); bool result = false; io_object_t iod = getAppleRemoteDevice(); if (iod != 0) { if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode)) result = true; else stop(); IOObjectRelease (iod); } return result; } void AppleRemoteDevice::stop() throw() { if (queue != 0) { (*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue); (*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue); (*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue); queue = 0; } if (device != 0) { (*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device); (*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device); device = 0; } } bool AppleRemoteDevice::isActive() const throw() { return queue != 0; } static void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*) { if (result == kIOReturnSuccess) ((AppleRemoteDevice*) target)->handleCallbackInternal(); } bool AppleRemoteDevice::open (const bool openInExclusiveMode) throw() { #if ! MACOS_10_2_OR_EARLIER Array cookies; CFArrayRef elements; IOHIDDeviceInterface122** const device122 = (IOHIDDeviceInterface122**) device; if ((*device122)->copyMatchingElements (device122, 0, &elements) != kIOReturnSuccess) return false; for (int i = 0; i < CFArrayGetCount (elements); ++i) { CFDictionaryRef element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements, i); // get the cookie CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey)); if (object == 0 || CFGetTypeID (object) != CFNumberGetTypeID()) continue; long number; if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number)) continue; cookies.add ((int) number); } CFRelease (elements); if ((*(IOHIDDeviceInterface**) device) ->open ((IOHIDDeviceInterface**) device, openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice : kIOHIDOptionsTypeNone) == KERN_SUCCESS) { queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device); if (queue != 0) { (*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12); for (int i = 0; i < cookies.size(); ++i) { IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i); (*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0); } CFRunLoopSourceRef eventSource; if ((*(IOHIDQueueInterface**) queue) ->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS) { if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue, appleRemoteQueueCallback, this, 0) == KERN_SUCCESS) { CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); (*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue); return true; } } } } #endif return false; } void AppleRemoteDevice::handleCallbackInternal() { int totalValues = 0; AbsoluteTime nullTime = { 0, 0 }; char cookies [12]; int numCookies = 0; while (numCookies < numElementsInArray (cookies)) { IOHIDEventStruct e; if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess) break; if ((int) e.elementCookie == 19) { remoteId = e.value; buttonPressed (switched, false); } else { totalValues += e.value; cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie; } } cookies [numCookies++] = 0; static const char buttonPatterns[] = { 14, 7, 6, 5, 14, 7, 6, 5, 0, 14, 8, 6, 5, 14, 8, 6, 5, 0, 14, 12, 11, 6, 5, 0, 14, 13, 11, 6, 5, 0, 14, 9, 6, 5, 14, 9, 6, 5, 0, 14, 10, 6, 5, 14, 10, 6, 5, 0, 14, 6, 5, 4, 2, 0, 14, 6, 5, 3, 2, 0, 14, 6, 5, 14, 6, 5, 0, 18, 14, 6, 5, 18, 14, 6, 5, 0, 19, 0 }; int buttonNum = (int) menuButton; int i = 0; while (i < numElementsInArray (buttonPatterns)) { if (strcmp (cookies, buttonPatterns + i) == 0) { buttonPressed ((ButtonType) buttonNum, totalValues > 0); break; } i += strlen (buttonPatterns + i) + 1; ++buttonNum; } } //============================================================================== #if JUCE_OPENGL //============================================================================== class WindowedGLContext : public OpenGLContext { public: WindowedGLContext (Component* const component, const OpenGLPixelFormat& pixelFormat_, AGLContext sharedContext) : renderContext (0), pixelFormat (pixelFormat_) { jassert (component != 0); HIViewComponentPeer* const peer = dynamic_cast (component->getTopLevelComponent()->getPeer()); if (peer == 0) return; GLint attribs [64]; int n = 0; attribs[n++] = AGL_RGBA; attribs[n++] = AGL_DOUBLEBUFFER; attribs[n++] = AGL_ACCELERATED; attribs[n++] = AGL_RED_SIZE; attribs[n++] = pixelFormat.redBits; attribs[n++] = AGL_GREEN_SIZE; attribs[n++] = pixelFormat.greenBits; attribs[n++] = AGL_BLUE_SIZE; attribs[n++] = pixelFormat.blueBits; attribs[n++] = AGL_ALPHA_SIZE; attribs[n++] = pixelFormat.alphaBits; attribs[n++] = AGL_DEPTH_SIZE; attribs[n++] = pixelFormat.depthBufferBits; attribs[n++] = AGL_STENCIL_SIZE; attribs[n++] = pixelFormat.stencilBufferBits; attribs[n++] = AGL_ACCUM_RED_SIZE; attribs[n++] = pixelFormat.accumulationBufferRedBits; attribs[n++] = AGL_ACCUM_GREEN_SIZE; attribs[n++] = pixelFormat.accumulationBufferGreenBits; attribs[n++] = AGL_ACCUM_BLUE_SIZE; attribs[n++] = pixelFormat.accumulationBufferBlueBits; attribs[n++] = AGL_ACCUM_ALPHA_SIZE; attribs[n++] = pixelFormat.accumulationBufferAlphaBits; // xxx not sure how to do fullSceneAntiAliasingNumSamples.. attribs[n++] = AGL_SAMPLE_BUFFERS_ARB; attribs[n++] = 1; attribs[n++] = AGL_SAMPLES_ARB; attribs[n++] = 4; attribs[n++] = AGL_CLOSEST_POLICY; attribs[n++] = AGL_NO_RECOVERY; attribs[n++] = AGL_NONE; renderContext = aglCreateContext (aglChoosePixelFormat (0, 0, attribs), sharedContext); aglSetDrawable (renderContext, GetWindowPort (peer->windowRef)); } ~WindowedGLContext() { makeInactive(); aglSetDrawable (renderContext, 0); aglDestroyContext (renderContext); } bool makeActive() const throw() { jassert (renderContext != 0); return aglSetCurrentContext (renderContext); } bool makeInactive() const throw() { return (! isActive()) || aglSetCurrentContext (0); } bool isActive() const throw() { return aglGetCurrentContext() == renderContext; } const OpenGLPixelFormat getPixelFormat() const { return pixelFormat; } void* getRawContext() const throw() { return renderContext; } void updateWindowPosition (int x, int y, int w, int h, int outerWindowHeight) { GLint bufferRect[4]; bufferRect[0] = x; bufferRect[1] = outerWindowHeight - (y + h); bufferRect[2] = w; bufferRect[3] = h; aglSetInteger (renderContext, AGL_BUFFER_RECT, bufferRect); aglEnable (renderContext, AGL_BUFFER_RECT); } void swapBuffers() { aglSwapBuffers (renderContext); } bool setSwapInterval (const int numFramesPerSwap) { return aglSetInteger (renderContext, AGL_SWAP_INTERVAL, (const GLint*) &numFramesPerSwap); } int getSwapInterval() const { GLint numFrames = 0; aglGetInteger (renderContext, AGL_SWAP_INTERVAL, &numFrames); return numFrames; } void repaint() { } //============================================================================== juce_UseDebuggingNewOperator AGLContext renderContext; private: OpenGLPixelFormat pixelFormat; //============================================================================== WindowedGLContext (const WindowedGLContext&); const WindowedGLContext& operator= (const WindowedGLContext&); }; //============================================================================== OpenGLContext* OpenGLContext::createContextForWindow (Component* const component, const OpenGLPixelFormat& pixelFormat, const OpenGLContext* const contextToShareWith) { WindowedGLContext* c = new WindowedGLContext (component, pixelFormat, contextToShareWith != 0 ? (AGLContext) contextToShareWith->getRawContext() : 0); if (c->renderContext == 0) deleteAndZero (c); return c; } void juce_glViewport (const int w, const int h) { glViewport (0, 0, w, h); } static int getAGLAttribute (AGLPixelFormat p, const GLint attrib) { GLint result = 0; aglDescribePixelFormat (p, attrib, &result); return result; } void OpenGLPixelFormat::getAvailablePixelFormats (Component* /*component*/, OwnedArray & results) { GLint attribs [64]; int n = 0; attribs[n++] = AGL_RGBA; attribs[n++] = AGL_DOUBLEBUFFER; attribs[n++] = AGL_ACCELERATED; attribs[n++] = AGL_NO_RECOVERY; attribs[n++] = AGL_NONE; AGLPixelFormat p = aglChoosePixelFormat (0, 0, attribs); while (p != 0) { OpenGLPixelFormat* const pf = new OpenGLPixelFormat(); pf->redBits = getAGLAttribute (p, AGL_RED_SIZE); pf->greenBits = getAGLAttribute (p, AGL_GREEN_SIZE); pf->blueBits = getAGLAttribute (p, AGL_BLUE_SIZE); pf->alphaBits = getAGLAttribute (p, AGL_ALPHA_SIZE); pf->depthBufferBits = getAGLAttribute (p, AGL_DEPTH_SIZE); pf->stencilBufferBits = getAGLAttribute (p, AGL_STENCIL_SIZE); pf->accumulationBufferRedBits = getAGLAttribute (p, AGL_ACCUM_RED_SIZE); pf->accumulationBufferGreenBits = getAGLAttribute (p, AGL_ACCUM_GREEN_SIZE); pf->accumulationBufferBlueBits = getAGLAttribute (p, AGL_ACCUM_BLUE_SIZE); pf->accumulationBufferAlphaBits = getAGLAttribute (p, AGL_ACCUM_ALPHA_SIZE); results.add (pf); p = aglNextPixelFormat (p); } } #endif END_JUCE_NAMESPACE