| @@ -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 <OpenGLComponentRenderThread>; | |||
| ScopedPointer <OpenGLComponentRenderThread> renderThread; | |||
| class OpenGLComponentWatcher; | |||
| friend class OpenGLComponentWatcher; | |||
| friend class ScopedPointer <OpenGLComponentWatcher>; | |||
| @@ -63935,7 +63975,7 @@ private: | |||
| CriticalSection contextLock; | |||
| OpenGLPixelFormat preferredPixelFormat; | |||
| bool needToUpdateViewport; | |||
| bool needToUpdateViewport, useThread; | |||
| OpenGLContext* createContext(); | |||
| void updateContextPosition(); | |||
| @@ -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 <MidiBuffer>&, 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<float> buffer; | |||
| const int channel, bufferSize; | |||
| int readIndex, writeIndex; | |||
| JUCE_DECLARE_NON_COPYABLE (DelayChannelOp); | |||
| }; | |||
| //============================================================================== | |||
| class ProcessBufferOp : public AudioGraphRenderingOp | |||
| { | |||
| @@ -223,7 +258,8 @@ public: | |||
| const Array<void*>& orderedNodes_, | |||
| Array<void*>& 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 <uint32> nodeDelayIDs; | |||
| Array <int> 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<void*>& renderingOps, | |||
| @@ -265,6 +339,8 @@ private: | |||
| Array <int> 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)); | |||
| } | |||
| @@ -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 | |||
| @@ -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<int> 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<int> 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); | |||
| @@ -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 <OpenGLComponentRenderThread>; | |||
| ScopedPointer <OpenGLComponentRenderThread> renderThread; | |||
| class OpenGLComponentWatcher; | |||
| friend class OpenGLComponentWatcher; | |||
| friend class ScopedPointer <OpenGLComponentWatcher>; | |||
| @@ -360,7 +398,7 @@ private: | |||
| CriticalSection contextLock; | |||
| OpenGLPixelFormat preferredPixelFormat; | |||
| bool needToUpdateViewport; | |||
| bool needToUpdateViewport, useThread; | |||
| OpenGLContext* createContext(); | |||
| void updateContextPosition(); | |||
| @@ -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 | |||
| @@ -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<int>& 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); | |||
| } | |||
| @@ -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 <CGSize> 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 <int>& resultGlyphs, Array <float>& 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 <CGSize> advances (length); | |||
| CTRunGetAdvances (run, CFRangeMake (0, 0), advances); | |||
| HeapBlock <CGGlyph> 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<Path*> (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; | |||
| @@ -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 <NSDraggingInfo> 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<int> pos (convertToRectInt (r)); | |||
| Rectangle<int> 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<int> 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 | |||
| } | |||
| //============================================================================== | |||
| @@ -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 | |||
| */ | |||