From 7d969716f251c0afff9f2278492d7904c8df4947 Mon Sep 17 00:00:00 2001 From: Julian Storer Date: Tue, 19 Apr 2011 15:52:10 +0100 Subject: [PATCH] Fix for return key and multi-touch handling on iOS. Added CoreText typeface support for newer OSX and iOS versions. Experimental support for latency correction in the AudioProcessorGraph. File extension fix for directories. Experimental support for openGL threading. --- juce_amalgamated.cpp | 1032 +++++++++++++---- juce_amalgamated.h | 88 +- .../processors/juce_AudioProcessorGraph.cpp | 126 +- src/core/juce_StandardHeader.h | 3 +- .../special/juce_OpenGLComponent.cpp | 103 +- .../components/special/juce_OpenGLComponent.h | 42 +- src/io/files/juce_File.cpp | 13 +- .../mac/juce_ios_UIViewComponentPeer.mm | 13 +- src/native/mac/juce_mac_Fonts.mm | 243 +++- .../mac/juce_mac_NSViewComponentPeer.mm | 143 +-- src/threads/juce_WaitableEvent.h | 11 +- 11 files changed, 1432 insertions(+), 385 deletions(-) diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 6da712d60b..d05cf60d2b 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -8096,17 +8096,12 @@ const File File::getNonexistentSibling (const bool putNumbersInBrackets) const const String File::getFileExtension() const { - String ext; + const int indexOfDot = fullPath.lastIndexOfChar ('.'); - if (! isDirectory()) - { - const int indexOfDot = fullPath.lastIndexOfChar ('.'); - - if (indexOfDot > fullPath.lastIndexOfChar (separator)) - ext = fullPath.substring (indexOfDot); - } + if (indexOfDot > fullPath.lastIndexOfChar (separator)) + return fullPath.substring (indexOfDot); - return ext; + return String::empty; } bool File::hasFileExtension (const String& possibleSuffix) const @@ -36650,6 +36645,39 @@ private: JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp); }; +class DelayChannelOp : public AudioGraphRenderingOp +{ +public: + DelayChannelOp (const int channel_, const int numSamplesDelay_) + : channel (channel_), + bufferSize (numSamplesDelay_ + 1), + readIndex (0), writeIndex (numSamplesDelay_) + { + buffer.calloc (bufferSize); + } + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) + { + float* data = sharedBufferChans.getSampleData (channel, 0); + + for (int i = numSamples; --i >= 0;) + { + buffer [writeIndex] = *data; + *data++ = buffer [readIndex]; + + if (++readIndex >= bufferSize) readIndex = 0; + if (++writeIndex >= bufferSize) writeIndex = 0; + } + } + +private: + HeapBlock buffer; + const int channel, bufferSize; + int readIndex, writeIndex; + + JUCE_DECLARE_NON_COPYABLE (DelayChannelOp); +}; + class ProcessBufferOp : public AudioGraphRenderingOp { public: @@ -36702,7 +36730,8 @@ public: const Array& orderedNodes_, Array& renderingOps) : graph (graph_), - orderedNodes (orderedNodes_) + orderedNodes (orderedNodes_), + totalLatency (0) { nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros channels.add (0); @@ -36716,6 +36745,8 @@ public: markAnyUnusedBuffersAsFree (i); } + + graph.setLatencySamples (totalLatency); } int getNumBuffersNeeded() const { return nodeIds.size(); } @@ -36732,6 +36763,42 @@ private: static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } + Array nodeDelayIDs; + Array nodeDelays; + int totalLatency; + + int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } + + void setNodeDelay (const uint32 nodeID, const int latency) + { + const int index = nodeDelayIDs.indexOf (nodeID); + + if (index >= 0) + { + nodeDelays.set (index, latency); + } + else + { + nodeDelayIDs.add (nodeID); + nodeDelays.add (latency); + } + } + + int getInputLatencyForNode (const uint32 nodeID) const + { + int maxLatency = 0; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == nodeID) + maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); + } + + return maxLatency; + } + void createRenderingOpsForNode (AudioProcessorGraph::Node* const node, Array& renderingOps, const int ourRenderingIndex) @@ -36743,6 +36810,8 @@ private: Array audioChannelsToUse; int midiBufferToUse = -1; + int maxLatency = getInputLatencyForNode (node->nodeId); + for (int inputChan = 0; inputChan < numIns; ++inputChan) { // get a list of all the inputs to this node @@ -36804,6 +36873,11 @@ private: bufIndex = newFreeBuffer; } + + const int nodeDelay = getNodeDelay (srcNode); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } else { @@ -36826,6 +36900,11 @@ private: // we've found one of our input chans that can be re-used.. reusableInputIndex = i; bufIndex = sourceBufIndex; + + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); + break; } } @@ -36849,6 +36928,10 @@ private: } reusableInputIndex = 0; + const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } for (int j = 0; j < sourceNodes.size(); ++j) @@ -36858,7 +36941,30 @@ private: const int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) - renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + { + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); + + if (nodeDelay < maxLatency) + { + if (! isBufferNeededLater (ourRenderingIndex, inputChan, + sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j))) + { + renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); + } + else // buffer is reused elsewhere, can't be delayed + { + const int bufferToDelay = getFreeBuffer (false); + renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); + renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); + renderingOps.add (new AddChannelOp (bufferToDelay, bufIndex)); + } + } + else + { + renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + } + } } } } @@ -36920,8 +37026,8 @@ private: } else { - // probably a feedback loop, so just use an empty one.. - midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + // probably a feedback loop, so just use an empty one.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi } } else @@ -36979,6 +37085,11 @@ private: markBufferAsContaining (midiBufferToUse, node->nodeId, AudioProcessorGraph::midiChannelIndex); + setNodeDelay (node->nodeId, maxLatency + node->getProcessor()->getLatencySamples()); + + if (numOuts == 0) + totalLatency = maxLatency; + renderingOps.add (new ProcessBufferOp (node, audioChannelsToUse, totalChans, midiBufferToUse)); } @@ -76774,6 +76885,37 @@ OpenGLContext* OpenGLContext::getCurrentContext() return nullptr; } +class OpenGLComponent::OpenGLComponentRenderThread : public Thread, + public AsyncUpdater +{ +public: + + OpenGLComponentRenderThread (OpenGLComponent& owner_) + : Thread ("OpenGL Render"), + owner (owner_) + { + } + + void run() + { + // Context will get created and callback triggered on first render + while (owner.renderAndSwapBuffers() && ! threadShouldExit()) + owner.waitAfterSwapping(); + + triggerAsyncUpdate(); + } + + void handleAsyncUpdate() + { + owner.stopRendering(); + } + +private: + OpenGLComponent& owner; + + JUCE_DECLARE_NON_COPYABLE (OpenGLComponentRenderThread); +}; + class OpenGLComponent::OpenGLComponentWatcher : public ComponentMovementWatcher { public: @@ -76792,7 +76934,7 @@ public: void componentPeerChanged() { const ScopedLock sl (owner->getContextLock()); - owner->deleteContext(); + owner->stopRendering(); } void componentVisibilityChanged() @@ -76800,7 +76942,7 @@ public: if (! owner->isShowing()) { const ScopedLock sl (owner->getContextLock()); - owner->deleteContext(); + owner->stopRendering(); } } @@ -76924,17 +77066,38 @@ void OpenGLComponent::swapBuffers() context->swapBuffers(); } +void OpenGLComponent::setUsingDedicatedThread (bool useDedicatedThread) noexcept +{ + useThread = useDedicatedThread; +} + void OpenGLComponent::paint (Graphics&) { - if (renderAndSwapBuffers()) + if (useThread) { - ComponentPeer* const peer = getPeer(); + if (renderThread == nullptr) + renderThread = new OpenGLComponentRenderThread (*this); - if (peer != nullptr) - { - const Point topLeft (getScreenPosition() - peer->getScreenPosition()); - peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight()); - } + if (! renderThread->isThreadRunning()) + renderThread->startThread (6); + + // fall-through and update the masking region + } + else + { + if (renderThread != nullptr && renderThread->isThreadRunning()) + renderThread->stopThread (5000); + + if (! renderAndSwapBuffers()) + return; + } + + ComponentPeer* const peer = getPeer(); + + if (peer != nullptr) + { + const Point topLeft (getScreenPosition() - peer->getScreenPosition()); + peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight()); } } @@ -76957,6 +77120,35 @@ bool OpenGLComponent::renderAndSwapBuffers() return true; } +void OpenGLComponent::waitAfterSwapping() +{ + jassert (renderThread != nullptr && Thread::getCurrentThread() == renderThread); + + Thread::sleep (20); +} + +bool OpenGLComponent::stopRendering() +{ + const ScopedLock sl (contextLock); + + if (! makeCurrentContextActive()) + return false; + + releaseOpenGLContext(); // callback to allow for shutdown + + if (renderThread != nullptr && Thread::getCurrentThread() == renderThread) + { + // make the context inactive - if we're on a thread, this will release the context, + // so the main thread can take it and do shutdown + + makeCurrentContextInactive(); + } + else if (context != nullptr) + context->deleteContext(); + + return true; +} + void OpenGLComponent::internalRepaint (int x, int y, int w, int h) { Component::internalRepaint (x, y, w, h); @@ -243014,7 +243206,7 @@ void Logger::outputDebugString (const String& text) static int64 hiResTicksPerSecond; static double hiResTicksScaleFactor; -#if JUCE_USE_INTRINSICS +#if JUCE_USE_INTRINSICS || JUCE_64BIT // CPU info functions using intrinsics... @@ -243042,9 +243234,6 @@ static void juce_getCpuVendor (char* const v) { int vendor[4] = { 0 }; - #if JUCE_64BIT - /// xxx todo - #else #ifndef __MINGW32__ __try #endif @@ -243069,7 +243258,6 @@ static void juce_getCpuVendor (char* const v) *v = 0; } #endif - #endif memcpy (v, vendor, 16); } @@ -268223,7 +268411,7 @@ private: if (nextMessage == nullptr) return false; - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL MessageManager::getInstance()->deliverMessage (nextMessage); return true; } @@ -268317,7 +268505,7 @@ CFStringRef PlatformUtilities::juceStringToCFString (const String& s) const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) { #if JUCE_IOS - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL return nsStringToJuce ([juceStringToNS (s) precomposedStringWithCanonicalMapping]); #else UnicodeMapping map; @@ -268637,12 +268825,12 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd const String& bodyText, const StringArray& filesToAttach) { -#if JUCE_IOS + #if JUCE_IOS //xxx probably need to use MFMailComposeViewController jassertfalse; return false; -#else - const ScopedAutoReleasePool pool; + #else + JUCE_AUTORELEASEPOOL String script; script << "tell application \"Mail\"\r\n" @@ -268670,14 +268858,13 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd script << "end tell\r\n" "end tell\r\n"; - NSAppleScript* s = [[NSAppleScript alloc] - initWithSource: juceStringToNS (script)]; + NSAppleScript* s = [[NSAppleScript alloc] initWithSource: juceStringToNS (script)]; NSDictionary* error = nil; const bool ok = [s executeAndReturnError: &error] != nil; [s release]; return ok; -#endif + #endif } END_JUCE_NAMESPACE @@ -268735,7 +268922,7 @@ public: while (! threadShouldExit()) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; } } @@ -269253,11 +269440,11 @@ int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOut bool Process::isForegroundProcess() { -#if JUCE_MAC + #if JUCE_MAC return [NSApp isActive]; -#else + #else return true; // xxx change this if more than one app is ever possible on the iPhone! -#endif + #endif } void Process::raisePrivilege() @@ -270119,7 +270306,7 @@ void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) bool File::copyInternal (const File& dest) const { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSFileManager* fm = [NSFileManager defaultManager]; return [fm fileExistsAtPath: juceStringToNS (fullPath)] @@ -270160,7 +270347,7 @@ namespace FileHelpers bool isHiddenFile (const String& path) { #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSNumber* hidden = nil; NSError* err = nil; @@ -270225,10 +270412,10 @@ bool File::isOnHardDisk() const bool File::isOnRemovableDrive() const { - #if JUCE_IOS + #if JUCE_IOS return false; // xxx is this possible? - #else - const ScopedAutoReleasePool pool; + #else + JUCE_AUTORELEASEPOOL BOOL removable = false; [[NSWorkspace sharedWorkspace] @@ -270240,7 +270427,7 @@ bool File::isOnRemovableDrive() const type: nil]; return removable; - #endif + #endif } bool File::isHidden() const @@ -270252,8 +270439,7 @@ const char* juce_Argv0 = nullptr; // referenced from juce_Application.cpp const File File::getSpecialLocation (const SpecialLocationType type) { - const ScopedAutoReleasePool pool; - + JUCE_AUTORELEASEPOOL String resultPath; switch (type) @@ -270334,7 +270520,7 @@ const File File::getSpecialLocation (const SpecialLocationType type) const String File::getVersion() const { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL String result; NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]; @@ -270379,7 +270565,7 @@ bool File::moveToTrash() const #if JUCE_IOS return deleteFile(); //xxx is there a trashcan on the iPhone? #else - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSString* p = juceStringToNS (getFullPathName()); @@ -270400,7 +270586,7 @@ public: wildCard (wildCard_), enumerator (nil) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL enumerator = [[[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (directory.getFullPathName())] retain]; } @@ -270414,7 +270600,7 @@ public: bool* const isDir, bool* const isHidden, int64* const fileSize, Time* const modTime, Time* const creationTime, bool* const isReadOnly) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL const char* wildcardUTF8 = nullptr; for (;;) @@ -270470,7 +270656,7 @@ bool PlatformUtilities::openDocument (const String& fileName, const String& para #if JUCE_IOS return [[UIApplication sharedApplication] openURL: [NSURL fileURLWithPath: juceStringToNS (fileName)]]; #else - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL if (parameters.isEmpty()) { @@ -270533,26 +270719,26 @@ const String PlatformUtilities::makePathFromFSRef (FSRef* file) OSType PlatformUtilities::getTypeOfFile (const String& filename) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL - #if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5) + #if JUCE_IOS || (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5) NSDictionary* fileDict = [[NSFileManager defaultManager] attributesOfItemAtPath: juceStringToNS (filename) error: nil]; - #else + #else // (the cast here avoids a deprecation warning) NSDictionary* fileDict = [((id) [NSFileManager defaultManager]) fileAttributesAtPath: juceStringToNS (filename) traverseLink: NO]; - #endif + #endif return [fileDict fileHFSTypeCode]; } bool PlatformUtilities::isBundle (const String& filename) { - #if JUCE_IOS + #if JUCE_IOS return false; // xxx can't find a sensible way to do this without trying to open the bundle.. - #else - const ScopedAutoReleasePool pool; + #else + JUCE_AUTORELEASEPOOL return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; - #endif + #endif } #endif @@ -270668,7 +270854,7 @@ public: int getResult() { jassert (callback == nullptr); - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL while (! alert.hidden && alert.superview != nil) [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; @@ -270714,7 +270900,7 @@ void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType const String& title, const String& message, Component* associatedComponent) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL iOSMessageBox mb (title, message, @"OK", nil, nil, 0, false); (void) mb.getResult(); } @@ -270723,7 +270909,7 @@ void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIcon const String& title, const String& message, Component* associatedComponent) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL new iOSMessageBox (title, message, @"OK", nil, nil, 0, true); } @@ -270847,7 +271033,7 @@ public: int getResult() const { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSInteger r = getRawResult(); return r == NSAlertDefaultReturn ? 1 : (r == NSAlertOtherReturn ? 2 : 0); } @@ -270948,7 +271134,7 @@ bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& fi return false; } - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSView* view = (NSView*) sourceComp->getWindowHandle(); @@ -270995,7 +271181,7 @@ bool Desktop::canUseSemiTransparentWindows() noexcept const Point MouseInputSource::getCurrentMousePosition() { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL const NSPoint p ([NSEvent mouseLocation]); return Point (roundToInt (p.x), roundToInt ([[[NSScreen screens] objectAtIndex: 0] frame].size.height - p.y)); } @@ -271114,7 +271300,7 @@ juce_ImplementSingleton_SingleThreaded (DisplaySettingsChangeCallback); void Desktop::getCurrentMonitorPositions (Array >& monitorCoords, const bool clipToWorkArea) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL DisplaySettingsChangeCallback::getInstance(); @@ -271187,6 +271373,241 @@ JUCE_API bool JUCE_CALLTYPE Process::isRunningUnderDebugger() // compiled on its own). #if JUCE_INCLUDED_FILE +#if (JUCE_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) \ + || (JUCE_IOS && defined (__IPHONE_3_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2) + #define JUCE_CORETEXT_AVAILABLE 1 +#endif + +#if JUCE_CORETEXT_AVAILABLE + +class MacTypeface : public Typeface +{ +public: + + MacTypeface (const Font& font) + : Typeface (font.getTypefaceName()), + fontRef (nullptr), + fontHeightToCGSizeFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + ctFontRef (nullptr), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + JUCE_AUTORELEASEPOOL + CFStringRef name = PlatformUtilities::juceStringToCFString (font.getTypefaceName()); + ctFontRef = CTFontCreateWithName (name, 1024, nullptr); + CFRelease (name); + + if (ctFontRef != nullptr) + { + bool needsItalicTransform = false; + + if (font.isItalic()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontItalicTrait, kCTFontItalicTrait); + + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + else + { + needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform.. + } + } + + if (font.isBold()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontBoldTrait, kCTFontBoldTrait); + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + } + + ascent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float totalSize = ascent + std::abs ((float) CTFontGetDescent (ctFontRef)); + ascent /= totalSize; + + pathTransform = AffineTransform::identity.scale (1.0f / totalSize, 1.0f / totalSize); + + if (needsItalicTransform) + { + pathTransform = pathTransform.sheared (-0.15f, 0.0f); + renderingTransform.c = 0.15f; + } + + fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); + + const int totalHeight = abs (CGFontGetAscent (fontRef)) + abs (CGFontGetDescent (fontRef)); + const float ctTotalHeight = abs (CTFontGetAscent (ctFontRef)) + abs (CTFontGetDescent (ctFontRef)); + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + } + + ~MacTypeface() + { + if (attributedStringAtts != nullptr) + CFRelease (attributedStringAtts); + + if (fontRef != nullptr) + CGFontRelease (fontRef); + + if (ctFontRef != nullptr) + CFRelease (ctFontRef); + } + + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + + float getStringWidth (const String& text) + { + float x = 0; + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + + for (int j = 0; j < length; ++j) + x += (float) advances[j].width; + } + + CFRelease (line); + CFRelease (attribString); + + x *= unitsToHeightScaleFactor; + } + + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + xOffsets.add (0); + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + float x = 0; + + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + HeapBlock glyphs (length); + CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphs); + + for (int j = 0; j < length; ++j) + { + x += (float) advances[j].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphs[j]); + } + } + + CFRelease (line); + CFRelease (attribString); + } + } + + EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) + { + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + + return nullptr; + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) + { + jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty + + CGPathRef pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform); + CGPathApply (pathRef, &path, pathApplier); + CFRelease (pathRef); + + if (! pathTransform.isIdentity()) + path.applyTransform (pathTransform); + + return true; + } + + CGFontRef fontRef; + + float fontHeightToCGSizeFactor; + CGAffineTransform renderingTransform; + +private: + CTFontRef ctFontRef; + CFDictionaryRef attributedStringAtts; + float ascent, unitsToHeightScaleFactor; + AffineTransform pathTransform; + + static void pathApplier (void* info, const CGPathElement* const element) + { + Path& path = *static_cast (info); + const CGPoint* const p = element->points; + + switch (element->type) + { + case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y); break; + case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y, + (float) p[2].x, (float) -p[2].y); break; + case kCGPathElementCloseSubpath: path.closeSubPath(); break; + default: jassertfalse; break; + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); +}; + +#else + +// The stuff that follows is a mash-up that supports pre-OSX 10.5 and pre-iOS 3.2 APIs. +// (Hopefully all of this can be ditched at some point in the future). + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #define SUPPORT_10_4_FONTS 1 #define NEW_CGFONT_FUNCTIONS_UNAVAILABLE (CGFontCreateWithFontName == 0) @@ -271209,7 +271630,7 @@ public: MacTypeface (const Font& font) : Typeface (font.getTypefaceName()) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL renderingTransform = CGAffineTransformIdentity; bool needsItalicTransform = false; @@ -271288,7 +271709,7 @@ public: renderingTransform.c = 0.15f; } -#if SUPPORT_ONLY_10_4_FONTS + #if SUPPORT_ONLY_10_4_FONTS ATSFontRef atsFont = ATSFontFindFromName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault); if (atsFont == 0) @@ -271299,7 +271720,7 @@ public: const float totalHeight = std::abs ([nsFont ascender]) + std::abs ([nsFont descender]); unitsToHeightScaleFactor = 1.0f / totalHeight; fontHeightToCGSizeFactor = 1024.0f / totalHeight; -#else + #else #if SUPPORT_10_4_FONTS if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) { @@ -271323,7 +271744,7 @@ public: unitsToHeightScaleFactor = 1.0f / totalHeight; fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight; } -#endif + #endif #endif } @@ -271469,7 +271890,7 @@ public: // we might need to apply a transform to the path, so it mustn't have anything else in it jassert (path.isEmpty()); - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSBezierPath* bez = [NSBezierPath bezierPath]; [bez moveToPoint: NSMakePoint (0, 0)]; @@ -271640,6 +272061,8 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); }; +#endif + const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new MacTypeface (font); @@ -271649,7 +272072,7 @@ const StringArray Font::findAllTypefaceNames() { StringArray names; - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL #if JUCE_IOS NSArray* fonts = [UIFont familyNames]; @@ -271771,7 +272194,7 @@ public: #if JUCE_MAC static NSImage* createNSImage (const Image& image) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSImage* im = [[NSImage alloc] init]; [im setSize: NSMakeSize (image.getWidth(), image.getHeight())]; @@ -273263,13 +273686,16 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons currentTouches.add (touch); } - if (isDown) + if ([touch phase] == UITouchPhaseBegan + || [touch phase] == UITouchPhaseStationary + || [touch phase] == UITouchPhaseMoved) { currentModifiers = currentModifiers.withoutMouseButtons(); handleMouseEvent (touchIndex, pos, currentModifiers, time); currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); } - else if (isUp) + else if ([touch phase] == UITouchPhaseEnded + || [touch phase] == UITouchPhaseCancelled) { currentModifiers = currentModifiers.withoutMouseButtons(); currentTouches.remove (touchIndex); @@ -273363,7 +273789,11 @@ BOOL UIViewComponentPeer::textViewReplaceCharacters (const Range& range, co if (currentSelection.isEmpty()) target->setHighlightedRegion (currentSelection.withStart (currentSelection.getStart() - 1)); - target->insertTextAtCaret (text); + if (text == "\r" || text == "\n" || text == "\r\n") + handleKeyPress (KeyPress::returnKey, text[0]); + else + target->insertTextAtCaret (text); + updateHiddenTextContent (target); } @@ -273503,7 +273933,7 @@ Desktop::DisplayOrientation Desktop::getCurrentOrientation() const void Desktop::getCurrentMonitorPositions (Array >& monitorCoords, const bool clipToWorkArea) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL monitorCoords.clear(); CGRect r = clipToWorkArea ? [[UIScreen mainScreen] applicationFrame] @@ -273631,7 +274061,7 @@ void MessageManager::stopDispatchLoop() bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL jassert (isThisTheMessageThread()); // must only be called by the message thread uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor; @@ -273639,7 +274069,7 @@ bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) while (! quitMessagePosted) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: endDate]; @@ -273709,7 +274139,7 @@ void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* call // call your function.. jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager()); - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL CallbackMessagePayload cmp; cmp.function = callback; @@ -273798,7 +274228,7 @@ void FileChooser::showPlatformDialog (Array& results, bool selectMultipleFiles, FilePreviewComponent* /*extraInfoComponent*/) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL StringArray* filters = new StringArray(); filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String::empty); @@ -273874,7 +274304,7 @@ void FileChooser::showPlatformDialog (Array& results, bool selectMultipleFiles, FilePreviewComponent* extraInfoComponent) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL jassertfalse; //xxx to do } @@ -274377,7 +274807,7 @@ private: OpenGLContext* OpenGLComponent::createContext() { - ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL UIViewComponentPeer* peer = dynamic_cast (getPeer()); if (peer != nullptr) @@ -274445,7 +274875,7 @@ void* MouseCursor::createMouseCursorFromImage (const Image& image, int hotspotX, void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSCursor* c = nil; switch (type) @@ -275950,6 +276380,241 @@ MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) { re // compiled on its own). #if JUCE_INCLUDED_FILE +#if (JUCE_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) \ + || (JUCE_IOS && defined (__IPHONE_3_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2) + #define JUCE_CORETEXT_AVAILABLE 1 +#endif + +#if JUCE_CORETEXT_AVAILABLE + +class MacTypeface : public Typeface +{ +public: + + MacTypeface (const Font& font) + : Typeface (font.getTypefaceName()), + fontRef (nullptr), + fontHeightToCGSizeFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + ctFontRef (nullptr), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + JUCE_AUTORELEASEPOOL + CFStringRef name = PlatformUtilities::juceStringToCFString (font.getTypefaceName()); + ctFontRef = CTFontCreateWithName (name, 1024, nullptr); + CFRelease (name); + + if (ctFontRef != nullptr) + { + bool needsItalicTransform = false; + + if (font.isItalic()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontItalicTrait, kCTFontItalicTrait); + + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + else + { + needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform.. + } + } + + if (font.isBold()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontBoldTrait, kCTFontBoldTrait); + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + } + + ascent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float totalSize = ascent + std::abs ((float) CTFontGetDescent (ctFontRef)); + ascent /= totalSize; + + pathTransform = AffineTransform::identity.scale (1.0f / totalSize, 1.0f / totalSize); + + if (needsItalicTransform) + { + pathTransform = pathTransform.sheared (-0.15f, 0.0f); + renderingTransform.c = 0.15f; + } + + fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); + + const int totalHeight = abs (CGFontGetAscent (fontRef)) + abs (CGFontGetDescent (fontRef)); + const float ctTotalHeight = abs (CTFontGetAscent (ctFontRef)) + abs (CTFontGetDescent (ctFontRef)); + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + } + + ~MacTypeface() + { + if (attributedStringAtts != nullptr) + CFRelease (attributedStringAtts); + + if (fontRef != nullptr) + CGFontRelease (fontRef); + + if (ctFontRef != nullptr) + CFRelease (ctFontRef); + } + + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + + float getStringWidth (const String& text) + { + float x = 0; + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + + for (int j = 0; j < length; ++j) + x += (float) advances[j].width; + } + + CFRelease (line); + CFRelease (attribString); + + x *= unitsToHeightScaleFactor; + } + + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + xOffsets.add (0); + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + float x = 0; + + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + HeapBlock glyphs (length); + CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphs); + + for (int j = 0; j < length; ++j) + { + x += (float) advances[j].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphs[j]); + } + } + + CFRelease (line); + CFRelease (attribString); + } + } + + EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) + { + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + + return nullptr; + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) + { + jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty + + CGPathRef pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform); + CGPathApply (pathRef, &path, pathApplier); + CFRelease (pathRef); + + if (! pathTransform.isIdentity()) + path.applyTransform (pathTransform); + + return true; + } + + CGFontRef fontRef; + + float fontHeightToCGSizeFactor; + CGAffineTransform renderingTransform; + +private: + CTFontRef ctFontRef; + CFDictionaryRef attributedStringAtts; + float ascent, unitsToHeightScaleFactor; + AffineTransform pathTransform; + + static void pathApplier (void* info, const CGPathElement* const element) + { + Path& path = *static_cast (info); + const CGPoint* const p = element->points; + + switch (element->type) + { + case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y); break; + case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y, + (float) p[2].x, (float) -p[2].y); break; + case kCGPathElementCloseSubpath: path.closeSubPath(); break; + default: jassertfalse; break; + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); +}; + +#else + +// The stuff that follows is a mash-up that supports pre-OSX 10.5 and pre-iOS 3.2 APIs. +// (Hopefully all of this can be ditched at some point in the future). + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #define SUPPORT_10_4_FONTS 1 #define NEW_CGFONT_FUNCTIONS_UNAVAILABLE (CGFontCreateWithFontName == 0) @@ -275972,7 +276637,7 @@ public: MacTypeface (const Font& font) : Typeface (font.getTypefaceName()) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL renderingTransform = CGAffineTransformIdentity; bool needsItalicTransform = false; @@ -276051,7 +276716,7 @@ public: renderingTransform.c = 0.15f; } -#if SUPPORT_ONLY_10_4_FONTS + #if SUPPORT_ONLY_10_4_FONTS ATSFontRef atsFont = ATSFontFindFromName ((CFStringRef) [nsFont fontName], kATSOptionFlagsDefault); if (atsFont == 0) @@ -276062,7 +276727,7 @@ public: const float totalHeight = std::abs ([nsFont ascender]) + std::abs ([nsFont descender]); unitsToHeightScaleFactor = 1.0f / totalHeight; fontHeightToCGSizeFactor = 1024.0f / totalHeight; -#else + #else #if SUPPORT_10_4_FONTS if (NEW_CGFONT_FUNCTIONS_UNAVAILABLE) { @@ -276086,7 +276751,7 @@ public: unitsToHeightScaleFactor = 1.0f / totalHeight; fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight; } -#endif + #endif #endif } @@ -276232,7 +276897,7 @@ public: // we might need to apply a transform to the path, so it mustn't have anything else in it jassert (path.isEmpty()); - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSBezierPath* bez = [NSBezierPath bezierPath]; [bez moveToPoint: NSMakePoint (0, 0)]; @@ -276403,6 +277068,8 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); }; +#endif + const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new MacTypeface (font); @@ -276412,7 +277079,7 @@ const StringArray Font::findAllTypefaceNames() { StringArray names; - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL #if JUCE_IOS NSArray* fonts = [UIFont familyNames]; @@ -276534,7 +277201,7 @@ public: #if JUCE_MAC static NSImage* createNSImage (const Image& image) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSImage* im = [[NSImage alloc] init]; [im setSize: NSMakeSize (image.getWidth(), image.getHeight())]; @@ -277491,9 +278158,9 @@ public: virtual bool redirectKeyDown (NSEvent* ev); virtual bool redirectKeyUp (NSEvent* ev); virtual void redirectModKeyChange (NSEvent* ev); -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 virtual bool redirectPerformKeyEquivalent (NSEvent* ev); -#endif + #endif virtual BOOL sendDragCallback (int type, id sender); @@ -277665,71 +278332,19 @@ END_JUCE_NAMESPACE waitUntilDone: NO]; } -- (void) asyncMouseUp: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseUp (ev); -} - -- (void) mouseDragged: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseDrag (ev); -} - -- (void) mouseMoved: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseMove (ev); -} - -- (void) mouseEntered: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseEnter (ev); -} - -- (void) mouseExited: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseExit (ev); -} - -- (void) rightMouseDown: (NSEvent*) ev -{ - [self mouseDown: ev]; -} - -- (void) rightMouseDragged: (NSEvent*) ev -{ - [self mouseDragged: ev]; -} - -- (void) rightMouseUp: (NSEvent*) ev -{ - [self mouseUp: ev]; -} - -- (void) otherMouseDown: (NSEvent*) ev -{ - [self mouseDown: ev]; -} - -- (void) otherMouseDragged: (NSEvent*) ev -{ - [self mouseDragged: ev]; -} - -- (void) otherMouseUp: (NSEvent*) ev -{ - [self mouseUp: ev]; -} +- (void) asyncMouseUp: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseUp (ev); } +- (void) mouseDragged: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseDrag (ev); } +- (void) mouseMoved: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseMove (ev); } +- (void) mouseEntered: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseEnter (ev); } +- (void) mouseExited: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseExit (ev); } +- (void) scrollWheel: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseWheel (ev); } -- (void) scrollWheel: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseWheel (ev); -} +- (void) rightMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } +- (void) rightMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } +- (void) rightMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } +- (void) otherMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } +- (void) otherMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } +- (void) otherMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } - (BOOL) acceptsFirstMouse: (NSEvent*) ev { @@ -277925,26 +278540,10 @@ END_JUCE_NAMESPACE } #endif -- (BOOL) becomeFirstResponder -{ - if (owner != nullptr) - owner->viewFocusGain(); - - return true; -} - -- (BOOL) resignFirstResponder -{ - if (owner != nullptr) - owner->viewFocusLoss(); +- (BOOL) becomeFirstResponder { if (owner != nullptr) owner->viewFocusGain(); return YES; } +- (BOOL) resignFirstResponder { if (owner != nullptr) owner->viewFocusLoss(); return YES; } - return true; -} - -- (BOOL) acceptsFirstResponder -{ - return owner != nullptr && owner->canBecomeKeyWindow(); -} +- (BOOL) acceptsFirstResponder { return owner != nullptr && owner->canBecomeKeyWindow(); } - (NSArray*) getSupportedDragTypes { @@ -278143,14 +278742,14 @@ NSViewComponentPeer::NSViewComponentPeer (Component* const component_, isSharedWindow (viewToAttachTo != nil), fullScreen (false), insideDrawRect (false), -#if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING usingCoreGraphics (true), -#else + #else usingCoreGraphics (false), -#endif + #endif recursiveToFrontCall (false) { - NSRect r = NSMakeRect (0, 0, (float) component->getWidth(),(float) component->getHeight()); + NSRect r = NSMakeRect (0, 0, (float) component->getWidth(), (float) component->getHeight()); view = [[JuceNSView alloc] initWithOwner: this withFrame: r]; [view setPostsFrameChangedNotifications: YES]; @@ -278256,7 +278855,7 @@ void NSViewComponentPeer::setVisible (bool shouldBeVisible) void NSViewComponentPeer::setTitle (const String& title) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL if (! isSharedWindow) [window setTitle: juceStringToNS (title)]; @@ -278349,12 +278948,12 @@ NSRect NSViewComponentPeer::constrainRect (NSRect r) Rectangle pos (convertToRectInt (r)); Rectangle original (convertToRectInt (current)); - #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 if ([window inLiveResize]) - #else + #else if ([window respondsToSelector: @selector (inLiveResize)] && [window performSelector: @selector (inLiveResize)]) - #endif + #endif { constrainer->checkBounds (pos, original, Desktop::getInstance().getAllMonitorDisplayAreas().getBounds(), @@ -278387,9 +278986,9 @@ void NSViewComponentPeer::setAlpha (float newAlpha) } else { - #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 + #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 [view setAlphaValue: (CGFloat) newAlpha]; - #else + #else if ([view respondsToSelector: @selector (setAlphaValue:)]) { // PITA dynamic invocation for 10.4 builds.. @@ -278492,22 +279091,17 @@ const BorderSize NSViewComponentPeer::getFrameSize() const bool NSViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop) { if (! isSharedWindow) - { [window setLevel: alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel]; - } - return true; } void NSViewComponentPeer::toFront (bool makeActiveWindow) { if (isSharedWindow) - { [[view superview] addSubview: view positioned: NSWindowAbove relativeTo: nil]; - } if (window != nil && component->isVisible()) { @@ -278864,7 +279458,7 @@ void NSViewComponentPeer::drawRect (NSRect r) if (! component->isOpaque()) CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); - #if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics) { CoreGraphicsContext context (cg, (float) [view frame].size.height); @@ -278874,7 +279468,7 @@ void NSViewComponentPeer::drawRect (NSRect r) insideDrawRect = false; } else - #endif + #endif { Image temp (getComponent()->isOpaque() ? Image::RGB : Image::ARGB, (int) (r.size.width + 0.5f), @@ -278920,9 +279514,9 @@ const StringArray NSViewComponentPeer::getAvailableRenderingEngines() { StringArray s (ComponentPeer::getAvailableRenderingEngines()); - #if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING s.add ("CoreGraphics Renderer"); - #endif + #endif return s; } @@ -278934,13 +279528,13 @@ int NSViewComponentPeer::getCurrentRenderingEngine() const void NSViewComponentPeer::setCurrentRenderingEngine (int index) { -#if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics != (index > 0)) { usingCoreGraphics = index > 0; [view setNeedsDisplay: true]; } -#endif + #endif } bool NSViewComponentPeer::canBecomeKeyWindow() @@ -278975,7 +279569,7 @@ void Desktop::createMouseInputSources() void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars) { - #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 if (enableOrDisable) { [NSApp setPresentationOptions: (allowMenusAndBars ? (NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar) @@ -278986,7 +279580,7 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis { [NSApp setPresentationOptions: NSApplicationPresentationDefault]; } - #else + #else if (enableOrDisable) { SetSystemUIMode (kUIModeAllSuppressed, allowMenusAndBars ? kUIOptionAutoShowMenuBar : 0); @@ -278996,7 +279590,7 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis { SetSystemUIMode (kUIModeNormal, 0); } - #endif + #endif } void NSViewComponentPeer::repaint (const Rectangle& area) @@ -279043,7 +279637,7 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) const Image juce_createIconForFile (const File& file) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSImage* image = [[NSWorkspace sharedWorkspace] iconForFile: juceStringToNS (file.getFullPathName())]; @@ -279161,7 +279755,7 @@ void* MouseCursor::createMouseCursorFromImage (const Image& image, int hotspotX, void* MouseCursor::createStandardMouseCursor (MouseCursor::StandardCursorType type) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSCursor* c = nil; switch (type) @@ -280114,7 +280708,7 @@ private: OpenGLContext* OpenGLComponent::createContext() { - ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL UIViewComponentPeer* peer = dynamic_cast (getPeer()); if (peer != nullptr) @@ -280612,7 +281206,7 @@ namespace MainMenuHelpers if (JUCEApplication::getInstance() != nullptr) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"]; NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""]; @@ -280636,7 +281230,7 @@ void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel, { if (getMacMainMenu() != newMenuBarModel) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL if (newMenuBarModel == nullptr) { @@ -280744,7 +281338,7 @@ void FileChooser::showPlatformDialog (Array& results, bool selectMultipleFiles, FilePreviewComponent* /*extraInfoComponent*/) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL StringArray* filters = new StringArray(); filters->addTokens (filter.replaceCharacters (",:", ";;"), ";", String::empty); @@ -280820,7 +281414,7 @@ void FileChooser::showPlatformDialog (Array& results, bool selectMultipleFiles, FilePreviewComponent* extraInfoComponent) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL jassertfalse; //xxx to do } @@ -281699,7 +282293,7 @@ const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressLis void AudioCDReader::ejectDisk() { - const ScopedAutoReleasePool p; + JUCE_AUTORELEASEPOOL [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; } @@ -282215,7 +282809,7 @@ void MessageManager::runDispatchLoop() { if (! quitMessagePosted) // check that the quit message wasn't already posted.. { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL // must only be called by the message thread! jassert (isThisTheMessageThread()); @@ -282344,7 +282938,7 @@ bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) while (! quitMessagePosted) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true); @@ -282417,7 +283011,7 @@ void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* call // call your function.. jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager()); - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL AppDelegateRedirector::CallbackMessagePayload cmp; cmp.function = callback; @@ -284619,7 +285213,7 @@ class QTCameraDeviceInteral public: QTCameraDeviceInteral (CameraDevice* owner, int index) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL session = [[QTCaptureSession alloc] init]; @@ -284790,7 +285384,7 @@ END_JUCE_NAMESPACE { if (internal->listeners.size() > 0) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL internal->callListeners ([CIImage imageWithCVImageBuffer: videoFrame], CVPixelBufferGetWidth (videoFrame), @@ -284804,11 +285398,11 @@ END_JUCE_NAMESPACE { const Time now (Time::getCurrentTime()); -#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 + #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5 NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: QTSampleBufferHostTimeAttribute]; -#else + #else NSNumber* hosttime = (NSNumber*) [sampleBuffer attributeForKey: @"hostTime"]; -#endif + #endif int64 presentationTime = (hosttime != nil) ? ((int64) AudioConvertHostTimeToNanos ([hosttime unsignedLongLongValue]) / 1000000 + 40) @@ -284835,7 +285429,7 @@ class QTCaptureViewerComp : public NSViewComponent public: QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal) { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL captureView = [[QTCaptureView alloc] init]; [captureView setCaptureSession: internal->session]; @@ -284948,7 +285542,7 @@ void CameraDevice::removeListener (Listener* listenerToRemove) const StringArray CameraDevice::getAvailableDevices() { - const ScopedAutoReleasePool pool; + JUCE_AUTORELEASEPOOL StringArray results; NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 95826beb53..c0b5d9eb58 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -73,7 +73,7 @@ namespace JuceDummyNamespace {} */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 76 +#define JUCE_BUILDNUMBER 77 /** Current Juce version number. @@ -5681,6 +5681,8 @@ private: #endif // __JUCE_LEAKEDOBJECTDETECTOR_JUCEHEADER__ /*** End of inlined file: juce_LeakedObjectDetector.h ***/ +#undef TYPE_BOOL // (stupidly-named CoreServices definition which interferes with other libraries). + END_JUCE_NAMESPACE #endif // __JUCE_STANDARDHEADER_JUCEHEADER__ @@ -17777,28 +17779,26 @@ private: JUCE_DECLARE_NON_COPYABLE (PlatformUtilities); }; -#if JUCE_MAC || JUCE_IOS +#if JUCE_MAC || JUCE_IOS || DOXYGEN -/** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. -*/ -class JUCE_API ScopedAutoReleasePool -{ -public: - ScopedAutoReleasePool(); - ~ScopedAutoReleasePool(); + /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. */ + class JUCE_API ScopedAutoReleasePool + { + public: + ScopedAutoReleasePool(); + ~ScopedAutoReleasePool(); -private: - void* pool; + private: + void* pool; - JUCE_DECLARE_NON_COPYABLE (ScopedAutoReleasePool); -}; + JUCE_DECLARE_NON_COPYABLE (ScopedAutoReleasePool); + }; -#define JUCE_AUTORELEASEPOOL const JUCE_NAMESPACE::ScopedAutoReleasePool pool; + /** A macro that can be used to easily declare a local ScopedAutoReleasePool object for RAII-based obj-C autoreleasing. */ + #define JUCE_AUTORELEASEPOOL const JUCE_NAMESPACE::ScopedAutoReleasePool JUCE_JOIN_MACRO (autoReleasePool_, __LINE__); #else - -#define JUCE_AUTORELEASEPOOL - + #define JUCE_AUTORELEASEPOOL #endif #if JUCE_LINUX @@ -22317,12 +22317,13 @@ public: If signal() is called when nothing is waiting, the next thread to call wait() will return immediately and reset the signal. - If the WaitableEvent is manual reset, all threads that are currently waiting on this - object will be woken. + If the WaitableEvent is manual reset, all current and future threads that wait upon this + object will be woken, until reset() is explicitly called. - If the WaitableEvent is automatic reset, and there are one or more threads waiting - on the object, then one of them will be woken up. If no threads are currently waiting, - then the next thread to call wait() will be woken up. + If the WaitableEvent is automatic reset, and one or more threads is waiting upon the object, + then one of them will be woken up. If no threads are currently waiting, then the next thread + to call wait() will be woken up. As soon as a thread is woken, the signal is automatically + reset. @see wait, reset */ @@ -63814,11 +63815,21 @@ public: /** Returns the context that this component is sharing with. @see shareWith */ - OpenGLContext* getShareContext() const noexcept { return contextToShareListsWith; } + OpenGLContext* getShareContext() const noexcept { return contextToShareListsWith; } /** Flips the openGL buffers over. */ void swapBuffers(); + /** Indicates whether the component should perform its rendering on a background thread. + By default, this is set to false, and the renderOpenGL() callback happens on the main + UI thread, in response to a repaint. If set to true, then the component will create + a background thread which it uses to repeatedly call renderOpenGL(). + */ + void setUsingDedicatedThread (bool useDedicatedThread) noexcept; + + /** Returns true if the component is performing the rendering on a background thread. */ + bool isUsingDedicatedThread() const noexcept { return useThread; } + /** This replaces the normal paint() callback - use it to draw your openGL stuff. When this is called, makeCurrentContextActive() will already have been called @@ -63848,6 +63859,17 @@ public: */ virtual void newOpenGLContextCreated() = 0; + /** This method is called when the component shuts down its OpenGL context. + + You can use this callback to delete textures and any other OpenGL objects you + created in the component's context. + + When this callback happens, the context will have been made current + using the makeCurrentContextActive() method, so there's no need to call it + again in your code. + */ + virtual void releaseOpenGLContext() {} + /** Returns the context that will draw into this component. This may return 0 if the component is currently invisible or hasn't currently @@ -63900,6 +63922,19 @@ public: */ virtual bool renderAndSwapBuffers(); + /** Wait after swapping before next render pass. + + Used when rendering is running on a thread. The default is 20 millseconds, giving + a nominal frame rate of just under 50 fps. + */ + virtual void waitAfterSwapping(); + + /** Stop Rendering. + + Use to shut down an openGLComponent properly, whether on a thread or not. + */ + virtual bool stopRendering(); + /** This returns a critical section that can be used to lock the current context. Because the context that is used by this component can change, e.g. when the @@ -63926,6 +63961,11 @@ public: private: const OpenGLType type; + class OpenGLComponentRenderThread; + friend class OpenGLComponentRenderThread; + friend class ScopedPointer ; + ScopedPointer renderThread; + class OpenGLComponentWatcher; friend class OpenGLComponentWatcher; friend class ScopedPointer ; @@ -63935,7 +63975,7 @@ private: CriticalSection contextLock; OpenGLPixelFormat preferredPixelFormat; - bool needToUpdateViewport; + bool needToUpdateViewport, useThread; OpenGLContext* createContext(); void updateContextPosition(); diff --git a/src/audio/processors/juce_AudioProcessorGraph.cpp b/src/audio/processors/juce_AudioProcessorGraph.cpp index b321fd8a30..c5ed232621 100644 --- a/src/audio/processors/juce_AudioProcessorGraph.cpp +++ b/src/audio/processors/juce_AudioProcessorGraph.cpp @@ -169,6 +169,41 @@ private: JUCE_DECLARE_NON_COPYABLE (AddMidiBufferOp); }; +//============================================================================== +class DelayChannelOp : public AudioGraphRenderingOp +{ +public: + DelayChannelOp (const int channel_, const int numSamplesDelay_) + : channel (channel_), + bufferSize (numSamplesDelay_ + 1), + readIndex (0), writeIndex (numSamplesDelay_) + { + buffer.calloc (bufferSize); + } + + void perform (AudioSampleBuffer& sharedBufferChans, const OwnedArray &, const int numSamples) + { + float* data = sharedBufferChans.getSampleData (channel, 0); + + for (int i = numSamples; --i >= 0;) + { + buffer [writeIndex] = *data; + *data++ = buffer [readIndex]; + + if (++readIndex >= bufferSize) readIndex = 0; + if (++writeIndex >= bufferSize) writeIndex = 0; + } + } + +private: + HeapBlock buffer; + const int channel, bufferSize; + int readIndex, writeIndex; + + JUCE_DECLARE_NON_COPYABLE (DelayChannelOp); +}; + + //============================================================================== class ProcessBufferOp : public AudioGraphRenderingOp { @@ -223,7 +258,8 @@ public: const Array& orderedNodes_, Array& renderingOps) : graph (graph_), - orderedNodes (orderedNodes_) + orderedNodes (orderedNodes_), + totalLatency (0) { nodeIds.add ((uint32) zeroNodeID); // first buffer is read-only zeros channels.add (0); @@ -237,6 +273,8 @@ public: markAnyUnusedBuffersAsFree (i); } + + graph.setLatencySamples (totalLatency); } int getNumBuffersNeeded() const { return nodeIds.size(); } @@ -253,6 +291,42 @@ private: static bool isNodeBusy (uint32 nodeID) noexcept { return nodeID != freeNodeID && nodeID != zeroNodeID; } + Array nodeDelayIDs; + Array nodeDelays; + int totalLatency; + + int getNodeDelay (const uint32 nodeID) const { return nodeDelays [nodeDelayIDs.indexOf (nodeID)]; } + + void setNodeDelay (const uint32 nodeID, const int latency) + { + const int index = nodeDelayIDs.indexOf (nodeID); + + if (index >= 0) + { + nodeDelays.set (index, latency); + } + else + { + nodeDelayIDs.add (nodeID); + nodeDelays.add (latency); + } + } + + int getInputLatencyForNode (const uint32 nodeID) const + { + int maxLatency = 0; + + for (int i = graph.getNumConnections(); --i >= 0;) + { + const AudioProcessorGraph::Connection* const c = graph.getConnection (i); + + if (c->destNodeId == nodeID) + maxLatency = jmax (maxLatency, getNodeDelay (c->sourceNodeId)); + } + + return maxLatency; + } + //============================================================================== void createRenderingOpsForNode (AudioProcessorGraph::Node* const node, Array& renderingOps, @@ -265,6 +339,8 @@ private: Array audioChannelsToUse; int midiBufferToUse = -1; + int maxLatency = getInputLatencyForNode (node->nodeId); + for (int inputChan = 0; inputChan < numIns; ++inputChan) { // get a list of all the inputs to this node @@ -326,6 +402,11 @@ private: bufIndex = newFreeBuffer; } + + const int nodeDelay = getNodeDelay (srcNode); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } else { @@ -348,6 +429,11 @@ private: // we've found one of our input chans that can be re-used.. reusableInputIndex = i; bufIndex = sourceBufIndex; + + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (i)); + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (sourceBufIndex, maxLatency - nodeDelay)); + break; } } @@ -371,6 +457,10 @@ private: } reusableInputIndex = 0; + const int nodeDelay = getNodeDelay (sourceNodes.getFirst()); + + if (nodeDelay < maxLatency) + renderingOps.add (new DelayChannelOp (bufIndex, maxLatency - nodeDelay)); } for (int j = 0; j < sourceNodes.size(); ++j) @@ -380,7 +470,30 @@ private: const int srcIndex = getBufferContaining (sourceNodes.getUnchecked(j), sourceOutputChans.getUnchecked(j)); if (srcIndex >= 0) - renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + { + const int nodeDelay = getNodeDelay (sourceNodes.getUnchecked (j)); + + if (nodeDelay < maxLatency) + { + if (! isBufferNeededLater (ourRenderingIndex, inputChan, + sourceNodes.getUnchecked(j), + sourceOutputChans.getUnchecked(j))) + { + renderingOps.add (new DelayChannelOp (srcIndex, maxLatency - nodeDelay)); + } + else // buffer is reused elsewhere, can't be delayed + { + const int bufferToDelay = getFreeBuffer (false); + renderingOps.add (new CopyChannelOp (srcIndex, bufferToDelay)); + renderingOps.add (new DelayChannelOp (bufferToDelay, maxLatency - nodeDelay)); + renderingOps.add (new AddChannelOp (bufferToDelay, bufIndex)); + } + } + else + { + renderingOps.add (new AddChannelOp (srcIndex, bufIndex)); + } + } } } } @@ -442,8 +555,8 @@ private: } else { - // probably a feedback loop, so just use an empty one.. - midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi + // probably a feedback loop, so just use an empty one.. + midiBufferToUse = getFreeBuffer (true); // need to pick a buffer even if the processor doesn't use midi } } else @@ -501,6 +614,11 @@ private: markBufferAsContaining (midiBufferToUse, node->nodeId, AudioProcessorGraph::midiChannelIndex); + setNodeDelay (node->nodeId, maxLatency + node->getProcessor()->getLatencySamples()); + + if (numOuts == 0) + totalLatency = maxLatency; + renderingOps.add (new ProcessBufferOp (node, audioChannelsToUse, totalChans, midiBufferToUse)); } diff --git a/src/core/juce_StandardHeader.h b/src/core/juce_StandardHeader.h index d4dd7d3a83..1aa8781c4b 100644 --- a/src/core/juce_StandardHeader.h +++ b/src/core/juce_StandardHeader.h @@ -33,7 +33,7 @@ */ #define JUCE_MAJOR_VERSION 1 #define JUCE_MINOR_VERSION 53 -#define JUCE_BUILDNUMBER 76 +#define JUCE_BUILDNUMBER 77 /** Current Juce version number. @@ -186,6 +186,7 @@ extern JUCE_API bool JUCE_CALLTYPE juce_isRunningUnderDebugger(); #include "juce_Logger.h" #include "../memory/juce_LeakedObjectDetector.h" +#undef TYPE_BOOL // (stupidly-named CoreServices definition which interferes with other libraries). END_JUCE_NAMESPACE diff --git a/src/gui/components/special/juce_OpenGLComponent.cpp b/src/gui/components/special/juce_OpenGLComponent.cpp index b07239e714..cefa12b136 100644 --- a/src/gui/components/special/juce_OpenGLComponent.cpp +++ b/src/gui/components/special/juce_OpenGLComponent.cpp @@ -32,6 +32,7 @@ BEGIN_JUCE_NAMESPACE #include "juce_OpenGLComponent.h" #include "../windows/juce_ComponentPeer.h" #include "../layout/juce_ComponentMovementWatcher.h" +#include "../../../threads/juce_Thread.h" //============================================================================== @@ -129,6 +130,40 @@ OpenGLContext* OpenGLContext::getCurrentContext() return nullptr; } +//============================================================================== +class OpenGLComponent::OpenGLComponentRenderThread : public Thread, + public AsyncUpdater +{ +public: + //============================================================================== + OpenGLComponentRenderThread (OpenGLComponent& owner_) + : Thread ("OpenGL Render"), + owner (owner_) + { + } + + void run() + { + // Context will get created and callback triggered on first render + while (owner.renderAndSwapBuffers() && ! threadShouldExit()) + owner.waitAfterSwapping(); + + triggerAsyncUpdate(); + } + + void handleAsyncUpdate() + { + owner.stopRendering(); + } + + //============================================================================== +private: + OpenGLComponent& owner; + + JUCE_DECLARE_NON_COPYABLE (OpenGLComponentRenderThread); +}; + + //============================================================================== class OpenGLComponent::OpenGLComponentWatcher : public ComponentMovementWatcher @@ -150,7 +185,7 @@ public: void componentPeerChanged() { const ScopedLock sl (owner->getContextLock()); - owner->deleteContext(); + owner->stopRendering(); } void componentVisibilityChanged() @@ -158,7 +193,7 @@ public: if (! owner->isShowing()) { const ScopedLock sl (owner->getContextLock()); - owner->deleteContext(); + owner->stopRendering(); } } @@ -284,17 +319,38 @@ void OpenGLComponent::swapBuffers() context->swapBuffers(); } +void OpenGLComponent::setUsingDedicatedThread (bool useDedicatedThread) noexcept +{ + useThread = useDedicatedThread; +} + void OpenGLComponent::paint (Graphics&) { - if (renderAndSwapBuffers()) + if (useThread) { - ComponentPeer* const peer = getPeer(); + if (renderThread == nullptr) + renderThread = new OpenGLComponentRenderThread (*this); - if (peer != nullptr) - { - const Point topLeft (getScreenPosition() - peer->getScreenPosition()); - peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight()); - } + if (! renderThread->isThreadRunning()) + renderThread->startThread (6); + + // fall-through and update the masking region + } + else + { + if (renderThread != nullptr && renderThread->isThreadRunning()) + renderThread->stopThread (5000); + + if (! renderAndSwapBuffers()) + return; + } + + ComponentPeer* const peer = getPeer(); + + if (peer != nullptr) + { + const Point topLeft (getScreenPosition() - peer->getScreenPosition()); + peer->addMaskedRegion (topLeft.getX(), topLeft.getY(), getWidth(), getHeight()); } } @@ -317,6 +373,35 @@ bool OpenGLComponent::renderAndSwapBuffers() return true; } +void OpenGLComponent::waitAfterSwapping() +{ + jassert (renderThread != nullptr && Thread::getCurrentThread() == renderThread); + + Thread::sleep (20); +} + +bool OpenGLComponent::stopRendering() +{ + const ScopedLock sl (contextLock); + + if (! makeCurrentContextActive()) + return false; + + releaseOpenGLContext(); // callback to allow for shutdown + + if (renderThread != nullptr && Thread::getCurrentThread() == renderThread) + { + // make the context inactive - if we're on a thread, this will release the context, + // so the main thread can take it and do shutdown + + makeCurrentContextInactive(); + } + else if (context != nullptr) + context->deleteContext(); + + return true; +} + void OpenGLComponent::internalRepaint (int x, int y, int w, int h) { Component::internalRepaint (x, y, w, h); diff --git a/src/gui/components/special/juce_OpenGLComponent.h b/src/gui/components/special/juce_OpenGLComponent.h index 22fe2ac883..796f31d5fe 100644 --- a/src/gui/components/special/juce_OpenGLComponent.h +++ b/src/gui/components/special/juce_OpenGLComponent.h @@ -230,13 +230,23 @@ public: /** Returns the context that this component is sharing with. @see shareWith */ - OpenGLContext* getShareContext() const noexcept { return contextToShareListsWith; } + OpenGLContext* getShareContext() const noexcept { return contextToShareListsWith; } //============================================================================== /** Flips the openGL buffers over. */ void swapBuffers(); + /** Indicates whether the component should perform its rendering on a background thread. + By default, this is set to false, and the renderOpenGL() callback happens on the main + UI thread, in response to a repaint. If set to true, then the component will create + a background thread which it uses to repeatedly call renderOpenGL(). + */ + void setUsingDedicatedThread (bool useDedicatedThread) noexcept; + + /** Returns true if the component is performing the rendering on a background thread. */ + bool isUsingDedicatedThread() const noexcept { return useThread; } + /** This replaces the normal paint() callback - use it to draw your openGL stuff. When this is called, makeCurrentContextActive() will already have been called @@ -266,6 +276,16 @@ public: */ virtual void newOpenGLContextCreated() = 0; + /** This method is called when the component shuts down its OpenGL context. + + You can use this callback to delete textures and any other OpenGL objects you + created in the component's context. + + When this callback happens, the context will have been made current + using the makeCurrentContextActive() method, so there's no need to call it + again in your code. + */ + virtual void releaseOpenGLContext() {} //============================================================================== /** Returns the context that will draw into this component. @@ -322,6 +342,19 @@ public: */ virtual bool renderAndSwapBuffers(); + /** Wait after swapping before next render pass. + + Used when rendering is running on a thread. The default is 20 millseconds, giving + a nominal frame rate of just under 50 fps. + */ + virtual void waitAfterSwapping(); + + /** Stop Rendering. + + Use to shut down an openGLComponent properly, whether on a thread or not. + */ + virtual bool stopRendering(); + /** This returns a critical section that can be used to lock the current context. Because the context that is used by this component can change, e.g. when the @@ -351,6 +384,11 @@ public: private: const OpenGLType type; + class OpenGLComponentRenderThread; + friend class OpenGLComponentRenderThread; + friend class ScopedPointer ; + ScopedPointer renderThread; + class OpenGLComponentWatcher; friend class OpenGLComponentWatcher; friend class ScopedPointer ; @@ -360,7 +398,7 @@ private: CriticalSection contextLock; OpenGLPixelFormat preferredPixelFormat; - bool needToUpdateViewport; + bool needToUpdateViewport, useThread; OpenGLContext* createContext(); void updateContextPosition(); diff --git a/src/io/files/juce_File.cpp b/src/io/files/juce_File.cpp index 6adeae239e..1506fa644e 100644 --- a/src/io/files/juce_File.cpp +++ b/src/io/files/juce_File.cpp @@ -616,17 +616,12 @@ const File File::getNonexistentSibling (const bool putNumbersInBrackets) const //============================================================================== const String File::getFileExtension() const { - String ext; + const int indexOfDot = fullPath.lastIndexOfChar ('.'); - if (! isDirectory()) - { - const int indexOfDot = fullPath.lastIndexOfChar ('.'); - - if (indexOfDot > fullPath.lastIndexOfChar (separator)) - ext = fullPath.substring (indexOfDot); - } + if (indexOfDot > fullPath.lastIndexOfChar (separator)) + return fullPath.substring (indexOfDot); - return ext; + return String::empty; } bool File::hasFileExtension (const String& possibleSuffix) const diff --git a/src/native/mac/juce_ios_UIViewComponentPeer.mm b/src/native/mac/juce_ios_UIViewComponentPeer.mm index 66a4e3a503..85fe3362fc 100644 --- a/src/native/mac/juce_ios_UIViewComponentPeer.mm +++ b/src/native/mac/juce_ios_UIViewComponentPeer.mm @@ -749,13 +749,16 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons currentTouches.add (touch); } - if (isDown) + if ([touch phase] == UITouchPhaseBegan + || [touch phase] == UITouchPhaseStationary + || [touch phase] == UITouchPhaseMoved) { currentModifiers = currentModifiers.withoutMouseButtons(); handleMouseEvent (touchIndex, pos, currentModifiers, time); currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier); } - else if (isUp) + else if ([touch phase] == UITouchPhaseEnded + || [touch phase] == UITouchPhaseCancelled) { currentModifiers = currentModifiers.withoutMouseButtons(); currentTouches.remove (touchIndex); @@ -850,7 +853,11 @@ BOOL UIViewComponentPeer::textViewReplaceCharacters (const Range& range, co if (currentSelection.isEmpty()) target->setHighlightedRegion (currentSelection.withStart (currentSelection.getStart() - 1)); - target->insertTextAtCaret (text); + if (text == "\r" || text == "\n" || text == "\r\n") + handleKeyPress (KeyPress::returnKey, text[0]); + else + target->insertTextAtCaret (text); + updateHiddenTextContent (target); } diff --git a/src/native/mac/juce_mac_Fonts.mm b/src/native/mac/juce_mac_Fonts.mm index e3089fd762..03f6a08fbf 100644 --- a/src/native/mac/juce_mac_Fonts.mm +++ b/src/native/mac/juce_mac_Fonts.mm @@ -27,6 +27,245 @@ // compiled on its own). #if JUCE_INCLUDED_FILE +#if (JUCE_MAC && defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) \ + || (JUCE_IOS && defined (__IPHONE_3_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_3_2) + #define JUCE_CORETEXT_AVAILABLE 1 +#endif + +#if JUCE_CORETEXT_AVAILABLE + +//============================================================================== +class MacTypeface : public Typeface +{ +public: + //============================================================================== + MacTypeface (const Font& font) + : Typeface (font.getTypefaceName()), + fontRef (nullptr), + fontHeightToCGSizeFactor (1.0f), + renderingTransform (CGAffineTransformIdentity), + ctFontRef (nullptr), + attributedStringAtts (nullptr), + ascent (0.0f), + unitsToHeightScaleFactor (0.0f) + { + JUCE_AUTORELEASEPOOL + CFStringRef name = PlatformUtilities::juceStringToCFString (font.getTypefaceName()); + ctFontRef = CTFontCreateWithName (name, 1024, nullptr); + CFRelease (name); + + if (ctFontRef != nullptr) + { + bool needsItalicTransform = false; + + if (font.isItalic()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontItalicTrait, kCTFontItalicTrait); + + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + else + { + needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform.. + } + } + + if (font.isBold()) + { + CTFontRef newFont = CTFontCreateCopyWithSymbolicTraits (ctFontRef, 0.0, nullptr, + kCTFontBoldTrait, kCTFontBoldTrait); + if (newFont != nullptr) + { + CFRelease (ctFontRef); + ctFontRef = newFont; + } + } + + ascent = std::abs ((float) CTFontGetAscent (ctFontRef)); + const float totalSize = ascent + std::abs ((float) CTFontGetDescent (ctFontRef)); + ascent /= totalSize; + + pathTransform = AffineTransform::identity.scale (1.0f / totalSize, 1.0f / totalSize); + + if (needsItalicTransform) + { + pathTransform = pathTransform.sheared (-0.15f, 0.0f); + renderingTransform.c = 0.15f; + } + + fontRef = CTFontCopyGraphicsFont (ctFontRef, nullptr); + + const int totalHeight = abs (CGFontGetAscent (fontRef)) + abs (CGFontGetDescent (fontRef)); + const float ctTotalHeight = abs (CTFontGetAscent (ctFontRef)) + abs (CTFontGetDescent (ctFontRef)); + unitsToHeightScaleFactor = 1.0f / ctTotalHeight; + fontHeightToCGSizeFactor = CGFontGetUnitsPerEm (fontRef) / (float) totalHeight; + + const short zero = 0; + CFNumberRef numberRef = CFNumberCreate (0, kCFNumberShortType, &zero); + + CFStringRef keys[] = { kCTFontAttributeName, kCTLigatureAttributeName }; + CFTypeRef values[] = { ctFontRef, numberRef }; + attributedStringAtts = CFDictionaryCreate (nullptr, (const void**) &keys, (const void**) &values, numElementsInArray (keys), + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFRelease (numberRef); + } + } + + ~MacTypeface() + { + if (attributedStringAtts != nullptr) + CFRelease (attributedStringAtts); + + if (fontRef != nullptr) + CGFontRelease (fontRef); + + if (ctFontRef != nullptr) + CFRelease (ctFontRef); + } + + float getAscent() const { return ascent; } + float getDescent() const { return 1.0f - ascent; } + + float getStringWidth (const String& text) + { + float x = 0; + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + + for (int j = 0; j < length; ++j) + x += (float) advances[j].width; + } + + CFRelease (line); + CFRelease (attribString); + + x *= unitsToHeightScaleFactor; + } + + return x; + } + + void getGlyphPositions (const String& text, Array & resultGlyphs, Array & xOffsets) + { + xOffsets.add (0); + + if (ctFontRef != nullptr && text.isNotEmpty()) + { + float x = 0; + + CFStringRef cfText = PlatformUtilities::juceStringToCFString (text); + CFAttributedStringRef attribString = CFAttributedStringCreate (kCFAllocatorDefault, cfText, attributedStringAtts); + CFRelease (cfText); + + CTLineRef line = CTLineCreateWithAttributedString (attribString); + CFArrayRef runArray = CTLineGetGlyphRuns (line); + + for (CFIndex i = 0; i < CFArrayGetCount (runArray); ++i) + { + CTRunRef run = (CTRunRef) CFArrayGetValueAtIndex (runArray, i); + CFIndex length = CTRunGetGlyphCount (run); + HeapBlock advances (length); + CTRunGetAdvances (run, CFRangeMake (0, 0), advances); + HeapBlock glyphs (length); + CTRunGetGlyphs (run, CFRangeMake (0, 0), glyphs); + + for (int j = 0; j < length; ++j) + { + x += (float) advances[j].width; + xOffsets.add (x * unitsToHeightScaleFactor); + resultGlyphs.add (glyphs[j]); + } + } + + CFRelease (line); + CFRelease (attribString); + } + } + + EdgeTable* getEdgeTableForGlyph (int glyphNumber, const AffineTransform& transform) + { + Path path; + + if (getOutlineForGlyph (glyphNumber, path) && ! path.isEmpty()) + return new EdgeTable (path.getBoundsTransformed (transform).getSmallestIntegerContainer().expanded (1, 0), + path, transform); + + return nullptr; + } + + bool getOutlineForGlyph (int glyphNumber, Path& path) + { + jassert (path.isEmpty()); // we might need to apply a transform to the path, so this must be empty + + CGPathRef pathRef = CTFontCreatePathForGlyph (ctFontRef, (CGGlyph) glyphNumber, &renderingTransform); + CGPathApply (pathRef, &path, pathApplier); + CFRelease (pathRef); + + if (! pathTransform.isIdentity()) + path.applyTransform (pathTransform); + + return true; + } + + //============================================================================== + CGFontRef fontRef; + + float fontHeightToCGSizeFactor; + CGAffineTransform renderingTransform; + +private: + CTFontRef ctFontRef; + CFDictionaryRef attributedStringAtts; + float ascent, unitsToHeightScaleFactor; + AffineTransform pathTransform; + + static void pathApplier (void* info, const CGPathElement* const element) + { + Path& path = *static_cast (info); + const CGPoint* const p = element->points; + + switch (element->type) + { + case kCGPathElementMoveToPoint: path.startNewSubPath ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddLineToPoint: path.lineTo ((float) p[0].x, (float) -p[0].y); break; + case kCGPathElementAddQuadCurveToPoint: path.quadraticTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y); break; + case kCGPathElementAddCurveToPoint: path.cubicTo ((float) p[0].x, (float) -p[0].y, + (float) p[1].x, (float) -p[1].y, + (float) p[2].x, (float) -p[2].y); break; + case kCGPathElementCloseSubpath: path.closeSubPath(); break; + default: jassertfalse; break; + } + } + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); +}; + +#else + +//============================================================================== +// The stuff that follows is a mash-up that supports pre-OSX 10.5 and pre-iOS 3.2 APIs. +// (Hopefully all of this can be ditched at some point in the future). + +//============================================================================== #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 #define SUPPORT_10_4_FONTS 1 #define NEW_CGFONT_FUNCTIONS_UNAVAILABLE (CGFontCreateWithFontName == 0) @@ -482,12 +721,14 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MacTypeface); }; +#endif + +//============================================================================== const Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) { return new MacTypeface (font); } -//============================================================================== const StringArray Font::findAllTypefaceNames() { StringArray names; diff --git a/src/native/mac/juce_mac_NSViewComponentPeer.mm b/src/native/mac/juce_mac_NSViewComponentPeer.mm index 5e4e849a8e..120d1abeba 100644 --- a/src/native/mac/juce_mac_NSViewComponentPeer.mm +++ b/src/native/mac/juce_mac_NSViewComponentPeer.mm @@ -198,9 +198,9 @@ public: virtual bool redirectKeyDown (NSEvent* ev); virtual bool redirectKeyUp (NSEvent* ev); virtual void redirectModKeyChange (NSEvent* ev); -#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 + #if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 virtual bool redirectPerformKeyEquivalent (NSEvent* ev); -#endif + #endif virtual BOOL sendDragCallback (int type, id sender); @@ -378,71 +378,19 @@ END_JUCE_NAMESPACE waitUntilDone: NO]; } -- (void) asyncMouseUp: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseUp (ev); -} +- (void) asyncMouseUp: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseUp (ev); } +- (void) mouseDragged: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseDrag (ev); } +- (void) mouseMoved: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseMove (ev); } +- (void) mouseEntered: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseEnter (ev); } +- (void) mouseExited: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseExit (ev); } +- (void) scrollWheel: (NSEvent*) ev { if (owner != nullptr) owner->redirectMouseWheel (ev); } -- (void) mouseDragged: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseDrag (ev); -} - -- (void) mouseMoved: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseMove (ev); -} - -- (void) mouseEntered: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseEnter (ev); -} - -- (void) mouseExited: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseExit (ev); -} - -- (void) rightMouseDown: (NSEvent*) ev -{ - [self mouseDown: ev]; -} - -- (void) rightMouseDragged: (NSEvent*) ev -{ - [self mouseDragged: ev]; -} - -- (void) rightMouseUp: (NSEvent*) ev -{ - [self mouseUp: ev]; -} - -- (void) otherMouseDown: (NSEvent*) ev -{ - [self mouseDown: ev]; -} - -- (void) otherMouseDragged: (NSEvent*) ev -{ - [self mouseDragged: ev]; -} - -- (void) otherMouseUp: (NSEvent*) ev -{ - [self mouseUp: ev]; -} - -- (void) scrollWheel: (NSEvent*) ev -{ - if (owner != nullptr) - owner->redirectMouseWheel (ev); -} +- (void) rightMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } +- (void) rightMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } +- (void) rightMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } +- (void) otherMouseDown: (NSEvent*) ev { [self mouseDown: ev]; } +- (void) otherMouseDragged: (NSEvent*) ev { [self mouseDragged: ev]; } +- (void) otherMouseUp: (NSEvent*) ev { [self mouseUp: ev]; } - (BOOL) acceptsFirstMouse: (NSEvent*) ev { @@ -641,26 +589,10 @@ END_JUCE_NAMESPACE } #endif -- (BOOL) becomeFirstResponder -{ - if (owner != nullptr) - owner->viewFocusGain(); - - return true; -} - -- (BOOL) resignFirstResponder -{ - if (owner != nullptr) - owner->viewFocusLoss(); - - return true; -} +- (BOOL) becomeFirstResponder { if (owner != nullptr) owner->viewFocusGain(); return YES; } +- (BOOL) resignFirstResponder { if (owner != nullptr) owner->viewFocusLoss(); return YES; } -- (BOOL) acceptsFirstResponder -{ - return owner != nullptr && owner->canBecomeKeyWindow(); -} +- (BOOL) acceptsFirstResponder { return owner != nullptr && owner->canBecomeKeyWindow(); } //============================================================================== - (NSArray*) getSupportedDragTypes @@ -866,14 +798,14 @@ NSViewComponentPeer::NSViewComponentPeer (Component* const component_, isSharedWindow (viewToAttachTo != nil), fullScreen (false), insideDrawRect (false), -#if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING usingCoreGraphics (true), -#else + #else usingCoreGraphics (false), -#endif + #endif recursiveToFrontCall (false) { - NSRect r = NSMakeRect (0, 0, (float) component->getWidth(),(float) component->getHeight()); + NSRect r = NSMakeRect (0, 0, (float) component->getWidth(), (float) component->getHeight()); view = [[JuceNSView alloc] initWithOwner: this withFrame: r]; [view setPostsFrameChangedNotifications: YES]; @@ -1073,12 +1005,12 @@ NSRect NSViewComponentPeer::constrainRect (NSRect r) Rectangle pos (convertToRectInt (r)); Rectangle original (convertToRectInt (current)); - #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_6 if ([window inLiveResize]) - #else + #else if ([window respondsToSelector: @selector (inLiveResize)] && [window performSelector: @selector (inLiveResize)]) - #endif + #endif { constrainer->checkBounds (pos, original, Desktop::getInstance().getAllMonitorDisplayAreas().getBounds(), @@ -1111,9 +1043,9 @@ void NSViewComponentPeer::setAlpha (float newAlpha) } else { - #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 + #if defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_ALLOWED >= MAC_OS_X_VERSION_10_5 [view setAlphaValue: (CGFloat) newAlpha]; - #else + #else if ([view respondsToSelector: @selector (setAlphaValue:)]) { // PITA dynamic invocation for 10.4 builds.. @@ -1216,22 +1148,17 @@ const BorderSize NSViewComponentPeer::getFrameSize() const bool NSViewComponentPeer::setAlwaysOnTop (bool alwaysOnTop) { if (! isSharedWindow) - { [window setLevel: alwaysOnTop ? NSFloatingWindowLevel : NSNormalWindowLevel]; - } - return true; } void NSViewComponentPeer::toFront (bool makeActiveWindow) { if (isSharedWindow) - { [[view superview] addSubview: view positioned: NSWindowAbove relativeTo: nil]; - } if (window != nil && component->isVisible()) { @@ -1591,7 +1518,7 @@ void NSViewComponentPeer::drawRect (NSRect r) if (! component->isOpaque()) CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); - #if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics) { CoreGraphicsContext context (cg, (float) [view frame].size.height); @@ -1601,7 +1528,7 @@ void NSViewComponentPeer::drawRect (NSRect r) insideDrawRect = false; } else - #endif + #endif { Image temp (getComponent()->isOpaque() ? Image::RGB : Image::ARGB, (int) (r.size.width + 0.5f), @@ -1647,9 +1574,9 @@ const StringArray NSViewComponentPeer::getAvailableRenderingEngines() { StringArray s (ComponentPeer::getAvailableRenderingEngines()); - #if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING s.add ("CoreGraphics Renderer"); - #endif + #endif return s; } @@ -1661,13 +1588,13 @@ int NSViewComponentPeer::getCurrentRenderingEngine() const void NSViewComponentPeer::setCurrentRenderingEngine (int index) { -#if USE_COREGRAPHICS_RENDERING + #if USE_COREGRAPHICS_RENDERING if (usingCoreGraphics != (index > 0)) { usingCoreGraphics = index > 0; [view setNeedsDisplay: true]; } -#endif + #endif } bool NSViewComponentPeer::canBecomeKeyWindow() @@ -1704,7 +1631,7 @@ void Desktop::createMouseInputSources() //============================================================================== void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDisable, bool allowMenusAndBars) { - #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 + #if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_6 if (enableOrDisable) { [NSApp setPresentationOptions: (allowMenusAndBars ? (NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar) @@ -1715,7 +1642,7 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis { [NSApp setPresentationOptions: NSApplicationPresentationDefault]; } - #else + #else if (enableOrDisable) { SetSystemUIMode (kUIModeAllSuppressed, allowMenusAndBars ? kUIOptionAutoShowMenuBar : 0); @@ -1725,7 +1652,7 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis { SetSystemUIMode (kUIModeNormal, 0); } - #endif + #endif } //============================================================================== diff --git a/src/threads/juce_WaitableEvent.h b/src/threads/juce_WaitableEvent.h index 1e4697a6ca..11e146fd89 100644 --- a/src/threads/juce_WaitableEvent.h +++ b/src/threads/juce_WaitableEvent.h @@ -79,12 +79,13 @@ public: If signal() is called when nothing is waiting, the next thread to call wait() will return immediately and reset the signal. - If the WaitableEvent is manual reset, all threads that are currently waiting on this - object will be woken. + If the WaitableEvent is manual reset, all current and future threads that wait upon this + object will be woken, until reset() is explicitly called. - If the WaitableEvent is automatic reset, and there are one or more threads waiting - on the object, then one of them will be woken up. If no threads are currently waiting, - then the next thread to call wait() will be woken up. + If the WaitableEvent is automatic reset, and one or more threads is waiting upon the object, + then one of them will be woken up. If no threads are currently waiting, then the next thread + to call wait() will be woken up. As soon as a thread is woken, the signal is automatically + reset. @see wait, reset */