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 */