| @@ -122,10 +122,35 @@ public: | |||||
| bool isLooping; | bool isLooping; | ||||
| //============================================================================== | //============================================================================== | ||||
| bool operator== (const CurrentPositionInfo& other) const noexcept; | |||||
| bool operator!= (const CurrentPositionInfo& other) const noexcept; | |||||
| void resetToDefault(); | |||||
| bool operator== (const CurrentPositionInfo& other) const noexcept | |||||
| { | |||||
| return timeInSamples == other.timeInSamples | |||||
| && ppqPosition == other.ppqPosition | |||||
| && editOriginTime == other.editOriginTime | |||||
| && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart | |||||
| && frameRate == other.frameRate | |||||
| && isPlaying == other.isPlaying | |||||
| && isRecording == other.isRecording | |||||
| && bpm == other.bpm | |||||
| && timeSigNumerator == other.timeSigNumerator | |||||
| && timeSigDenominator == other.timeSigDenominator | |||||
| && ppqLoopStart == other.ppqLoopStart | |||||
| && ppqLoopEnd == other.ppqLoopEnd | |||||
| && isLooping == other.isLooping; | |||||
| } | |||||
| bool operator!= (const CurrentPositionInfo& other) const noexcept | |||||
| { | |||||
| return ! operator== (other); | |||||
| } | |||||
| void resetToDefault() | |||||
| { | |||||
| zerostruct (*this); | |||||
| timeSigNumerator = 4; | |||||
| timeSigDenominator = 4; | |||||
| bpm = 120; | |||||
| } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -32,7 +32,7 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | intData += destBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -43,7 +43,7 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= destBytesPerSample; | intData -= destBytesPerSample; | ||||
| *reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -57,7 +57,7 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | intData += destBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -68,7 +68,7 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= destBytesPerSample; | intData -= destBytesPerSample; | ||||
| *reinterpret_cast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint16*> (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -132,7 +132,7 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | intData += destBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -143,7 +143,7 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= destBytesPerSample; | intData -= destBytesPerSample; | ||||
| *reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -157,7 +157,7 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| intData += destBytesPerSample; | intData += destBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -168,7 +168,7 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= destBytesPerSample; | intData -= destBytesPerSample; | ||||
| *reinterpret_cast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| *unalignedPointerCast<uint32*> (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -181,10 +181,10 @@ void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* de | |||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<float*> (d) = source[i]; | |||||
| *unalignedPointerCast<float*> (d) = source[i]; | |||||
| #if JUCE_BIG_ENDIAN | #if JUCE_BIG_ENDIAN | ||||
| *reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||||
| *unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||||
| #endif | #endif | ||||
| d += destBytesPerSample; | d += destBytesPerSample; | ||||
| @@ -199,10 +199,10 @@ void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* de | |||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| *reinterpret_cast<float*> (d) = source[i]; | |||||
| *unalignedPointerCast<float*> (d) = source[i]; | |||||
| #if JUCE_LITTLE_ENDIAN | #if JUCE_LITTLE_ENDIAN | ||||
| *reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||||
| *unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||||
| #endif | #endif | ||||
| d += destBytesPerSample; | d += destBytesPerSample; | ||||
| @@ -219,7 +219,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint16*> (intData)); | |||||
| intData += srcBytesPerSample; | intData += srcBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -230,7 +230,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= srcBytesPerSample; | intData -= srcBytesPerSample; | ||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
| dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint16*> (intData)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -244,7 +244,7 @@ void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint16*> (intData)); | |||||
| intData += srcBytesPerSample; | intData += srcBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -255,7 +255,7 @@ void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= srcBytesPerSample; | intData -= srcBytesPerSample; | ||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData)); | |||||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint16*> (intData)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -319,7 +319,7 @@ void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint32*> (intData)); | |||||
| intData += srcBytesPerSample; | intData += srcBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -330,7 +330,7 @@ void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= srcBytesPerSample; | intData -= srcBytesPerSample; | ||||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint32*> (intData)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -344,7 +344,7 @@ void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest | |||||
| { | { | ||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint32*> (intData)); | |||||
| intData += srcBytesPerSample; | intData += srcBytesPerSample; | ||||
| } | } | ||||
| } | } | ||||
| @@ -355,7 +355,7 @@ void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest | |||||
| for (int i = numSamples; --i >= 0;) | for (int i = numSamples; --i >= 0;) | ||||
| { | { | ||||
| intData -= srcBytesPerSample; | intData -= srcBytesPerSample; | ||||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData)); | |||||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint32*> (intData)); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -366,10 +366,10 @@ void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* de | |||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = *reinterpret_cast<const float*> (s); | |||||
| dest[i] = *unalignedPointerCast<const float*> (s); | |||||
| #if JUCE_BIG_ENDIAN | #if JUCE_BIG_ENDIAN | ||||
| auto d = reinterpret_cast<uint32*> (dest + i); | |||||
| auto d = unalignedPointerCast<uint32*> (dest + i); | |||||
| *d = ByteOrder::swap (*d); | *d = ByteOrder::swap (*d); | ||||
| #endif | #endif | ||||
| @@ -383,10 +383,10 @@ void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* de | |||||
| for (int i = 0; i < numSamples; ++i) | for (int i = 0; i < numSamples; ++i) | ||||
| { | { | ||||
| dest[i] = *reinterpret_cast<const float*> (s); | |||||
| dest[i] = *unalignedPointerCast<const float*> (s); | |||||
| #if JUCE_LITTLE_ENDIAN | #if JUCE_LITTLE_ENDIAN | ||||
| auto d = reinterpret_cast<uint32*> (dest + i); | |||||
| auto d = unalignedPointerCast<uint32*> (dest + i); | |||||
| *d = ByteOrder::swap (*d); | *d = ByteOrder::swap (*d); | ||||
| #endif | #endif | ||||
| @@ -409,8 +409,8 @@ public: | |||||
| auto numSamplesToCopy = (size_t) jmin (newNumSamples, size); | auto numSamplesToCopy = (size_t) jmin (newNumSamples, size); | ||||
| auto newChannels = reinterpret_cast<Type**> (newData.get()); | |||||
| auto newChan = reinterpret_cast<Type*> (newData + channelListSize); | |||||
| auto newChannels = unalignedPointerCast<Type**> (newData.get()); | |||||
| auto newChan = unalignedPointerCast<Type*> (newData + channelListSize); | |||||
| for (int j = 0; j < newNumChannels; ++j) | for (int j = 0; j < newNumChannels; ++j) | ||||
| { | { | ||||
| @@ -442,10 +442,10 @@ public: | |||||
| { | { | ||||
| allocatedBytes = newTotalBytes; | allocatedBytes = newTotalBytes; | ||||
| allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); | allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); | ||||
| channels = reinterpret_cast<Type**> (allocatedData.get()); | |||||
| channels = unalignedPointerCast<Type**> (allocatedData.get()); | |||||
| } | } | ||||
| auto* chan = reinterpret_cast<Type*> (allocatedData + channelListSize); | |||||
| auto* chan = unalignedPointerCast<Type*> (allocatedData + channelListSize); | |||||
| for (int i = 0; i < newNumChannels; ++i) | for (int i = 0; i < newNumChannels; ++i) | ||||
| { | { | ||||
| @@ -1127,7 +1127,7 @@ private: | |||||
| void allocateData() | void allocateData() | ||||
| { | { | ||||
| #if (! JUCE_GCC) || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 | |||||
| #if ! JUCE_PROJUCER_LIVE_BUILD && (! JUCE_GCC || (__GNUC__ * 100 + __GNUC_MINOR__) >= 409) | |||||
| static_assert (alignof (Type) <= detail::maxAlignment, | static_assert (alignof (Type) <= detail::maxAlignment, | ||||
| "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); | "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); | ||||
| #endif | #endif | ||||
| @@ -1142,8 +1142,8 @@ private: | |||||
| allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; | allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; | ||||
| allocatedData.malloc (allocatedBytes); | allocatedData.malloc (allocatedBytes); | ||||
| channels = reinterpret_cast<Type**> (allocatedData.get()); | |||||
| auto chan = reinterpret_cast<Type*> (allocatedData + channelListSize); | |||||
| channels = unalignedPointerCast<Type**> (allocatedData.get()); | |||||
| auto chan = unalignedPointerCast<Type*> (allocatedData + channelListSize); | |||||
| for (int i = 0; i < numChannels; ++i) | for (int i = 0; i < numChannels; ++i) | ||||
| { | { | ||||
| @@ -1167,7 +1167,7 @@ private: | |||||
| else | else | ||||
| { | { | ||||
| allocatedData.malloc (numChannels + 1, sizeof (Type*)); | allocatedData.malloc (numChannels + 1, sizeof (Type*)); | ||||
| channels = reinterpret_cast<Type**> (allocatedData.get()); | |||||
| channels = unalignedPointerCast<Type**> (allocatedData.get()); | |||||
| } | } | ||||
| for (int i = 0; i < numChannels; ++i) | for (int i = 0; i < numChannels; ++i) | ||||
| @@ -32,7 +32,7 @@ | |||||
| ID: juce_audio_basics | ID: juce_audio_basics | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE audio and MIDI data classes | name: JUCE audio and MIDI data classes | ||||
| description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -136,7 +136,7 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Receives events from a MidiKeyboardState object. */ | /** Receives events from a MidiKeyboardState object. */ | ||||
| class Listener | |||||
| class JUCE_API Listener | |||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -401,7 +401,7 @@ public: | |||||
| /** Returns true if the message is an aftertouch event. | /** Returns true if the message is an aftertouch event. | ||||
| For aftertouch events, use the getNoteNumber() method to find out the key | For aftertouch events, use the getNoteNumber() method to find out the key | ||||
| that it applies to, and getAftertouchValue() to find out the amount. Use | |||||
| that it applies to, and getAfterTouchValue() to find out the amount. Use | |||||
| getChannel() to find out the channel. | getChannel() to find out the channel. | ||||
| @see getAftertouchValue, getNoteNumber | @see getAftertouchValue, getNoteNumber | ||||
| @@ -177,8 +177,9 @@ static void addIfNotNull (OwnedArray<AudioIODeviceType>& list, AudioIODeviceType | |||||
| void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& list) | void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& list) | ||||
| { | { | ||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (false)); | |||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (true)); | |||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::shared)); | |||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::exclusive)); | |||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode::sharedLowLatency)); | |||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); | ||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_ASIO()); | ||||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | ||||
| @@ -544,6 +545,8 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup | |||||
| else if (currentAudioDevice != nullptr) | else if (currentAudioDevice != nullptr) | ||||
| return {}; | return {}; | ||||
| stopDevice(); | |||||
| if (getCurrentDeviceTypeObject() == nullptr | if (getCurrentDeviceTypeObject() == nullptr | ||||
| || (newSetup.inputDeviceName.isEmpty() && newSetup.outputDeviceName.isEmpty())) | || (newSetup.inputDeviceName.isEmpty() && newSetup.outputDeviceName.isEmpty())) | ||||
| { | { | ||||
| @@ -555,8 +558,6 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| stopDevice(); | |||||
| String error; | String error; | ||||
| if (currentSetup.inputDeviceName != newSetup.inputDeviceName | if (currentSetup.inputDeviceName != newSetup.inputDeviceName | ||||
| @@ -42,48 +42,105 @@ void AudioIODeviceType::callDeviceChangeListeners() | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #if ! JUCE_MAC | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||||
| #if JUCE_MAC | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return new CoreAudioClasses::CoreAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! JUCE_IOS | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } | |||||
| #if JUCE_IOS | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return new iOSAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_WINDOWS && JUCE_WASAPI) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; } | |||||
| #if JUCE_WINDOWS && JUCE_WASAPI | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode) | |||||
| { | |||||
| auto windowsVersion = SystemStats::getOperatingSystemType(); | |||||
| if (windowsVersion < SystemStats::WinVista | |||||
| || (WasapiClasses::isLowLatencyMode (deviceMode) && windowsVersion < SystemStats::Windows10)) | |||||
| return nullptr; | |||||
| return new WasapiClasses::WASAPIAudioIODeviceType (deviceMode); | |||||
| } | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode) | |||||
| { | |||||
| return createAudioIODeviceType_WASAPI (exclusiveMode ? WASAPIDeviceMode::exclusive | |||||
| : WASAPIDeviceMode::shared); | |||||
| } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode) { return nullptr; } | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_WINDOWS && JUCE_DIRECTSOUND) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } | |||||
| #if JUCE_WINDOWS && JUCE_DIRECTSOUND | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return new DSoundAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_WINDOWS && JUCE_ASIO) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } | |||||
| #if JUCE_WINDOWS && JUCE_ASIO | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return new ASIOAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_LINUX && JUCE_ALSA) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } | |||||
| #if JUCE_LINUX && JUCE_ALSA | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return createAudioIODeviceType_ALSA_PCMDevices(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_LINUX && JUCE_JACK) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } | |||||
| #if JUCE_LINUX && JUCE_JACK | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return new JackAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_LINUX && JUCE_BELA) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; } | |||||
| #if JUCE_LINUX && JUCE_BELA | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return new BelaAudioIODeviceType(); } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! JUCE_ANDROID | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } | |||||
| #if JUCE_ANDROID | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() | |||||
| { | |||||
| #if JUCE_USE_ANDROID_OBOE | |||||
| if (isOboeAvailable()) | |||||
| return nullptr; | |||||
| #endif | |||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| if (isOpenSLAvailable()) | |||||
| return nullptr; | |||||
| #endif | |||||
| return new AndroidAudioIODeviceType(); | |||||
| } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } | |||||
| #if JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() | |||||
| { | |||||
| return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; | |||||
| } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; } | |||||
| #endif | #endif | ||||
| #if ! (JUCE_ANDROID && JUCE_USE_ANDROID_OBOE) | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; } | |||||
| #if JUCE_ANDROID && JUCE_USE_ANDROID_OBOE | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() | |||||
| { | |||||
| return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr; | |||||
| } | |||||
| #else | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; } | |||||
| #endif | #endif | ||||
| } // namespace juce | } // namespace juce | ||||
| @@ -149,8 +149,8 @@ public: | |||||
| static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); | static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); | ||||
| /** Creates an iOS device type if it's available on this platform, or returns null. */ | /** Creates an iOS device type if it's available on this platform, or returns null. */ | ||||
| static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); | static AudioIODeviceType* createAudioIODeviceType_iOSAudio(); | ||||
| /** Creates a WASAPI device type if it's available on this platform, or returns null. */ | |||||
| static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode); | |||||
| /** Creates a WASAPI device type in the specified mode if it's available on this platform, or returns null. */ | |||||
| static AudioIODeviceType* createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode); | |||||
| /** Creates a DirectSound device type if it's available on this platform, or returns null. */ | /** Creates a DirectSound device type if it's available on this platform, or returns null. */ | ||||
| static AudioIODeviceType* createAudioIODeviceType_DirectSound(); | static AudioIODeviceType* createAudioIODeviceType_DirectSound(); | ||||
| /** Creates an ASIO device type if it's available on this platform, or returns null. */ | /** Creates an ASIO device type if it's available on this platform, or returns null. */ | ||||
| @@ -168,6 +168,9 @@ public: | |||||
| /** Creates a Bela device type if it's available on this platform, or returns null. */ | /** Creates a Bela device type if it's available on this platform, or returns null. */ | ||||
| static AudioIODeviceType* createAudioIODeviceType_Bela(); | static AudioIODeviceType* createAudioIODeviceType_Bela(); | ||||
| /** This method has been deprecated. You should call the method which takes a WASAPIDeviceMode instead. */ | |||||
| JUCE_DEPRECATED (static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode)); | |||||
| protected: | protected: | ||||
| explicit AudioIODeviceType (const String& typeName); | explicit AudioIODeviceType (const String& typeName); | ||||
| @@ -45,6 +45,8 @@ | |||||
| #include "juce_audio_devices.h" | #include "juce_audio_devices.h" | ||||
| #include "native/juce_MidiDataConcatenator.h" | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| #define Point CarbonDummyPointName | #define Point CarbonDummyPointName | ||||
| @@ -55,6 +57,9 @@ | |||||
| #undef Point | #undef Point | ||||
| #undef Component | #undef Component | ||||
| #include "native/juce_mac_CoreAudio.cpp" | |||||
| #include "native/juce_mac_CoreMidi.cpp" | |||||
| #elif JUCE_IOS | #elif JUCE_IOS | ||||
| #import <AudioToolbox/AudioToolbox.h> | #import <AudioToolbox/AudioToolbox.h> | ||||
| #import <AVFoundation/AVFoundation.h> | #import <AVFoundation/AVFoundation.h> | ||||
| @@ -64,13 +69,21 @@ | |||||
| #import <CoreMIDI/MIDINetworkSession.h> | #import <CoreMIDI/MIDINetworkSession.h> | ||||
| #endif | #endif | ||||
| #include "native/juce_ios_Audio.cpp" | |||||
| #include "native/juce_mac_CoreMidi.cpp" | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
| #if JUCE_WASAPI | #if JUCE_WASAPI | ||||
| #include <mmreg.h> | #include <mmreg.h> | ||||
| #include "native/juce_win32_WASAPI.cpp" | |||||
| #endif | #endif | ||||
| #if JUCE_USE_WINRT_MIDI && JUCE_MSVC | |||||
| #if JUCE_DIRECTSOUND | |||||
| #include "native/juce_win32_DirectSound.cpp" | |||||
| #endif | |||||
| #if JUCE_USE_WINRT_MIDI && (JUCE_MSVC || JUCE_CLANG) | |||||
| /* If you cannot find any of the header files below then you are probably | /* If you cannot find any of the header files below then you are probably | ||||
| attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you | attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you | ||||
| need to install version 10.0.14393.0 of the Windows Standalone SDK and you may | need to install version 10.0.14393.0 of the Windows Standalone SDK and you may | ||||
| @@ -93,6 +106,8 @@ | |||||
| JUCE_END_IGNORE_WARNINGS_MSVC | JUCE_END_IGNORE_WARNINGS_MSVC | ||||
| #endif | #endif | ||||
| #include "native/juce_win32_Midi.cpp" | |||||
| #if JUCE_ASIO | #if JUCE_ASIO | ||||
| /* This is very frustrating - we only need to use a handful of definitions from | /* This is very frustrating - we only need to use a handful of definitions from | ||||
| a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy | a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy | ||||
| @@ -114,6 +129,7 @@ | |||||
| needed - so to simplify things, you could just copy these into your JUCE directory). | needed - so to simplify things, you could just copy these into your JUCE directory). | ||||
| */ | */ | ||||
| #include <iasiodrv.h> | #include <iasiodrv.h> | ||||
| #include "native/juce_win32_ASIO.cpp" | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -128,6 +144,7 @@ | |||||
| just set the JUCE_ALSA flag to 0. | just set the JUCE_ALSA flag to 0. | ||||
| */ | */ | ||||
| #include <alsa/asoundlib.h> | #include <alsa/asoundlib.h> | ||||
| #include "native/juce_linux_ALSA.cpp" | |||||
| #endif | #endif | ||||
| #if JUCE_JACK | #if JUCE_JACK | ||||
| @@ -140,6 +157,7 @@ | |||||
| JUCE with low latency audio support, just set the JUCE_JACK flag to 0. | JUCE with low latency audio support, just set the JUCE_JACK flag to 0. | ||||
| */ | */ | ||||
| #include <jack/jack.h> | #include <jack/jack.h> | ||||
| #include "native/juce_linux_JackAudio.cpp" | |||||
| #endif | #endif | ||||
| #if JUCE_BELA | #if JUCE_BELA | ||||
| @@ -149,89 +167,18 @@ | |||||
| */ | */ | ||||
| #include <Bela.h> | #include <Bela.h> | ||||
| #include <Midi.h> | #include <Midi.h> | ||||
| #include "native/juce_linux_Bela.cpp" | |||||
| #endif | #endif | ||||
| #undef SIZEOF | #undef SIZEOF | ||||
| //============================================================================== | |||||
| #elif JUCE_ANDROID | |||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| #include <SLES/OpenSLES.h> | |||||
| #include <SLES/OpenSLES_Android.h> | |||||
| #include <SLES/OpenSLES_AndroidConfiguration.h> | |||||
| #endif | |||||
| #if JUCE_USE_ANDROID_OBOE | |||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| #error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES" | |||||
| #endif | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", | |||||
| "-Wzero-as-null-pointer-constant", | |||||
| "-Winconsistent-missing-destructor-override", | |||||
| "-Wshadow-field-in-constructor", | |||||
| "-Wshadow-field") | |||||
| #include <oboe/Oboe.h> | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| #endif | |||||
| #endif | |||||
| #include "audio_io/juce_AudioDeviceManager.cpp" | |||||
| #include "audio_io/juce_AudioIODevice.cpp" | |||||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||||
| #include "midi_io/juce_MidiDevices.cpp" | |||||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||||
| #include "sources/juce_AudioTransportSource.cpp" | |||||
| #include "native/juce_MidiDataConcatenator.h" | |||||
| //============================================================================== | |||||
| #if JUCE_MAC | |||||
| #include "native/juce_mac_CoreAudio.cpp" | |||||
| #include "native/juce_mac_CoreMidi.cpp" | |||||
| //============================================================================== | |||||
| #elif JUCE_IOS | |||||
| #include "native/juce_ios_Audio.cpp" | |||||
| #include "native/juce_mac_CoreMidi.cpp" | |||||
| //============================================================================== | |||||
| #elif JUCE_WINDOWS | |||||
| #if JUCE_WASAPI | |||||
| #include "native/juce_win32_WASAPI.cpp" | |||||
| #endif | |||||
| #if JUCE_DIRECTSOUND | |||||
| #include "native/juce_win32_DirectSound.cpp" | |||||
| #endif | |||||
| #include "native/juce_win32_Midi.cpp" | |||||
| #if JUCE_ASIO | |||||
| #include "native/juce_win32_ASIO.cpp" | |||||
| #endif | |||||
| //============================================================================== | |||||
| #elif JUCE_LINUX | |||||
| #if JUCE_ALSA | |||||
| #include "native/juce_linux_ALSA.cpp" | |||||
| #endif | |||||
| #if JUCE_JACK | |||||
| #include "native/juce_linux_JackAudio.cpp" | |||||
| #endif | |||||
| #if JUCE_BELA | |||||
| #include "native/juce_linux_Bela.cpp" | |||||
| #else | |||||
| #if ! JUCE_BELA | |||||
| #include "native/juce_linux_Midi.cpp" | #include "native/juce_linux_Midi.cpp" | ||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
| #include "native/juce_android_Audio.cpp" | #include "native/juce_android_Audio.cpp" | ||||
| #include "native/juce_android_Midi.cpp" | #include "native/juce_android_Midi.cpp" | ||||
| @@ -239,10 +186,25 @@ | |||||
| #include "native/juce_android_HighPerformanceAudioHelpers.h" | #include "native/juce_android_HighPerformanceAudioHelpers.h" | ||||
| #if JUCE_USE_ANDROID_OPENSLES | #if JUCE_USE_ANDROID_OPENSLES | ||||
| #include <SLES/OpenSLES.h> | |||||
| #include <SLES/OpenSLES_Android.h> | |||||
| #include <SLES/OpenSLES_AndroidConfiguration.h> | |||||
| #include "native/juce_android_OpenSL.cpp" | #include "native/juce_android_OpenSL.cpp" | ||||
| #endif | #endif | ||||
| #if JUCE_USE_ANDROID_OBOE | #if JUCE_USE_ANDROID_OBOE | ||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| #error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES" | |||||
| #endif | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter", | |||||
| "-Wzero-as-null-pointer-constant", | |||||
| "-Winconsistent-missing-destructor-override", | |||||
| "-Wshadow-field-in-constructor", | |||||
| "-Wshadow-field") | |||||
| #include <oboe/Oboe.h> | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| #include "native/juce_android_Oboe.cpp" | #include "native/juce_android_Oboe.cpp" | ||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| @@ -259,3 +221,11 @@ namespace juce | |||||
| bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } | bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } | ||||
| } | } | ||||
| #endif | #endif | ||||
| #include "audio_io/juce_AudioDeviceManager.cpp" | |||||
| #include "audio_io/juce_AudioIODevice.cpp" | |||||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||||
| #include "midi_io/juce_MidiDevices.cpp" | |||||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||||
| #include "sources/juce_AudioTransportSource.cpp" | |||||
| @@ -32,7 +32,7 @@ | |||||
| ID: juce_audio_devices | ID: juce_audio_devices | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE audio and MIDI I/O device classes | name: JUCE audio and MIDI I/O device classes | ||||
| description: Classes to play and record from audio and MIDI I/O devices | description: Classes to play and record from audio and MIDI I/O devices | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -88,21 +88,12 @@ | |||||
| #endif | #endif | ||||
| /** Config: JUCE_WASAPI | /** Config: JUCE_WASAPI | ||||
| Enables WASAPI audio devices (Windows Vista and above). See also the | |||||
| JUCE_WASAPI_EXCLUSIVE flag. | |||||
| Enables WASAPI audio devices (Windows Vista and above). | |||||
| */ | */ | ||||
| #ifndef JUCE_WASAPI | #ifndef JUCE_WASAPI | ||||
| #define JUCE_WASAPI 1 | #define JUCE_WASAPI 1 | ||||
| #endif | #endif | ||||
| /** Config: JUCE_WASAPI_EXCLUSIVE | |||||
| Enables WASAPI audio devices in exclusive mode (Windows Vista and above). | |||||
| */ | |||||
| #ifndef JUCE_WASAPI_EXCLUSIVE | |||||
| #define JUCE_WASAPI_EXCLUSIVE 0 | |||||
| #endif | |||||
| /** Config: JUCE_DIRECTSOUND | /** Config: JUCE_DIRECTSOUND | ||||
| Enables DirectSound audio (MS Windows only). | Enables DirectSound audio (MS Windows only). | ||||
| */ | */ | ||||
| @@ -174,6 +165,22 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #include "midi_io/juce_MidiDevices.h" | #include "midi_io/juce_MidiDevices.h" | ||||
| #include "midi_io/juce_MidiMessageCollector.h" | #include "midi_io/juce_MidiMessageCollector.h" | ||||
| namespace juce | |||||
| { | |||||
| /** Available modes for the WASAPI audio device. | |||||
| Pass one of these to the AudioIODeviceType::createAudioIODeviceType_WASAPI() | |||||
| method to create a WASAPI AudioIODeviceType object in this mode. | |||||
| */ | |||||
| enum class WASAPIDeviceMode | |||||
| { | |||||
| shared, | |||||
| exclusive, | |||||
| sharedLowLatency | |||||
| }; | |||||
| } | |||||
| #include "audio_io/juce_AudioIODevice.h" | #include "audio_io/juce_AudioIODevice.h" | ||||
| #include "audio_io/juce_AudioIODeviceType.h" | #include "audio_io/juce_AudioIODeviceType.h" | ||||
| #include "audio_io/juce_SystemAudioVolume.h" | #include "audio_io/juce_SystemAudioVolume.h" | ||||
| @@ -164,12 +164,16 @@ public: | |||||
| /** Deprecated. */ | /** Deprecated. */ | ||||
| static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*); | static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*); | ||||
| /** @internal */ | |||||
| class Pimpl; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| explicit MidiInput (const String&, const String&); | explicit MidiInput (const String&, const String&); | ||||
| MidiDeviceInfo deviceInfo; | MidiDeviceInfo deviceInfo; | ||||
| void* internal = nullptr; | |||||
| std::unique_ptr<Pimpl> internal; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | ||||
| }; | }; | ||||
| @@ -350,6 +354,9 @@ public: | |||||
| /** Deprecated. */ | /** Deprecated. */ | ||||
| static std::unique_ptr<MidiOutput> openDevice (int); | static std::unique_ptr<MidiOutput> openDevice (int); | ||||
| /** @internal */ | |||||
| class Pimpl; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| struct PendingMessage | struct PendingMessage | ||||
| @@ -368,7 +375,9 @@ private: | |||||
| void run() override; | void run() override; | ||||
| MidiDeviceInfo deviceInfo; | MidiDeviceInfo deviceInfo; | ||||
| void* internal = nullptr; | |||||
| std::unique_ptr<Pimpl> internal; | |||||
| CriticalSection lock; | CriticalSection lock; | ||||
| PendingMessage* firstMessage = nullptr; | PendingMessage* firstMessage = nullptr; | ||||
| @@ -478,19 +478,4 @@ private: | |||||
| extern bool isOboeAvailable(); | extern bool isOboeAvailable(); | ||||
| extern bool isOpenSLAvailable(); | extern bool isOpenSLAvailable(); | ||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() | |||||
| { | |||||
| #if JUCE_USE_ANDROID_OBOE | |||||
| if (isOboeAvailable()) | |||||
| return nullptr; | |||||
| #endif | |||||
| #if JUCE_USE_ANDROID_OPENSLES | |||||
| if (isOpenSLAvailable()) | |||||
| return nullptr; | |||||
| #endif | |||||
| return new AndroidAudioIODeviceType(); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -350,17 +350,17 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$Juc | |||||
| #undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
| //============================================================================== | //============================================================================== | ||||
| class AndroidMidiInput | |||||
| class MidiInput::Pimpl | |||||
| { | { | ||||
| public: | public: | ||||
| AndroidMidiInput (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) | |||||
| Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager) | |||||
| : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), | : juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048), | ||||
| javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, | javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, | ||||
| (jint) deviceID, (jlong) this))) | (jint) deviceID, (jlong) this))) | ||||
| { | { | ||||
| } | } | ||||
| ~AndroidMidiInput() | |||||
| ~Pimpl() | |||||
| { | { | ||||
| if (jobject d = javaMidiDevice.get()) | if (jobject d = javaMidiDevice.get()) | ||||
| { | { | ||||
| @@ -416,7 +416,7 @@ public: | |||||
| static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, | static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, | ||||
| jint offset, jint len, jlong timestamp) | jint offset, jint len, jlong timestamp) | ||||
| { | { | ||||
| auto* myself = reinterpret_cast<AndroidMidiInput*> (host); | |||||
| auto* myself = reinterpret_cast<Pimpl*> (host); | |||||
| myself->handleMidi (byteArray, offset, len, timestamp); | myself->handleMidi (byteArray, offset, len, timestamp); | ||||
| } | } | ||||
| @@ -429,15 +429,15 @@ private: | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| class AndroidMidiOutput | |||||
| class MidiOutput::Pimpl | |||||
| { | { | ||||
| public: | public: | ||||
| AndroidMidiOutput (const LocalRef<jobject>& midiDevice) | |||||
| Pimpl (const LocalRef<jobject>& midiDevice) | |||||
| : javaMidiDevice (midiDevice) | : javaMidiDevice (midiDevice) | ||||
| { | { | ||||
| } | } | ||||
| ~AndroidMidiOutput() | |||||
| ~Pimpl() | |||||
| { | { | ||||
| if (jobject d = javaMidiDevice.get()) | if (jobject d = javaMidiDevice.get()) | ||||
| { | { | ||||
| @@ -468,7 +468,7 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ | ||||
| CALLBACK (AndroidMidiInput::handleReceive, "handleReceive", "(J[BIIJ)V" ) | |||||
| CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" ) | |||||
| DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) | DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23) | ||||
| #undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
| @@ -509,11 +509,11 @@ public: | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| AndroidMidiInput* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||||
| MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback) | |||||
| { | { | ||||
| if (auto dm = deviceManager.get()) | if (auto dm = deviceManager.get()) | ||||
| { | { | ||||
| std::unique_ptr<AndroidMidiInput> androidMidiInput (new AndroidMidiInput (juceMidiInput, deviceID, callback, dm)); | |||||
| auto androidMidiInput = std::make_unique<MidiInput::Pimpl> (juceMidiInput, deviceID, callback, dm); | |||||
| if (androidMidiInput->isOpen()) | if (androidMidiInput->isOpen()) | ||||
| return androidMidiInput.release(); | return androidMidiInput.release(); | ||||
| @@ -522,11 +522,11 @@ public: | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| AndroidMidiOutput* openMidiOutputPortWithID (int deviceID) | |||||
| MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) | |||||
| { | { | ||||
| if (auto dm = deviceManager.get()) | if (auto dm = deviceManager.get()) | ||||
| if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) | if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID)) | ||||
| return new AndroidMidiOutput (LocalRef<jobject>(javaMidiPort)); | |||||
| return new MidiOutput::Pimpl (LocalRef<jobject>(javaMidiPort)); | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| @@ -564,7 +564,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) | if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) | ||||
| { | { | ||||
| midiInput->internal = port; | |||||
| midiInput->internal.reset (port); | |||||
| midiInput->setName (port->getName()); | midiInput->setName (port->getName()); | ||||
| return midiInput; | return midiInput; | ||||
| @@ -601,20 +601,17 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
| { | { | ||||
| } | } | ||||
| MidiInput::~MidiInput() | |||||
| { | |||||
| delete reinterpret_cast<AndroidMidiInput*> (internal); | |||||
| } | |||||
| MidiInput::~MidiInput() = default; | |||||
| void MidiInput::start() | void MidiInput::start() | ||||
| { | { | ||||
| if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||||
| if (auto* mi = internal.get()) | |||||
| mi->start(); | mi->start(); | ||||
| } | } | ||||
| void MidiInput::stop() | void MidiInput::stop() | ||||
| { | { | ||||
| if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||||
| if (auto* mi = internal.get()) | |||||
| mi->stop(); | mi->stop(); | ||||
| } | } | ||||
| @@ -646,7 +643,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||||
| if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) | if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) | ||||
| { | { | ||||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier)); | std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier)); | ||||
| midiOutput->internal = port; | |||||
| midiOutput->internal.reset (port); | |||||
| midiOutput->setName (port->getName()); | midiOutput->setName (port->getName()); | ||||
| return midiOutput; | return midiOutput; | ||||
| @@ -681,13 +678,11 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||||
| MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
| { | { | ||||
| stopBackgroundThread(); | stopBackgroundThread(); | ||||
| delete reinterpret_cast<AndroidMidiOutput*> (internal); | |||||
| } | } | ||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | void MidiOutput::sendMessageNow (const MidiMessage& message) | ||||
| { | { | ||||
| if (auto* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal)) | |||||
| if (auto* androidMidi = internal.get()) | |||||
| { | { | ||||
| auto* env = getEnv(); | auto* env = getEnv(); | ||||
| auto messageSize = message.getRawDataSize(); | auto messageSize = message.getRawDataSize(); | ||||
| @@ -1211,9 +1211,13 @@ public: | |||||
| jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I"); | jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I"); | ||||
| jmethodID isSourceMethod = env->GetMethodID (deviceClass, "isSource", "()Z"); | jmethodID isSourceMethod = env->GetMethodID (deviceClass, "isSource", "()Z"); | ||||
| auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod)); | |||||
| name << deviceTypeToString (env->CallIntMethod (device, getTypeMethod)); | |||||
| int id = env->CallIntMethod (device, getIdMethod); | |||||
| auto deviceTypeString = deviceTypeToString (env->CallIntMethod (device, getTypeMethod)); | |||||
| if (deviceTypeString.isEmpty()) // unknown device | |||||
| return; | |||||
| auto name = juceString ((jstring) env->CallObjectMethod (device, getProductNameMethod)) + " " + deviceTypeString; | |||||
| auto id = env->CallIntMethod (device, getIdMethod); | |||||
| auto jSampleRates = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getSampleRatesMethod)); | auto jSampleRates = LocalRef<jintArray> ((jintArray) env->CallObjectMethod (device, getSampleRatesMethod)); | ||||
| auto sampleRates = jintArrayToJuceArray (jSampleRates); | auto sampleRates = jintArrayToJuceArray (jSampleRates); | ||||
| @@ -1222,42 +1226,42 @@ public: | |||||
| auto channelCounts = jintArrayToJuceArray (jChannelCounts); | auto channelCounts = jintArrayToJuceArray (jChannelCounts); | ||||
| int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast(); | int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast(); | ||||
| bool isInput = env->CallBooleanMethod (device, isSourceMethod); | |||||
| auto isInput = env->CallBooleanMethod (device, isSourceMethod); | |||||
| auto& devices = isInput ? inputDevices : outputDevices; | auto& devices = isInput ? inputDevices : outputDevices; | ||||
| devices.add ({ name, id, sampleRates, numChannels }); | devices.add ({ name, id, sampleRates, numChannels }); | ||||
| } | } | ||||
| static const char* deviceTypeToString (int type) | |||||
| static String deviceTypeToString (int type) | |||||
| { | { | ||||
| switch (type) | switch (type) | ||||
| { | { | ||||
| case 0: return ""; | |||||
| case 1: return " built-in earphone speaker"; | |||||
| case 2: return " built-in speaker"; | |||||
| case 3: return " wired headset"; | |||||
| case 4: return " wired headphones"; | |||||
| case 5: return " line analog"; | |||||
| case 6: return " line digital"; | |||||
| case 7: return " Bluetooth device typically used for telephony"; | |||||
| case 8: return " Bluetooth device supporting the A2DP profile"; | |||||
| case 9: return " HDMI"; | |||||
| case 10: return " HDMI audio return channel"; | |||||
| case 11: return " USB device"; | |||||
| case 12: return " USB accessory"; | |||||
| case 13: return " DOCK"; | |||||
| case 14: return " FM"; | |||||
| case 15: return " built-in microphone"; | |||||
| case 16: return " FM tuner"; | |||||
| case 17: return " TV tuner"; | |||||
| case 18: return " telephony"; | |||||
| case 19: return " auxiliary line-level connectors"; | |||||
| case 20: return " IP"; | |||||
| case 21: return " BUS"; | |||||
| case 22: return " USB headset"; | |||||
| case 23: return " hearing aid"; | |||||
| case 24: return " built-in speaker safe"; | |||||
| default: jassertfalse; return ""; // type not supported yet, needs to be added! | |||||
| case 0: return {}; | |||||
| case 1: return "built-in earphone speaker"; | |||||
| case 2: return "built-in speaker"; | |||||
| case 3: return "wired headset"; | |||||
| case 4: return "wired headphones"; | |||||
| case 5: return "line analog"; | |||||
| case 6: return "line digital"; | |||||
| case 7: return "Bluetooth device typically used for telephony"; | |||||
| case 8: return "Bluetooth device supporting the A2DP profile"; | |||||
| case 9: return "HDMI"; | |||||
| case 10: return "HDMI audio return channel"; | |||||
| case 11: return "USB device"; | |||||
| case 12: return "USB accessory"; | |||||
| case 13: return "DOCK"; | |||||
| case 14: return "FM"; | |||||
| case 15: return "built-in microphone"; | |||||
| case 16: return "FM tuner"; | |||||
| case 17: return "TV tuner"; | |||||
| case 18: return "telephony"; | |||||
| case 19: return "auxiliary line-level connectors"; | |||||
| case 20: return "IP"; | |||||
| case 21: return "BUS"; | |||||
| case 22: return "USB headset"; | |||||
| case 23: return "hearing aid"; | |||||
| case 24: return "built-in speaker safe"; | |||||
| default: jassertfalse; return {}; // type not supported yet, needs to be added! | |||||
| } | } | ||||
| } | } | ||||
| @@ -1306,15 +1310,8 @@ public: | |||||
| const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe"; | const char* const OboeAudioIODevice::oboeTypeName = "Android Oboe"; | ||||
| //============================================================================== | |||||
| bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); } | bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); } | ||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() | |||||
| { | |||||
| return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class OboeRealtimeThread : private oboe::AudioStreamCallback | class OboeRealtimeThread : private oboe::AudioStreamCallback | ||||
| { | { | ||||
| @@ -71,12 +71,11 @@ static void destroyObject (SLObjectType object) | |||||
| (*object)->Destroy (object); | (*object)->Destroy (object); | ||||
| } | } | ||||
| template <> | |||||
| struct ContainerDeletePolicy<const SLObjectItf_* const> | |||||
| struct SLObjectItfFree | |||||
| { | { | ||||
| static void destroy (SLObjectItf object) | |||||
| void operator() (SLObjectItf obj) const noexcept | |||||
| { | { | ||||
| destroyObject (object); | |||||
| destroyObject (obj); | |||||
| } | } | ||||
| }; | }; | ||||
| @@ -112,7 +111,7 @@ private: | |||||
| ControlBlock() = default; | ControlBlock() = default; | ||||
| ControlBlock (SLObjectItf o) : ptr (o) {} | ControlBlock (SLObjectItf o) : ptr (o) {} | ||||
| std::unique_ptr<const SLObjectItf_* const> ptr; | |||||
| std::unique_ptr<const SLObjectItf_* const, SLObjectItfFree> ptr; | |||||
| }; | }; | ||||
| ReferenceCountedObjectPtr<ControlBlock> cb; | ReferenceCountedObjectPtr<ControlBlock> cb; | ||||
| @@ -1121,11 +1120,6 @@ const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL"; | |||||
| //============================================================================== | //============================================================================== | ||||
| bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } | bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } | ||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() | |||||
| { | |||||
| return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class SLRealtimeThread | class SLRealtimeThread | ||||
| { | { | ||||
| @@ -1431,12 +1431,6 @@ void iOSAudioIODeviceType::handleAsyncUpdate() | |||||
| callDeviceChangeListeners(); | callDeviceChangeListeners(); | ||||
| } | } | ||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() | |||||
| { | |||||
| return new iOSAudioIODeviceType(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } | AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } | ||||
| AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } | AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } | ||||
| @@ -1299,9 +1299,4 @@ AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices() | |||||
| return new ALSAAudioIODeviceType (false, "ALSA"); | return new ALSAAudioIODeviceType (false, "ALSA"); | ||||
| } | } | ||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() | |||||
| { | |||||
| return createAudioIODeviceType_ALSA_PCMDevices(); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -24,12 +24,12 @@ namespace juce | |||||
| { | { | ||||
| //============================================================================== | //============================================================================== | ||||
| class BelaMidiInput | |||||
| class MidiInput::Pimpl | |||||
| { | { | ||||
| public: | public: | ||||
| static Array<BelaMidiInput*> midiInputs; | |||||
| static Array<Pimpl*> midiInputs; | |||||
| BelaMidiInput (const String& port, MidiInput* input, MidiInputCallback* callback) | |||||
| Pimpl (const String& port, MidiInput* input, MidiInputCallback* callback) | |||||
| : midiInput (input), midiPort (port), midiCallback (callback) | : midiInput (input), midiPort (port), midiCallback (callback) | ||||
| { | { | ||||
| jassert (midiCallback != nullptr); | jassert (midiCallback != nullptr); | ||||
| @@ -38,7 +38,7 @@ public: | |||||
| buffer.resize (32); | buffer.resize (32); | ||||
| } | } | ||||
| ~BelaMidiInput() | |||||
| ~Pimpl() | |||||
| { | { | ||||
| stop(); | stop(); | ||||
| midiInputs.removeAllInstancesOf (this); | midiInputs.removeAllInstancesOf (this); | ||||
| @@ -76,7 +76,7 @@ public: | |||||
| } | } | ||||
| if (receivedBytes > 0) | if (receivedBytes > 0) | ||||
| pushMidiData (receivedBytes); | |||||
| pushMidiData ((int) receivedBytes); | |||||
| } | } | ||||
| static Array<MidiDeviceInfo> getDevices (bool input) | static Array<MidiDeviceInfo> getDevices (bool input) | ||||
| @@ -141,7 +141,7 @@ private: | |||||
| snd_rawmidi_info_t* info; | snd_rawmidi_info_t* info; | ||||
| snd_rawmidi_info_alloca (&info); | snd_rawmidi_info_alloca (&info); | ||||
| snd_rawmidi_info_set_device (info, device); | |||||
| snd_rawmidi_info_set_device (info, (unsigned int) device); | |||||
| snd_rawmidi_info_set_stream (info, input ? SND_RAWMIDI_STREAM_INPUT | snd_rawmidi_info_set_stream (info, input ? SND_RAWMIDI_STREAM_INPUT | ||||
| : SND_RAWMIDI_STREAM_OUTPUT); | : SND_RAWMIDI_STREAM_OUTPUT); | ||||
| @@ -173,10 +173,10 @@ private: | |||||
| Midi midi; | Midi midi; | ||||
| MidiDataConcatenator concatenator { 512 }; | MidiDataConcatenator concatenator { 512 }; | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaMidiInput) | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
| }; | }; | ||||
| Array<BelaMidiInput*> BelaMidiInput::midiInputs; | |||||
| Array<MidiInput::Pimpl*> MidiInput::Pimpl::midiInputs; | |||||
| //============================================================================== | //============================================================================== | ||||
| @@ -366,7 +366,7 @@ public: | |||||
| String getLastError() override { return lastError; } | String getLastError() override { return lastError; } | ||||
| //============================================================================== | //============================================================================== | ||||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||||
| int getCurrentBufferSizeSamples() override { return (int) actualBufferSize; } | |||||
| double getCurrentSampleRate() override { return 44100.0; } | double getCurrentSampleRate() override { return 44100.0; } | ||||
| int getCurrentBitDepth() override { return 16; } | int getCurrentBitDepth() override { return 16; } | ||||
| BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; } | BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; } | ||||
| @@ -384,8 +384,8 @@ private: | |||||
| bool setup (BelaContext& context) | bool setup (BelaContext& context) | ||||
| { | { | ||||
| actualBufferSize = context.audioFrames; | actualBufferSize = context.audioFrames; | ||||
| actualNumberOfInputs = context.audioInChannels + context.analogInChannels; | |||||
| actualNumberOfOutputs = context.audioOutChannels + context.analogOutChannels; | |||||
| actualNumberOfInputs = (int) (context.audioInChannels + context.analogInChannels); | |||||
| actualNumberOfOutputs = (int) (context.audioOutChannels + context.analogOutChannels); | |||||
| isBelaOpen = true; | isBelaOpen = true; | ||||
| firstCallback = true; | firstCallback = true; | ||||
| @@ -405,7 +405,7 @@ private: | |||||
| ScopedLock lock (callbackLock); | ScopedLock lock (callbackLock); | ||||
| // Check for and process and midi | // Check for and process and midi | ||||
| for (auto midiInput : BelaMidiInput::midiInputs) | |||||
| for (auto midiInput : MidiInput::Pimpl::midiInputs) | |||||
| midiInput->poll(); | midiInput->poll(); | ||||
| if (callback != nullptr) | if (callback != nullptr) | ||||
| @@ -413,27 +413,29 @@ private: | |||||
| jassert (context.audioFrames <= actualBufferSize); | jassert (context.audioFrames <= actualBufferSize); | ||||
| jassert ((context.flags & BELA_FLAG_INTERLEAVED) == 0); | jassert ((context.flags & BELA_FLAG_INTERLEAVED) == 0); | ||||
| using Frames = decltype (context.audioFrames); | |||||
| // Setup channelInBuffers | // Setup channelInBuffers | ||||
| for (int ch = 0; ch < actualNumberOfInputs; ++ch) | for (int ch = 0; ch < actualNumberOfInputs; ++ch) | ||||
| { | { | ||||
| if (ch < analogChannelStart) | if (ch < analogChannelStart) | ||||
| channelInBuffer[ch] = &context.audioIn[ch * context.audioFrames]; | |||||
| channelInBuffer[ch] = &context.audioIn[(Frames) ch * context.audioFrames]; | |||||
| else | else | ||||
| channelInBuffer[ch] = &context.analogIn[(ch - analogChannelStart) * context.analogFrames]; | |||||
| channelInBuffer[ch] = &context.analogIn[(Frames) (ch - analogChannelStart) * context.analogFrames]; | |||||
| } | } | ||||
| // Setup channelOutBuffers | // Setup channelOutBuffers | ||||
| for (int ch = 0; ch < actualNumberOfOutputs; ++ch) | for (int ch = 0; ch < actualNumberOfOutputs; ++ch) | ||||
| { | { | ||||
| if (ch < analogChannelStart) | if (ch < analogChannelStart) | ||||
| channelOutBuffer[ch] = &context.audioOut[ch * context.audioFrames]; | |||||
| channelOutBuffer[ch] = &context.audioOut[(Frames) ch * context.audioFrames]; | |||||
| else | else | ||||
| channelOutBuffer[ch] = &context.analogOut[(ch - analogChannelStart) * context.audioFrames]; | |||||
| channelOutBuffer[ch] = &context.analogOut[(Frames) (ch - analogChannelStart) * context.audioFrames]; | |||||
| } | } | ||||
| callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, | callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, | ||||
| channelOutBuffer.getData(), actualNumberOfOutputs, | channelOutBuffer.getData(), actualNumberOfOutputs, | ||||
| context.audioFrames); | |||||
| (int) context.audioFrames); | |||||
| } | } | ||||
| } | } | ||||
| @@ -521,25 +523,19 @@ struct BelaAudioIODeviceType : public AudioIODeviceType | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType) | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() | |||||
| { | |||||
| return new BelaAudioIODeviceType(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| MidiInput::MidiInput (const String& deviceName, const String& deviceID) | MidiInput::MidiInput (const String& deviceName, const String& deviceID) | ||||
| : deviceInfo (deviceName, deviceID) | : deviceInfo (deviceName, deviceID) | ||||
| { | { | ||||
| } | } | ||||
| MidiInput::~MidiInput() { delete static_cast<BelaMidiInput*> (internal); } | |||||
| void MidiInput::start() { static_cast<BelaMidiInput*> (internal)->start(); } | |||||
| void MidiInput::stop() { static_cast<BelaMidiInput*> (internal)->stop(); } | |||||
| MidiInput::~MidiInput() = default; | |||||
| void MidiInput::start() { internal->start(); } | |||||
| void MidiInput::stop() { internal->stop(); } | |||||
| Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | ||||
| { | { | ||||
| return BelaMidiInput::getDevices (true); | |||||
| return Pimpl::getDevices (true); | |||||
| } | } | ||||
| MidiDeviceInfo MidiInput::getDefaultDevice() | MidiDeviceInfo MidiInput::getDefaultDevice() | ||||
| @@ -553,7 +549,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| return {}; | return {}; | ||||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier)); | std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier)); | ||||
| midiInput->internal = new BelaMidiInput (deviceIdentifier, midiInput.get(), callback); | |||||
| midiInput->internal = std::make_unique<Pimpl> (deviceIdentifier, midiInput.get(), callback); | |||||
| return midiInput; | return midiInput; | ||||
| } | } | ||||
| @@ -587,7 +583,8 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* | |||||
| //============================================================================== | //============================================================================== | ||||
| // TODO: Add Bela MidiOutput support | // TODO: Add Bela MidiOutput support | ||||
| MidiOutput::~MidiOutput() {} | |||||
| class MidiOutput::Pimpl {}; | |||||
| MidiOutput::~MidiOutput() = default; | |||||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | void MidiOutput::sendMessageNow (const MidiMessage&) {} | ||||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | ||||
| MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } | MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; } | ||||
| @@ -36,9 +36,11 @@ static void* juce_loadJackFunction (const char* const name) | |||||
| #define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ | #define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \ | ||||
| return_type fn_name argument_types \ | return_type fn_name argument_types \ | ||||
| { \ | { \ | ||||
| using ReturnType = return_type; \ | |||||
| typedef return_type (*fn_type) argument_types; \ | typedef return_type (*fn_type) argument_types; \ | ||||
| static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | ||||
| return (fn != nullptr) ? ((*fn) arguments) : (return_type) 0; \ | |||||
| jassert (fn != nullptr); \ | |||||
| return (fn != nullptr) ? ((*fn) arguments) : ReturnType(); \ | |||||
| } | } | ||||
| #define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ | #define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \ | ||||
| @@ -46,30 +48,35 @@ static void* juce_loadJackFunction (const char* const name) | |||||
| { \ | { \ | ||||
| typedef void (*fn_type) argument_types; \ | typedef void (*fn_type) argument_types; \ | ||||
| static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | ||||
| jassert (fn != nullptr); \ | |||||
| if (fn != nullptr) (*fn) arguments; \ | if (fn != nullptr) (*fn) arguments; \ | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)); | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)); | |||||
| JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)); | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)); | |||||
| JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)); | |||||
| JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)); | |||||
| JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)); | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg)); | |||||
| JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client)) | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg)) | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_on_info_shutdown, (jack_client_t* client, JackInfoShutdownCallback function, void* arg), (client, function, arg)) | |||||
| JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size)) | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg)) | |||||
| JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port)) | |||||
| JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port)) | |||||
| JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg)) | |||||
| JUCE_DECL_JACK_FUNCTION (int, jack_port_flags, (const jack_port_t* port), (port)) | |||||
| JUCE_DECL_JACK_FUNCTION (jack_port_t*, jack_port_by_name, (jack_client_t* client, const char* name), (client, name)) | |||||
| JUCE_DECL_VOID_JACK_FUNCTION (jack_free, (void* ptr), (ptr)) | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| #define JACK_LOGGING_ENABLED 1 | #define JACK_LOGGING_ENABLED 1 | ||||
| @@ -115,56 +122,56 @@ namespace | |||||
| struct JackPortIterator | struct JackPortIterator | ||||
| { | { | ||||
| JackPortIterator (jack_client_t* const client, const bool forInput) | JackPortIterator (jack_client_t* const client, const bool forInput) | ||||
| : ports (nullptr), index (-1) | |||||
| { | { | ||||
| if (client != nullptr) | if (client != nullptr) | ||||
| ports = juce::jack_get_ports (client, nullptr, nullptr, | |||||
| forInput ? JackPortIsOutput : JackPortIsInput); | |||||
| // (NB: This looks like it's the wrong way round, but it is correct!) | |||||
| } | |||||
| ~JackPortIterator() | |||||
| { | |||||
| ::free (ports); | |||||
| ports.reset (juce::jack_get_ports (client, nullptr, nullptr, | |||||
| forInput ? JackPortIsInput : JackPortIsOutput)); | |||||
| } | } | ||||
| bool next() | bool next() | ||||
| { | { | ||||
| if (ports == nullptr || ports [index + 1] == nullptr) | |||||
| if (ports == nullptr || ports.get()[index + 1] == nullptr) | |||||
| return false; | return false; | ||||
| name = CharPointer_UTF8 (ports[++index]); | |||||
| clientName = name.upToFirstOccurrenceOf (":", false, false); | |||||
| name = CharPointer_UTF8 (ports.get()[++index]); | |||||
| return true; | return true; | ||||
| } | } | ||||
| const char** ports; | |||||
| int index; | |||||
| String getClientName() const | |||||
| { | |||||
| return name.upToFirstOccurrenceOf (":", false, false); | |||||
| } | |||||
| String getChannelName() const | |||||
| { | |||||
| return name.fromFirstOccurrenceOf (":", false, false); | |||||
| } | |||||
| struct Free | |||||
| { | |||||
| void operator() (const char** ptr) const noexcept { juce::jack_free (ptr); } | |||||
| }; | |||||
| std::unique_ptr<const char*, Free> ports; | |||||
| int index = -1; | |||||
| String name; | String name; | ||||
| String clientName; | |||||
| }; | }; | ||||
| class JackAudioIODeviceType; | |||||
| static Array<JackAudioIODeviceType*> activeDeviceTypes; | |||||
| //============================================================================== | //============================================================================== | ||||
| class JackAudioIODevice : public AudioIODevice | class JackAudioIODevice : public AudioIODevice | ||||
| { | { | ||||
| public: | public: | ||||
| JackAudioIODevice (const String& deviceName, | |||||
| const String& inId, | |||||
| const String& outId) | |||||
| : AudioIODevice (deviceName, "JACK"), | |||||
| inputId (inId), | |||||
| outputId (outId), | |||||
| deviceIsOpen (false), | |||||
| callback (nullptr), | |||||
| totalNumberOfInputChannels (0), | |||||
| totalNumberOfOutputChannels (0) | |||||
| { | |||||
| jassert (deviceName.isNotEmpty()); | |||||
| jack_status_t status; | |||||
| JackAudioIODevice (const String& inName, | |||||
| const String& outName, | |||||
| std::function<void()> notifyIn) | |||||
| : AudioIODevice (outName.isEmpty() ? inName : outName, "JACK"), | |||||
| inputName (inName), | |||||
| outputName (outName), | |||||
| notifyChannelsChanged (std::move (notifyIn)) | |||||
| { | |||||
| jassert (outName.isNotEmpty() || inName.isNotEmpty()); | |||||
| jack_status_t status = {}; | |||||
| client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); | client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status); | ||||
| if (client == nullptr) | if (client == nullptr) | ||||
| @@ -179,10 +186,10 @@ public: | |||||
| const StringArray inputChannels (getInputChannelNames()); | const StringArray inputChannels (getInputChannelNames()); | ||||
| for (int i = 0; i < inputChannels.size(); ++i) | for (int i = 0; i < inputChannels.size(); ++i) | ||||
| { | { | ||||
| String inputName; | |||||
| inputName << "in_" << ++totalNumberOfInputChannels; | |||||
| String inputChannelName; | |||||
| inputChannelName << "in_" << ++totalNumberOfInputChannels; | |||||
| inputPorts.add (juce::jack_port_register (client, inputName.toUTF8(), | |||||
| inputPorts.add (juce::jack_port_register (client, inputChannelName.toUTF8(), | |||||
| JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)); | ||||
| } | } | ||||
| @@ -190,10 +197,10 @@ public: | |||||
| const StringArray outputChannels (getOutputChannelNames()); | const StringArray outputChannels (getOutputChannelNames()); | ||||
| for (int i = 0; i < outputChannels.size(); ++i) | for (int i = 0; i < outputChannels.size(); ++i) | ||||
| { | { | ||||
| String outputName; | |||||
| outputName << "out_" << ++totalNumberOfOutputChannels; | |||||
| String outputChannelName; | |||||
| outputChannelName << "out_" << ++totalNumberOfOutputChannels; | |||||
| outputPorts.add (juce::jack_port_register (client, outputName.toUTF8(), | |||||
| outputPorts.add (juce::jack_port_register (client, outputChannelName.toUTF8(), | |||||
| JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)); | ||||
| } | } | ||||
| @@ -202,7 +209,7 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| ~JackAudioIODevice() | |||||
| ~JackAudioIODevice() override | |||||
| { | { | ||||
| close(); | close(); | ||||
| if (client != nullptr) | if (client != nullptr) | ||||
| @@ -212,19 +219,19 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| StringArray getChannelNames (bool forInput) const | |||||
| StringArray getChannelNames (const String& clientName, bool forInput) const | |||||
| { | { | ||||
| StringArray names; | StringArray names; | ||||
| for (JackPortIterator i (client, forInput); i.next();) | for (JackPortIterator i (client, forInput); i.next();) | ||||
| if (i.clientName == getName()) | |||||
| names.add (i.name.fromFirstOccurrenceOf (":", false, false)); | |||||
| if (i.getClientName() == clientName) | |||||
| names.add (i.getChannelName()); | |||||
| return names; | return names; | ||||
| } | } | ||||
| StringArray getOutputChannelNames() override { return getChannelNames (false); } | |||||
| StringArray getInputChannelNames() override { return getChannelNames (true); } | |||||
| StringArray getOutputChannelNames() override { return getChannelNames (outputName, true); } | |||||
| StringArray getInputChannelNames() override { return getChannelNames (inputName, false); } | |||||
| Array<double> getAvailableSampleRates() override | Array<double> getAvailableSampleRates() override | ||||
| { | { | ||||
| @@ -241,15 +248,29 @@ public: | |||||
| Array<int> sizes; | Array<int> sizes; | ||||
| if (client != nullptr) | if (client != nullptr) | ||||
| sizes.add (juce::jack_get_buffer_size (client)); | |||||
| sizes.add (static_cast<int> (juce::jack_get_buffer_size (client))); | |||||
| return sizes; | return sizes; | ||||
| } | } | ||||
| int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } | int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); } | ||||
| int getCurrentBufferSizeSamples() override { return client != nullptr ? juce::jack_get_buffer_size (client) : 0; } | |||||
| double getCurrentSampleRate() override { return client != nullptr ? juce::jack_get_sample_rate (client) : 0; } | |||||
| int getCurrentBufferSizeSamples() override { return client != nullptr ? static_cast<int> (juce::jack_get_buffer_size (client)) : 0; } | |||||
| double getCurrentSampleRate() override { return client != nullptr ? static_cast<int> (juce::jack_get_sample_rate (client)) : 0; } | |||||
| template <typename Fn> | |||||
| void forEachClientChannel (const String& clientName, bool isInput, Fn&& fn) | |||||
| { | |||||
| auto index = 0; | |||||
| for (JackPortIterator i (client, isInput); i.next();) | |||||
| { | |||||
| if (i.getClientName() != clientName) | |||||
| continue; | |||||
| fn (i.ports.get()[i.index], index); | |||||
| index += 1; | |||||
| } | |||||
| } | |||||
| String open (const BigInteger& inputChannels, const BigInteger& outputChannels, | String open (const BigInteger& inputChannels, const BigInteger& outputChannels, | ||||
| double /* sampleRate */, int /* bufferSizeSamples */) override | double /* sampleRate */, int /* bufferSizeSamples */) override | ||||
| @@ -263,38 +284,55 @@ public: | |||||
| lastError.clear(); | lastError.clear(); | ||||
| close(); | close(); | ||||
| xruns = 0; | |||||
| xruns.store (0, std::memory_order_relaxed); | |||||
| juce::jack_set_process_callback (client, processCallback, this); | juce::jack_set_process_callback (client, processCallback, this); | ||||
| juce::jack_set_port_connect_callback (client, portConnectCallback, this); | juce::jack_set_port_connect_callback (client, portConnectCallback, this); | ||||
| juce::jack_on_shutdown (client, shutdownCallback, this); | juce::jack_on_shutdown (client, shutdownCallback, this); | ||||
| juce::jack_on_info_shutdown (client, infoShutdownCallback, this); | |||||
| juce::jack_set_xrun_callback (client, xrunCallback, this); | juce::jack_set_xrun_callback (client, xrunCallback, this); | ||||
| juce::jack_activate (client); | juce::jack_activate (client); | ||||
| deviceIsOpen = true; | deviceIsOpen = true; | ||||
| if (! inputChannels.isZero()) | if (! inputChannels.isZero()) | ||||
| { | { | ||||
| for (JackPortIterator i (client, true); i.next();) | |||||
| forEachClientChannel (inputName, false, [&] (const char* portName, int index) | |||||
| { | { | ||||
| if (inputChannels [i.index] && i.clientName == getName()) | |||||
| { | |||||
| int error = juce::jack_connect (client, i.ports[i.index], juce::jack_port_name ((jack_port_t*) inputPorts[i.index])); | |||||
| if (error != 0) | |||||
| JUCE_JACK_LOG ("Cannot connect input port " + String (i.index) + " (" + i.name + "), error " + String (error)); | |||||
| } | |||||
| } | |||||
| if (! inputChannels[index]) | |||||
| return; | |||||
| jassert (index < inputPorts.size()); | |||||
| const auto* source = portName; | |||||
| const auto* inputPort = inputPorts[index]; | |||||
| jassert (juce::jack_port_flags (juce::jack_port_by_name (client, source)) & JackPortIsOutput); | |||||
| jassert (juce::jack_port_flags (inputPort) & JackPortIsInput); | |||||
| auto error = juce::jack_connect (client, source, juce::jack_port_name (inputPort)); | |||||
| if (error != 0) | |||||
| JUCE_JACK_LOG ("Cannot connect input port " + String (index) + " (" + portName + "), error " + String (error)); | |||||
| }); | |||||
| } | } | ||||
| if (! outputChannels.isZero()) | if (! outputChannels.isZero()) | ||||
| { | { | ||||
| for (JackPortIterator i (client, false); i.next();) | |||||
| forEachClientChannel (outputName, true, [&] (const char* portName, int index) | |||||
| { | { | ||||
| if (outputChannels [i.index] && i.clientName == getName()) | |||||
| { | |||||
| int error = juce::jack_connect (client, juce::jack_port_name ((jack_port_t*) outputPorts[i.index]), i.ports[i.index]); | |||||
| if (error != 0) | |||||
| JUCE_JACK_LOG ("Cannot connect output port " + String (i.index) + " (" + i.name + "), error " + String (error)); | |||||
| } | |||||
| } | |||||
| if (! outputChannels[index]) | |||||
| return; | |||||
| jassert (index < outputPorts.size()); | |||||
| const auto* outputPort = outputPorts[index]; | |||||
| const auto* destination = portName; | |||||
| jassert (juce::jack_port_flags (outputPort) & JackPortIsOutput); | |||||
| jassert (juce::jack_port_flags (juce::jack_port_by_name (client, destination)) & JackPortIsInput); | |||||
| auto error = juce::jack_connect (client, juce::jack_port_name (outputPort), destination); | |||||
| if (error != 0) | |||||
| JUCE_JACK_LOG ("Cannot connect output port " + String (index) + " (" + portName + "), error " + String (error)); | |||||
| }); | |||||
| } | } | ||||
| updateActivePorts(); | updateActivePorts(); | ||||
| @@ -308,12 +346,15 @@ public: | |||||
| if (client != nullptr) | if (client != nullptr) | ||||
| { | { | ||||
| juce::jack_deactivate (client); | |||||
| const auto result = juce::jack_deactivate (client); | |||||
| jassert (result == 0); | |||||
| ignoreUnused (result); | |||||
| juce::jack_set_xrun_callback (client, xrunCallback, nullptr); | juce::jack_set_xrun_callback (client, xrunCallback, nullptr); | ||||
| juce::jack_set_process_callback (client, processCallback, nullptr); | juce::jack_set_process_callback (client, processCallback, nullptr); | ||||
| juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); | juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); | ||||
| juce::jack_on_shutdown (client, shutdownCallback, nullptr); | juce::jack_on_shutdown (client, shutdownCallback, nullptr); | ||||
| juce::jack_on_info_shutdown (client, infoShutdownCallback, nullptr); | |||||
| } | } | ||||
| deviceIsOpen = false; | deviceIsOpen = false; | ||||
| @@ -347,7 +388,7 @@ public: | |||||
| bool isPlaying() override { return callback != nullptr; } | bool isPlaying() override { return callback != nullptr; } | ||||
| int getCurrentBitDepth() override { return 32; } | int getCurrentBitDepth() override { return 32; } | ||||
| String getLastError() override { return lastError; } | String getLastError() override { return lastError; } | ||||
| int getXRunCount() const noexcept override { return xruns; } | |||||
| int getXRunCount() const noexcept override { return xruns.load (std::memory_order_relaxed); } | |||||
| BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } | BigInteger getActiveOutputChannels() const override { return activeOutputChannels; } | ||||
| BigInteger getActiveInputChannels() const override { return activeInputChannels; } | BigInteger getActiveInputChannels() const override { return activeInputChannels; } | ||||
| @@ -357,7 +398,7 @@ public: | |||||
| int latency = 0; | int latency = 0; | ||||
| for (int i = 0; i < outputPorts.size(); i++) | for (int i = 0; i < outputPorts.size(); i++) | ||||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) outputPorts [i])); | |||||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, outputPorts[i])); | |||||
| return latency; | return latency; | ||||
| } | } | ||||
| @@ -367,14 +408,36 @@ public: | |||||
| int latency = 0; | int latency = 0; | ||||
| for (int i = 0; i < inputPorts.size(); i++) | for (int i = 0; i < inputPorts.size(); i++) | ||||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, (jack_port_t*) inputPorts [i])); | |||||
| latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, inputPorts[i])); | |||||
| return latency; | return latency; | ||||
| } | } | ||||
| String inputId, outputId; | |||||
| String inputName, outputName; | |||||
| private: | private: | ||||
| //============================================================================== | |||||
| class MainThreadDispatcher : private AsyncUpdater | |||||
| { | |||||
| public: | |||||
| explicit MainThreadDispatcher (JackAudioIODevice& device) : ref (device) {} | |||||
| ~MainThreadDispatcher() override { cancelPendingUpdate(); } | |||||
| void updateActivePorts() | |||||
| { | |||||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
| handleAsyncUpdate(); | |||||
| else | |||||
| triggerAsyncUpdate(); | |||||
| } | |||||
| private: | |||||
| void handleAsyncUpdate() override { ref.updateActivePorts(); } | |||||
| JackAudioIODevice& ref; | |||||
| }; | |||||
| //============================================================================== | |||||
| void process (const int numSamples) | void process (const int numSamples) | ||||
| { | { | ||||
| int numActiveInChans = 0, numActiveOutChans = 0; | int numActiveInChans = 0, numActiveOutChans = 0; | ||||
| @@ -382,17 +445,17 @@ private: | |||||
| for (int i = 0; i < totalNumberOfInputChannels; ++i) | for (int i = 0; i < totalNumberOfInputChannels; ++i) | ||||
| { | { | ||||
| if (activeInputChannels[i]) | if (activeInputChannels[i]) | ||||
| if (jack_default_audio_sample_t* in | |||||
| = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) inputPorts.getUnchecked(i), numSamples)) | |||||
| inChans [numActiveInChans++] = (float*) in; | |||||
| if (auto* in = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (inputPorts.getUnchecked (i), | |||||
| static_cast<jack_nframes_t> (numSamples))) | |||||
| inChans[numActiveInChans++] = (float*) in; | |||||
| } | } | ||||
| for (int i = 0; i < totalNumberOfOutputChannels; ++i) | for (int i = 0; i < totalNumberOfOutputChannels; ++i) | ||||
| { | { | ||||
| if (activeOutputChannels[i]) | if (activeOutputChannels[i]) | ||||
| if (jack_default_audio_sample_t* out | |||||
| = (jack_default_audio_sample_t*) juce::jack_port_get_buffer ((jack_port_t*) outputPorts.getUnchecked(i), numSamples)) | |||||
| outChans [numActiveOutChans++] = (float*) out; | |||||
| if (auto* out = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (outputPorts.getUnchecked (i), | |||||
| static_cast<jack_nframes_t> (numSamples))) | |||||
| outChans[numActiveOutChans++] = (float*) out; | |||||
| } | } | ||||
| const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
| @@ -406,14 +469,14 @@ private: | |||||
| else | else | ||||
| { | { | ||||
| for (int i = 0; i < numActiveOutChans; ++i) | for (int i = 0; i < numActiveOutChans; ++i) | ||||
| zeromem (outChans[i], sizeof (float) * numSamples); | |||||
| zeromem (outChans[i], static_cast<size_t> (numSamples) * sizeof (float)); | |||||
| } | } | ||||
| } | } | ||||
| static int processCallback (jack_nframes_t nframes, void* callbackArgument) | static int processCallback (jack_nframes_t nframes, void* callbackArgument) | ||||
| { | { | ||||
| if (callbackArgument != nullptr) | if (callbackArgument != nullptr) | ||||
| ((JackAudioIODevice*) callbackArgument)->process (nframes); | |||||
| ((JackAudioIODevice*) callbackArgument)->process (static_cast<int> (nframes)); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| @@ -431,11 +494,11 @@ private: | |||||
| BigInteger newOutputChannels, newInputChannels; | BigInteger newOutputChannels, newInputChannels; | ||||
| for (int i = 0; i < outputPorts.size(); ++i) | for (int i = 0; i < outputPorts.size(); ++i) | ||||
| if (juce::jack_port_connected ((jack_port_t*) outputPorts.getUnchecked(i))) | |||||
| if (juce::jack_port_connected (outputPorts.getUnchecked (i))) | |||||
| newOutputChannels.setBit (i); | newOutputChannels.setBit (i); | ||||
| for (int i = 0; i < inputPorts.size(); ++i) | for (int i = 0; i < inputPorts.size(); ++i) | ||||
| if (juce::jack_port_connected ((jack_port_t*) inputPorts.getUnchecked(i))) | |||||
| if (juce::jack_port_connected (inputPorts.getUnchecked (i))) | |||||
| newInputChannels.setBit (i); | newInputChannels.setBit (i); | ||||
| if (newOutputChannels != activeOutputChannels | if (newOutputChannels != activeOutputChannels | ||||
| @@ -451,14 +514,15 @@ private: | |||||
| if (oldCallback != nullptr) | if (oldCallback != nullptr) | ||||
| start (oldCallback); | start (oldCallback); | ||||
| sendDeviceChangedCallback(); | |||||
| if (notifyChannelsChanged != nullptr) | |||||
| notifyChannelsChanged(); | |||||
| } | } | ||||
| } | } | ||||
| static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | ||||
| { | { | ||||
| if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg)) | if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg)) | ||||
| device->updateActivePorts(); | |||||
| device->mainThreadDispatcher.updateActivePorts(); | |||||
| } | } | ||||
| static void threadInitCallback (void* /* callbackArgument */) | static void threadInitCallback (void* /* callbackArgument */) | ||||
| @@ -477,81 +541,76 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| static void infoShutdownCallback (jack_status_t code, const char* reason, void* arg) | |||||
| { | |||||
| jassert (code == 0); | |||||
| ignoreUnused (code); | |||||
| JUCE_JACK_LOG ("Shutting down with message:"); | |||||
| JUCE_JACK_LOG (reason); | |||||
| ignoreUnused (reason); | |||||
| shutdownCallback (arg); | |||||
| } | |||||
| static void errorCallback (const char* msg) | static void errorCallback (const char* msg) | ||||
| { | { | ||||
| JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg)); | JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg)); | ||||
| ignoreUnused (msg); | |||||
| } | } | ||||
| static void sendDeviceChangedCallback(); | |||||
| bool deviceIsOpen; | |||||
| jack_client_t* client; | |||||
| bool deviceIsOpen = false; | |||||
| jack_client_t* client = nullptr; | |||||
| String lastError; | String lastError; | ||||
| AudioIODeviceCallback* callback; | |||||
| AudioIODeviceCallback* callback = nullptr; | |||||
| CriticalSection callbackLock; | CriticalSection callbackLock; | ||||
| HeapBlock<float*> inChans, outChans; | HeapBlock<float*> inChans, outChans; | ||||
| int totalNumberOfInputChannels; | |||||
| int totalNumberOfOutputChannels; | |||||
| Array<void*> inputPorts, outputPorts; | |||||
| int totalNumberOfInputChannels = 0; | |||||
| int totalNumberOfOutputChannels = 0; | |||||
| Array<jack_port_t*> inputPorts, outputPorts; | |||||
| BigInteger activeInputChannels, activeOutputChannels; | BigInteger activeInputChannels, activeOutputChannels; | ||||
| int xruns; | |||||
| }; | |||||
| std::atomic<int> xruns { 0 }; | |||||
| std::function<void()> notifyChannelsChanged; | |||||
| MainThreadDispatcher mainThreadDispatcher { *this }; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| class JackAudioIODeviceType; | |||||
| class JackAudioIODeviceType : public AudioIODeviceType | class JackAudioIODeviceType : public AudioIODeviceType | ||||
| { | { | ||||
| public: | public: | ||||
| JackAudioIODeviceType() | JackAudioIODeviceType() | ||||
| : AudioIODeviceType ("JACK"), | |||||
| hasScanned (false) | |||||
| { | |||||
| activeDeviceTypes.add (this); | |||||
| } | |||||
| ~JackAudioIODeviceType() | |||||
| { | |||||
| activeDeviceTypes.removeFirstMatchingValue (this); | |||||
| } | |||||
| : AudioIODeviceType ("JACK") | |||||
| {} | |||||
| void scanForDevices() | void scanForDevices() | ||||
| { | { | ||||
| hasScanned = true; | hasScanned = true; | ||||
| inputNames.clear(); | inputNames.clear(); | ||||
| inputIds.clear(); | |||||
| outputNames.clear(); | outputNames.clear(); | ||||
| outputIds.clear(); | |||||
| if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); | if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); | ||||
| if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | ||||
| if (juce_libjackHandle == nullptr) return; | if (juce_libjackHandle == nullptr) return; | ||||
| jack_status_t status; | |||||
| jack_status_t status = {}; | |||||
| // open a dummy client | // open a dummy client | ||||
| if (jack_client_t* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) | |||||
| if (auto* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status)) | |||||
| { | { | ||||
| // scan for output devices | // scan for output devices | ||||
| for (JackPortIterator i (client, false); i.next();) | for (JackPortIterator i (client, false); i.next();) | ||||
| { | |||||
| if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.clientName)) | |||||
| { | |||||
| inputNames.add (i.clientName); | |||||
| inputIds.add (i.ports [i.index]); | |||||
| } | |||||
| } | |||||
| if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.getClientName())) | |||||
| inputNames.add (i.getClientName()); | |||||
| // scan for input devices | // scan for input devices | ||||
| for (JackPortIterator i (client, true); i.next();) | for (JackPortIterator i (client, true); i.next();) | ||||
| { | |||||
| if (i.clientName != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.clientName)) | |||||
| { | |||||
| outputNames.add (i.clientName); | |||||
| outputIds.add (i.ports [i.index]); | |||||
| } | |||||
| } | |||||
| if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.getClientName())) | |||||
| outputNames.add (i.getClientName()); | |||||
| juce::jack_client_close (client); | juce::jack_client_close (client); | ||||
| } | } | ||||
| @@ -580,8 +639,8 @@ public: | |||||
| jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
| if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device)) | if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device)) | ||||
| return asInput ? inputIds.indexOf (d->inputId) | |||||
| : outputIds.indexOf (d->outputId); | |||||
| return asInput ? inputNames.indexOf (d->inputName) | |||||
| : outputNames.indexOf (d->outputName); | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| @@ -595,34 +654,17 @@ public: | |||||
| const int outputIndex = outputNames.indexOf (outputDeviceName); | const int outputIndex = outputNames.indexOf (outputDeviceName); | ||||
| if (inputIndex >= 0 || outputIndex >= 0) | if (inputIndex >= 0 || outputIndex >= 0) | ||||
| return new JackAudioIODevice (outputIndex >= 0 ? outputDeviceName | |||||
| : inputDeviceName, | |||||
| inputIds [inputIndex], | |||||
| outputIds [outputIndex]); | |||||
| return new JackAudioIODevice (inputDeviceName, outputDeviceName, | |||||
| [this] { callDeviceChangeListeners(); }); | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| void portConnectionChange() { callDeviceChangeListeners(); } | |||||
| private: | private: | ||||
| StringArray inputNames, outputNames, inputIds, outputIds; | |||||
| bool hasScanned; | |||||
| StringArray inputNames, outputNames; | |||||
| bool hasScanned = false; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType) | ||||
| }; | }; | ||||
| void JackAudioIODevice::sendDeviceChangedCallback() | |||||
| { | |||||
| for (int i = activeDeviceTypes.size(); --i >= 0;) | |||||
| if (JackAudioIODeviceType* d = activeDeviceTypes[i]) | |||||
| d->portConnectionChange(); | |||||
| } | |||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() | |||||
| { | |||||
| return new JackAudioIODeviceType(); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -25,10 +25,6 @@ namespace juce | |||||
| #if JUCE_ALSA | #if JUCE_ALSA | ||||
| //============================================================================== | |||||
| namespace | |||||
| { | |||||
| //============================================================================== | //============================================================================== | ||||
| class AlsaClient : public ReferenceCountedObject | class AlsaClient : public ReferenceCountedObject | ||||
| { | { | ||||
| @@ -453,9 +449,23 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, | |||||
| return port; | return port; | ||||
| } | } | ||||
| } // namespace | |||||
| struct AlsaPortPtr | |||||
| { | |||||
| explicit AlsaPortPtr (AlsaClient::Port* p) | |||||
| : ptr (p) {} | |||||
| ~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); } | |||||
| AlsaClient::Port* ptr = nullptr; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| class MidiInput::Pimpl : public AlsaPortPtr | |||||
| { | |||||
| public: | |||||
| using AlsaPortPtr::AlsaPortPtr; | |||||
| }; | |||||
| Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | ||||
| { | { | ||||
| Array<MidiDeviceInfo> devices; | Array<MidiDeviceInfo> devices; | ||||
| @@ -485,7 +495,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (port->portName, deviceIdentifier)); | std::unique_ptr<MidiInput> midiInput (new MidiInput (port->portName, deviceIdentifier)); | ||||
| port->setupInput (midiInput.get(), callback); | port->setupInput (midiInput.get(), callback); | ||||
| midiInput->internal = port; | |||||
| midiInput->internal = std::make_unique<Pimpl> (port); | |||||
| return midiInput; | return midiInput; | ||||
| } | } | ||||
| @@ -501,7 +511,7 @@ std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, | |||||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | ||||
| port->setupInput (midiInput.get(), callback); | port->setupInput (midiInput.get(), callback); | ||||
| midiInput->internal = port; | |||||
| midiInput->internal = std::make_unique<Pimpl> (port); | |||||
| return midiInput; | return midiInput; | ||||
| } | } | ||||
| @@ -536,20 +546,25 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
| MidiInput::~MidiInput() | MidiInput::~MidiInput() | ||||
| { | { | ||||
| stop(); | stop(); | ||||
| AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
| } | } | ||||
| void MidiInput::start() | void MidiInput::start() | ||||
| { | { | ||||
| static_cast<AlsaClient::Port*> (internal)->enableCallback (true); | |||||
| internal->ptr->enableCallback (true); | |||||
| } | } | ||||
| void MidiInput::stop() | void MidiInput::stop() | ||||
| { | { | ||||
| static_cast<AlsaClient::Port*> (internal)->enableCallback (false); | |||||
| internal->ptr->enableCallback (false); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| class MidiOutput::Pimpl : public AlsaPortPtr | |||||
| { | |||||
| public: | |||||
| using AlsaPortPtr::AlsaPortPtr; | |||||
| }; | |||||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | ||||
| { | { | ||||
| Array<MidiDeviceInfo> devices; | Array<MidiDeviceInfo> devices; | ||||
| @@ -577,7 +592,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->portName, deviceIdentifier)); | std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->portName, deviceIdentifier)); | ||||
| port->setupOutput(); | port->setupOutput(); | ||||
| midiOutput->internal = port; | |||||
| midiOutput->internal = std::make_unique<Pimpl> (port); | |||||
| return midiOutput; | return midiOutput; | ||||
| } | } | ||||
| @@ -593,7 +608,7 @@ std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceNam | |||||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->portId))); | ||||
| port->setupOutput(); | port->setupOutput(); | ||||
| midiOutput->internal = port; | |||||
| midiOutput->internal = std::make_unique<Pimpl> (port); | |||||
| return midiOutput; | return midiOutput; | ||||
| } | } | ||||
| @@ -623,17 +638,18 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||||
| MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
| { | { | ||||
| stopBackgroundThread(); | stopBackgroundThread(); | ||||
| AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||||
| } | } | ||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | void MidiOutput::sendMessageNow (const MidiMessage& message) | ||||
| { | { | ||||
| static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||||
| internal->ptr->sendMessageNow (message); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| #else | #else | ||||
| class MidiInput::Pimpl {}; | |||||
| // (These are just stub functions if ALSA is unavailable...) | // (These are just stub functions if ALSA is unavailable...) | ||||
| MidiInput::MidiInput (const String& deviceName, const String& deviceID) | MidiInput::MidiInput (const String& deviceName, const String& deviceID) | ||||
| : deviceInfo (deviceName, deviceID) | : deviceInfo (deviceName, deviceID) | ||||
| @@ -651,6 +667,8 @@ StringArray MidiInput::getDevices() | |||||
| int MidiInput::getDefaultDeviceIndex() { return 0;} | int MidiInput::getDefaultDeviceIndex() { return 0;} | ||||
| std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; } | std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; } | ||||
| class MidiOutput::Pimpl {}; | |||||
| MidiOutput::~MidiOutput() {} | MidiOutput::~MidiOutput() {} | ||||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | void MidiOutput::sendMessageNow (const MidiMessage&) {} | ||||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | ||||
| @@ -2234,12 +2234,6 @@ private: | |||||
| }; | }; | ||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() | |||||
| { | |||||
| return new CoreAudioClasses::CoreAudioIODeviceType(); | |||||
| } | |||||
| #undef JUCE_COREAUDIOLOG | #undef JUCE_COREAUDIOLOG | ||||
| } // namespace juce | } // namespace juce | ||||
| @@ -393,6 +393,12 @@ namespace CoreMidiHelpers | |||||
| } | } | ||||
| } | } | ||||
| class MidiInput::Pimpl : public CoreMidiHelpers::MidiPortAndCallback | |||||
| { | |||||
| public: | |||||
| using MidiPortAndCallback::MidiPortAndCallback; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | Array<MidiDeviceInfo> MidiInput::getAvailableDevices() | ||||
| { | { | ||||
| @@ -424,7 +430,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | if (CHECK_ERROR (MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &cfName.cfString))) | ||||
| { | { | ||||
| MIDIPortRef port; | MIDIPortRef port; | ||||
| auto mpc = std::make_unique<MidiPortAndCallback> (*callback); | |||||
| auto mpc = std::make_unique<Pimpl> (*callback); | |||||
| if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port))) | if (CHECK_ERROR (MIDIInputPortCreate (client, cfName.cfString, midiInputProc, mpc.get(), &port))) | ||||
| { | { | ||||
| @@ -435,10 +441,11 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (endpointInfo.name, endpointInfo.identifier)); | std::unique_ptr<MidiInput> midiInput (new MidiInput (endpointInfo.name, endpointInfo.identifier)); | ||||
| mpc->input = midiInput.get(); | mpc->input = midiInput.get(); | ||||
| midiInput->internal = mpc.get(); | |||||
| auto* ptr = mpc.get(); | |||||
| midiInput->internal = std::move (mpc); | |||||
| const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
| activeCallbacks.add (mpc.release()); | |||||
| activeCallbacks.add (ptr); | |||||
| return midiInput; | return midiInput; | ||||
| } | } | ||||
| @@ -462,7 +469,7 @@ std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, | |||||
| if (auto client = getGlobalMidiClient()) | if (auto client = getGlobalMidiClient()) | ||||
| { | { | ||||
| auto mpc = std::make_unique<MidiPortAndCallback> (*callback); | |||||
| auto mpc = std::make_unique<Pimpl> (*callback); | |||||
| mpc->active = false; | mpc->active = false; | ||||
| MIDIEndpointRef endpoint; | MIDIEndpointRef endpoint; | ||||
| @@ -491,10 +498,11 @@ std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, | |||||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, String (deviceIdentifier))); | std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, String (deviceIdentifier))); | ||||
| mpc->input = midiInput.get(); | mpc->input = midiInput.get(); | ||||
| midiInput->internal = mpc.get(); | |||||
| auto* ptr = mpc.get(); | |||||
| midiInput->internal = std::move (mpc); | |||||
| const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
| activeCallbacks.add (mpc.release()); | |||||
| activeCallbacks.add (ptr); | |||||
| return midiInput; | return midiInput; | ||||
| } | } | ||||
| @@ -529,24 +537,27 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
| { | { | ||||
| } | } | ||||
| MidiInput::~MidiInput() | |||||
| { | |||||
| delete static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal); | |||||
| } | |||||
| MidiInput::~MidiInput() = default; | |||||
| void MidiInput::start() | void MidiInput::start() | ||||
| { | { | ||||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | const ScopedLock sl (CoreMidiHelpers::callbackLock); | ||||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||||
| internal->active = true; | |||||
| } | } | ||||
| void MidiInput::stop() | void MidiInput::stop() | ||||
| { | { | ||||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | const ScopedLock sl (CoreMidiHelpers::callbackLock); | ||||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = false; | |||||
| internal->active = false; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| class MidiOutput::Pimpl : public CoreMidiHelpers::MidiPortAndEndpoint | |||||
| { | |||||
| public: | |||||
| using MidiPortAndEndpoint::MidiPortAndEndpoint; | |||||
| }; | |||||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | ||||
| { | { | ||||
| return CoreMidiHelpers::findDevices (false); | return CoreMidiHelpers::findDevices (false); | ||||
| @@ -581,7 +592,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||||
| if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) | if (CHECK_ERROR (MIDIOutputPortCreate (client, cfName.cfString, &port))) | ||||
| { | { | ||||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); | std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (endpointInfo.name, endpointInfo.identifier)); | ||||
| midiOutput->internal = new MidiPortAndEndpoint (port, endpoint); | |||||
| midiOutput->internal = std::make_unique<Pimpl> (port, endpoint); | |||||
| return midiOutput; | return midiOutput; | ||||
| } | } | ||||
| @@ -622,7 +633,7 @@ std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceNam | |||||
| if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) deviceIdentifier))) | ||||
| { | { | ||||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); | std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, String (deviceIdentifier))); | ||||
| midiOutput->internal = new MidiPortAndEndpoint (0, endpoint); | |||||
| midiOutput->internal = std::make_unique<Pimpl> ((UInt32) 0, endpoint); | |||||
| return midiOutput; | return midiOutput; | ||||
| } | } | ||||
| @@ -655,8 +666,6 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||||
| MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
| { | { | ||||
| stopBackgroundThread(); | stopBackgroundThread(); | ||||
| delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal); | |||||
| } | } | ||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | void MidiOutput::sendMessageNow (const MidiMessage& message) | ||||
| @@ -715,7 +724,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||||
| return; | return; | ||||
| } | } | ||||
| static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | |||||
| internal->send (packetToSend); | |||||
| } | } | ||||
| #undef CHECK_ERROR | #undef CHECK_ERROR | ||||
| @@ -504,7 +504,7 @@ public: | |||||
| { | { | ||||
| inBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * n); | inBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * n); | ||||
| ASIOChannelInfo channelInfo = { 0 }; | |||||
| ASIOChannelInfo channelInfo = {}; | |||||
| channelInfo.channel = i; | channelInfo.channel = i; | ||||
| channelInfo.isInput = 1; | channelInfo.isInput = 1; | ||||
| asioObject->getChannelInfo (&channelInfo); | asioObject->getChannelInfo (&channelInfo); | ||||
| @@ -526,7 +526,7 @@ public: | |||||
| { | { | ||||
| outBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * (numActiveInputChans + n)); | outBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * (numActiveInputChans + n)); | ||||
| ASIOChannelInfo channelInfo = { 0 }; | |||||
| ASIOChannelInfo channelInfo = {}; | |||||
| channelInfo.channel = i; | channelInfo.channel = i; | ||||
| channelInfo.isInput = 0; | channelInfo.isInput = 0; | ||||
| asioObject->getChannelInfo (&channelInfo); | asioObject->getChannelInfo (&channelInfo); | ||||
| @@ -673,10 +673,10 @@ public: | |||||
| lastCallback->audioDeviceStopped(); | lastCallback->audioDeviceStopped(); | ||||
| } | } | ||||
| String getLastError() { return error; } | |||||
| bool hasControlPanel() const { return true; } | |||||
| String getLastError() override { return error; } | |||||
| bool hasControlPanel() const override { return true; } | |||||
| bool showControlPanel() | |||||
| bool showControlPanel() override | |||||
| { | { | ||||
| JUCE_ASIO_LOG ("showing control panel"); | JUCE_ASIO_LOG ("showing control panel"); | ||||
| @@ -767,7 +767,7 @@ private: | |||||
| bool deviceIsOpen = false, isStarted = false, buffersCreated = false; | bool deviceIsOpen = false, isStarted = false, buffersCreated = false; | ||||
| std::atomic<bool> calledback { false }; | std::atomic<bool> calledback { false }; | ||||
| bool littleEndian = false, postOutput = true, needToReset = false; | |||||
| bool postOutput = true, needToReset = false; | |||||
| bool insideControlPanelModalLoop = false; | bool insideControlPanelModalLoop = false; | ||||
| bool shouldUsePreferredSize = false; | bool shouldUsePreferredSize = false; | ||||
| int xruns = 0; | int xruns = 0; | ||||
| @@ -785,7 +785,7 @@ private: | |||||
| String getChannelName (int index, bool isInput) const | String getChannelName (int index, bool isInput) const | ||||
| { | { | ||||
| ASIOChannelInfo channelInfo = { 0 }; | |||||
| ASIOChannelInfo channelInfo = {}; | |||||
| channelInfo.channel = index; | channelInfo.channel = index; | ||||
| channelInfo.isInput = isInput ? 1 : 0; | channelInfo.isInput = isInput ? 1 : 0; | ||||
| asioObject->getChannelInfo (&channelInfo); | asioObject->getChannelInfo (&channelInfo); | ||||
| @@ -1065,7 +1065,7 @@ private: | |||||
| for (int i = 0; i < totalNumOutputChans; ++i) | for (int i = 0; i < totalNumOutputChans; ++i) | ||||
| { | { | ||||
| ASIOChannelInfo channelInfo = { 0 }; | |||||
| ASIOChannelInfo channelInfo = {}; | |||||
| channelInfo.channel = i; | channelInfo.channel = i; | ||||
| channelInfo.isInput = 0; | channelInfo.isInput = 0; | ||||
| asioObject->getChannelInfo (&channelInfo); | asioObject->getChannelInfo (&channelInfo); | ||||
| @@ -1640,9 +1640,4 @@ void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type) | |||||
| type->sendDeviceChangeToListeners(); | type->sendDeviceChangeToListeners(); | ||||
| } | } | ||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() | |||||
| { | |||||
| return new ASIOAudioIODeviceType(); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -1282,10 +1282,4 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() | |||||
| { | |||||
| return new DSoundAudioIODeviceType(); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -29,37 +29,39 @@ | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| struct MidiServiceType | |||||
| class MidiInput::Pimpl | |||||
| { | { | ||||
| struct InputWrapper | |||||
| { | |||||
| virtual ~InputWrapper() {} | |||||
| public: | |||||
| virtual ~Pimpl() noexcept = default; | |||||
| virtual String getDeviceIdentifier() = 0; | |||||
| virtual String getDeviceName() = 0; | |||||
| virtual String getDeviceIdentifier() = 0; | |||||
| virtual String getDeviceName() = 0; | |||||
| virtual void start() = 0; | |||||
| virtual void stop() = 0; | |||||
| }; | |||||
| virtual void start() = 0; | |||||
| virtual void stop() = 0; | |||||
| }; | |||||
| struct OutputWrapper | |||||
| { | |||||
| virtual ~OutputWrapper() {} | |||||
| class MidiOutput::Pimpl | |||||
| { | |||||
| public: | |||||
| virtual ~Pimpl() noexcept = default; | |||||
| virtual String getDeviceIdentifier() = 0; | |||||
| virtual String getDeviceName() = 0; | |||||
| virtual String getDeviceIdentifier() = 0; | |||||
| virtual String getDeviceName() = 0; | |||||
| virtual void sendMessageNow (const MidiMessage&) = 0; | |||||
| }; | |||||
| virtual void sendMessageNow (const MidiMessage&) = 0; | |||||
| }; | |||||
| MidiServiceType() {} | |||||
| virtual ~MidiServiceType() {} | |||||
| struct MidiServiceType | |||||
| { | |||||
| MidiServiceType() = default; | |||||
| virtual ~MidiServiceType() noexcept = default; | |||||
| virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0; | virtual Array<MidiDeviceInfo> getAvailableDevices (bool) = 0; | ||||
| virtual MidiDeviceInfo getDefaultDevice (bool) = 0; | virtual MidiDeviceInfo getDefaultDevice (bool) = 0; | ||||
| virtual InputWrapper* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; | |||||
| virtual OutputWrapper* createOutputWrapper (const String&) = 0; | |||||
| virtual MidiInput::Pimpl* createInputWrapper (MidiInput&, const String&, MidiInputCallback&) = 0; | |||||
| virtual MidiOutput::Pimpl* createOutputWrapper (const String&) = 0; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiServiceType) | ||||
| }; | }; | ||||
| @@ -82,12 +84,12 @@ struct Win32MidiService : public MidiServiceType, | |||||
| : Win32OutputWrapper::getDefaultDevice(); | : Win32OutputWrapper::getDefaultDevice(); | ||||
| } | } | ||||
| InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
| MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
| { | { | ||||
| return new Win32InputWrapper (*this, input, deviceIdentifier, callback); | return new Win32InputWrapper (*this, input, deviceIdentifier, callback); | ||||
| } | } | ||||
| OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||||
| MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override | |||||
| { | { | ||||
| return new Win32OutputWrapper (*this, deviceIdentifier); | return new Win32OutputWrapper (*this, deviceIdentifier); | ||||
| } | } | ||||
| @@ -384,7 +386,7 @@ private: | |||||
| } | } | ||||
| }; | }; | ||||
| struct Win32InputWrapper : public InputWrapper, | |||||
| struct Win32InputWrapper : public MidiInput::Pimpl, | |||||
| public Win32MidiDeviceQuery<Win32InputWrapper> | public Win32MidiDeviceQuery<Win32InputWrapper> | ||||
| { | { | ||||
| Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c) | Win32InputWrapper (Win32MidiService& parentService, MidiInput& midiInput, const String& deviceIdentifier, MidiInputCallback& c) | ||||
| @@ -508,7 +510,7 @@ private: | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| struct Win32OutputWrapper : public OutputWrapper, | |||||
| struct Win32OutputWrapper : public MidiOutput::Pimpl, | |||||
| public Win32MidiDeviceQuery<Win32OutputWrapper> | public Win32MidiDeviceQuery<Win32OutputWrapper> | ||||
| { | { | ||||
| Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) | Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) | ||||
| @@ -763,12 +765,12 @@ public: | |||||
| : outputDeviceWatcher->getDefaultDevice(); | : outputDeviceWatcher->getDefaultDevice(); | ||||
| } | } | ||||
| InputWrapper* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
| MidiInput::Pimpl* createInputWrapper (MidiInput& input, const String& deviceIdentifier, MidiInputCallback& callback) override | |||||
| { | { | ||||
| return new WinRTInputWrapper (*this, input, deviceIdentifier, callback); | return new WinRTInputWrapper (*this, input, deviceIdentifier, callback); | ||||
| } | } | ||||
| OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||||
| MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override | |||||
| { | { | ||||
| return new WinRTOutputWrapper (*this, deviceIdentifier); | return new WinRTOutputWrapper (*this, deviceIdentifier); | ||||
| } | } | ||||
| @@ -1554,7 +1556,7 @@ private: | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| struct WinRTInputWrapper final : public InputWrapper, | |||||
| struct WinRTInputWrapper final : public MidiInput::Pimpl, | |||||
| private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort> | private WinRTIOWrapper<IMidiInPortStatics, IMidiInPort> | ||||
| { | { | ||||
| @@ -1708,7 +1710,7 @@ private: | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| struct WinRTOutputWrapper final : public OutputWrapper, | |||||
| struct WinRTOutputWrapper final : public MidiOutput::Pimpl, | |||||
| private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> | private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> | ||||
| { | { | ||||
| WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) | WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) | ||||
| @@ -1865,7 +1867,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| return {}; | return {}; | ||||
| std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier)); | std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier)); | ||||
| std::unique_ptr<MidiServiceType::InputWrapper> wrapper; | |||||
| std::unique_ptr<Pimpl> wrapper; | |||||
| try | try | ||||
| { | { | ||||
| @@ -1877,7 +1879,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||||
| } | } | ||||
| in->setName (wrapper->getDeviceName()); | in->setName (wrapper->getDeviceName()); | ||||
| in->internal = wrapper.release(); | |||||
| in->internal = std::move (wrapper); | |||||
| return in; | return in; | ||||
| } | } | ||||
| @@ -1907,13 +1909,10 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||||
| { | { | ||||
| } | } | ||||
| MidiInput::~MidiInput() | |||||
| { | |||||
| delete static_cast<MidiServiceType::InputWrapper*> (internal); | |||||
| } | |||||
| MidiInput::~MidiInput() = default; | |||||
| void MidiInput::start() { static_cast<MidiServiceType::InputWrapper*> (internal)->start(); } | |||||
| void MidiInput::stop() { static_cast<MidiServiceType::InputWrapper*> (internal)->stop(); } | |||||
| void MidiInput::start() { internal->start(); } | |||||
| void MidiInput::stop() { internal->stop(); } | |||||
| //============================================================================== | //============================================================================== | ||||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() | ||||
| @@ -1931,7 +1930,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||||
| if (deviceIdentifier.isEmpty()) | if (deviceIdentifier.isEmpty()) | ||||
| return {}; | return {}; | ||||
| std::unique_ptr<MidiServiceType::OutputWrapper> wrapper; | |||||
| std::unique_ptr<Pimpl> wrapper; | |||||
| try | try | ||||
| { | { | ||||
| @@ -1945,7 +1944,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||||
| std::unique_ptr<MidiOutput> out; | std::unique_ptr<MidiOutput> out; | ||||
| out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); | out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); | ||||
| out->internal = wrapper.release(); | |||||
| out->internal = std::move (wrapper); | |||||
| return out; | return out; | ||||
| } | } | ||||
| @@ -1973,12 +1972,11 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||||
| MidiOutput::~MidiOutput() | MidiOutput::~MidiOutput() | ||||
| { | { | ||||
| stopBackgroundThread(); | stopBackgroundThread(); | ||||
| delete static_cast<MidiServiceType::OutputWrapper*> (internal); | |||||
| } | } | ||||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | void MidiOutput::sendMessageNow (const MidiMessage& message) | ||||
| { | { | ||||
| static_cast<MidiServiceType::OutputWrapper*> (internal)->sendMessageNow (message); | |||||
| internal->sendMessageNow (message); | |||||
| } | } | ||||
| } // namespace juce | } // namespace juce | ||||
| @@ -208,6 +208,29 @@ enum AUDCLNT_SHAREMODE | |||||
| AUDCLNT_SHAREMODE_EXCLUSIVE | AUDCLNT_SHAREMODE_EXCLUSIVE | ||||
| }; | }; | ||||
| enum AUDIO_STREAM_CATEGORY | |||||
| { | |||||
| AudioCategory_Other = 0, | |||||
| AudioCategory_ForegroundOnlyMedia, | |||||
| AudioCategory_BackgroundCapableMedia, | |||||
| AudioCategory_Communications, | |||||
| AudioCategory_Alerts, | |||||
| AudioCategory_SoundEffects, | |||||
| AudioCategory_GameEffects, | |||||
| AudioCategory_GameMedia, | |||||
| AudioCategory_GameChat, | |||||
| AudioCategory_Speech, | |||||
| AudioCategory_Movie, | |||||
| AudioCategory_Media | |||||
| }; | |||||
| struct AudioClientProperties | |||||
| { | |||||
| UINT32 cbSize; | |||||
| BOOL bIsOffload; | |||||
| AUDIO_STREAM_CATEGORY eCategory; | |||||
| }; | |||||
| JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") | JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") | ||||
| { | { | ||||
| JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0; | JUCE_COMCALL Initialize (AUDCLNT_SHAREMODE, DWORD, REFERENCE_TIME, REFERENCE_TIME, const WAVEFORMATEX*, LPCGUID) = 0; | ||||
| @@ -224,6 +247,20 @@ JUCE_IUNKNOWNCLASS (IAudioClient, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") | |||||
| JUCE_COMCALL GetService (REFIID, void**) = 0; | JUCE_COMCALL GetService (REFIID, void**) = 0; | ||||
| }; | }; | ||||
| JUCE_COMCLASS (IAudioClient2, "726778CD-F60A-4eda-82DE-E47610CD78AA") : public IAudioClient | |||||
| { | |||||
| JUCE_COMCALL IsOffloadCapable (AUDIO_STREAM_CATEGORY, BOOL*) = 0; | |||||
| JUCE_COMCALL SetClientProperties (const AudioClientProperties*) = 0; | |||||
| JUCE_COMCALL GetBufferSizeLimits (const WAVEFORMATEX*, BOOL, REFERENCE_TIME*, REFERENCE_TIME*) = 0; | |||||
| }; | |||||
| JUCE_COMCLASS (IAudioClient3, "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2") : public IAudioClient2 | |||||
| { | |||||
| JUCE_COMCALL GetSharedModeEnginePeriod (const WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32*) = 0; | |||||
| JUCE_COMCALL GetCurrentSharedModeEnginePeriod (WAVEFORMATEX**, UINT32*) = 0; | |||||
| JUCE_COMCALL InitializeSharedAudioStream (DWORD, UINT32, const WAVEFORMATEX*, LPCGUID) = 0; | |||||
| }; | |||||
| JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317") | JUCE_IUNKNOWNCLASS (IAudioCaptureClient, "C8ADBD64-E71E-48a0-A4DE-185C395CD317") | ||||
| { | { | ||||
| JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0; | JUCE_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0; | ||||
| @@ -322,87 +359,76 @@ String getDeviceID (IMMDevice* device) | |||||
| return s; | return s; | ||||
| } | } | ||||
| EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device) | |||||
| static EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device) | |||||
| { | { | ||||
| EDataFlow flow = eRender; | EDataFlow flow = eRender; | ||||
| ComSmartPtr<IMMEndpoint> endPoint; | |||||
| if (check (device.QueryInterface (endPoint))) | |||||
| (void) check (endPoint->GetDataFlow (&flow)); | |||||
| if (auto endpoint = device.getInterface<IMMEndpoint>()) | |||||
| (void) check (endpoint->GetDataFlow (&flow)); | |||||
| return flow; | return flow; | ||||
| } | } | ||||
| int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept | |||||
| static int refTimeToSamples (const REFERENCE_TIME& t, double sampleRate) noexcept | |||||
| { | { | ||||
| return roundToInt (sampleRate * ((double) t) * 0.0000001); | return roundToInt (sampleRate * ((double) t) * 0.0000001); | ||||
| } | } | ||||
| REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept | |||||
| static REFERENCE_TIME samplesToRefTime (int numSamples, double sampleRate) noexcept | |||||
| { | { | ||||
| return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); | return (REFERENCE_TIME) ((numSamples * 10000.0 * 1000.0 / sampleRate) + 0.5); | ||||
| } | } | ||||
| void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept | |||||
| static void copyWavFormat (WAVEFORMATEXTENSIBLE& dest, const WAVEFORMATEX* src) noexcept | |||||
| { | { | ||||
| memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE) | memcpy (&dest, src, src->wFormatTag == WAVE_FORMAT_EXTENSIBLE ? sizeof (WAVEFORMATEXTENSIBLE) | ||||
| : sizeof (WAVEFORMATEX)); | : sizeof (WAVEFORMATEX)); | ||||
| } | } | ||||
| static bool isExclusiveMode (WASAPIDeviceMode deviceMode) noexcept | |||||
| { | |||||
| return deviceMode == WASAPIDeviceMode::exclusive; | |||||
| } | |||||
| static bool isLowLatencyMode (WASAPIDeviceMode deviceMode) noexcept | |||||
| { | |||||
| return deviceMode == WASAPIDeviceMode::sharedLowLatency; | |||||
| } | |||||
| static bool supportsSampleRateConversion (WASAPIDeviceMode deviceMode) noexcept | |||||
| { | |||||
| return deviceMode == WASAPIDeviceMode::shared; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class WASAPIDeviceBase | class WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : device (d), useExclusiveMode (exclusiveMode) | |||||
| WASAPIDeviceBase (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode) | |||||
| : device (d), | |||||
| deviceMode (mode) | |||||
| { | { | ||||
| clientEvent = CreateEvent (nullptr, false, false, nullptr); | clientEvent = CreateEvent (nullptr, false, false, nullptr); | ||||
| ComSmartPtr<IAudioClient> tempClient (createClient()); | ComSmartPtr<IAudioClient> tempClient (createClient()); | ||||
| if (tempClient == nullptr) | if (tempClient == nullptr) | ||||
| return; | return; | ||||
| REFERENCE_TIME defaultPeriod, minPeriod; | |||||
| if (! check (tempClient->GetDevicePeriod (&defaultPeriod, &minPeriod))) | |||||
| return; | |||||
| WAVEFORMATEXTENSIBLE format; | |||||
| WAVEFORMATEX* mixFormat = nullptr; | |||||
| if (! check (tempClient->GetMixFormat (&mixFormat))) | |||||
| if (! getClientMixFormat (tempClient, format)) | |||||
| return; | return; | ||||
| WAVEFORMATEXTENSIBLE format; | |||||
| copyWavFormat (format, mixFormat); | |||||
| CoTaskMemFree (mixFormat); | |||||
| actualNumChannels = numChannels = format.Format.nChannels; | actualNumChannels = numChannels = format.Format.nChannels; | ||||
| defaultSampleRate = format.Format.nSamplesPerSec; | defaultSampleRate = format.Format.nSamplesPerSec; | ||||
| minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); | |||||
| defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); | |||||
| mixFormatChannelMask = format.dwChannelMask; | |||||
| rates.addUsingDefaultSort (defaultSampleRate); | rates.addUsingDefaultSort (defaultSampleRate); | ||||
| mixFormatChannelMask = format.dwChannelMask; | |||||
| if (useExclusiveMode | |||||
| && findSupportedFormat (tempClient, defaultSampleRate, format.dwChannelMask, format)) | |||||
| { | |||||
| // Got a format that is supported by the device so we can ask what sample rates are supported (in whatever format) | |||||
| } | |||||
| for (auto rate : { 8000, 11025, 16000, 22050, 32000, | |||||
| 44100, 48000, 88200, 96000, 176400, | |||||
| 192000, 352800, 384000, 705600, 768000 }) | |||||
| { | |||||
| if (rates.contains (rate)) | |||||
| continue; | |||||
| format.Format.nSamplesPerSec = (DWORD) rate; | |||||
| format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8); | |||||
| if (isExclusiveMode (deviceMode)) | |||||
| findSupportedFormat (tempClient, defaultSampleRate, mixFormatChannelMask, format); | |||||
| if (SUCCEEDED (tempClient->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE | |||||
| : AUDCLNT_SHAREMODE_SHARED, | |||||
| (WAVEFORMATEX*) &format, 0))) | |||||
| if (! rates.contains (rate)) | |||||
| rates.addUsingDefaultSort (rate); | |||||
| } | |||||
| querySupportedBufferSizes (format, tempClient); | |||||
| querySupportedSampleRates (format, tempClient); | |||||
| } | } | ||||
| virtual ~WASAPIDeviceBase() | virtual ~WASAPIDeviceBase() | ||||
| @@ -487,11 +513,14 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| ComSmartPtr<IMMDevice> device; | ComSmartPtr<IMMDevice> device; | ||||
| ComSmartPtr<IAudioClient> client; | ComSmartPtr<IAudioClient> client; | ||||
| WASAPIDeviceMode deviceMode; | |||||
| double sampleRate = 0, defaultSampleRate = 0; | double sampleRate = 0, defaultSampleRate = 0; | ||||
| int numChannels = 0, actualNumChannels = 0; | int numChannels = 0, actualNumChannels = 0; | ||||
| int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0; | int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0; | ||||
| int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0; | |||||
| DWORD mixFormatChannelMask = 0; | DWORD mixFormatChannelMask = 0; | ||||
| const bool useExclusiveMode; | |||||
| Array<double> rates; | Array<double> rates; | ||||
| HANDLE clientEvent = {}; | HANDLE clientEvent = {}; | ||||
| BigInteger channels; | BigInteger channels; | ||||
| @@ -582,6 +611,84 @@ private: | |||||
| return newClient; | return newClient; | ||||
| } | } | ||||
| static bool getClientMixFormat (ComSmartPtr<IAudioClient>& client, WAVEFORMATEXTENSIBLE& format) | |||||
| { | |||||
| WAVEFORMATEX* mixFormat = nullptr; | |||||
| if (! check (client->GetMixFormat (&mixFormat))) | |||||
| return false; | |||||
| copyWavFormat (format, mixFormat); | |||||
| CoTaskMemFree (mixFormat); | |||||
| return true; | |||||
| } | |||||
| //============================================================================== | |||||
| void querySupportedBufferSizes (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient) | |||||
| { | |||||
| if (isLowLatencyMode (deviceMode)) | |||||
| { | |||||
| if (auto audioClient3 = audioClient.getInterface<IAudioClient3>()) | |||||
| { | |||||
| UINT32 defaultPeriod = 0, fundamentalPeriod = 0, minPeriod = 0, maxPeriod = 0; | |||||
| if (check (audioClient3->GetSharedModeEnginePeriod ((WAVEFORMATEX*) &format, | |||||
| &defaultPeriod, | |||||
| &fundamentalPeriod, | |||||
| &minPeriod, | |||||
| &maxPeriod))) | |||||
| { | |||||
| minBufferSize = minPeriod; | |||||
| defaultBufferSize = defaultPeriod; | |||||
| lowLatencyMaxBufferSize = maxPeriod; | |||||
| lowLatencyBufferSizeMultiple = fundamentalPeriod; | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| REFERENCE_TIME defaultPeriod, minPeriod; | |||||
| if (! check (audioClient->GetDevicePeriod (&defaultPeriod, &minPeriod))) | |||||
| return; | |||||
| minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); | |||||
| defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); | |||||
| } | |||||
| } | |||||
| void querySupportedSampleRates (WAVEFORMATEXTENSIBLE format, ComSmartPtr<IAudioClient>& audioClient) | |||||
| { | |||||
| for (auto rate : { 8000, 11025, 16000, 22050, 32000, | |||||
| 44100, 48000, 88200, 96000, 176400, | |||||
| 192000, 352800, 384000, 705600, 768000 }) | |||||
| { | |||||
| if (rates.contains (rate)) | |||||
| continue; | |||||
| format.Format.nSamplesPerSec = (DWORD) rate; | |||||
| format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nChannels * format.Format.wBitsPerSample / 8); | |||||
| WAVEFORMATEX* nearestFormat = nullptr; | |||||
| if (SUCCEEDED (audioClient->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE | |||||
| : AUDCLNT_SHAREMODE_SHARED, | |||||
| (WAVEFORMATEX*) &format, | |||||
| isExclusiveMode (deviceMode) ? nullptr | |||||
| : &nearestFormat))) | |||||
| { | |||||
| if (nearestFormat != nullptr) | |||||
| rate = nearestFormat->nSamplesPerSec; | |||||
| if (! rates.contains (rate)) | |||||
| rates.addUsingDefaultSort (rate); | |||||
| } | |||||
| CoTaskMemFree (nearestFormat); | |||||
| } | |||||
| } | |||||
| struct AudioSampleFormat | struct AudioSampleFormat | ||||
| { | { | ||||
| bool useFloat; | bool useFloat; | ||||
| @@ -613,22 +720,35 @@ private: | |||||
| format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; | format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; | ||||
| format.dwChannelMask = newMixFormatChannelMask; | format.dwChannelMask = newMixFormatChannelMask; | ||||
| WAVEFORMATEXTENSIBLE* nearestFormat = nullptr; | |||||
| WAVEFORMATEX* nearestFormat = nullptr; | |||||
| HRESULT hr = clientToUse->IsFormatSupported (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE | |||||
| : AUDCLNT_SHAREMODE_SHARED, | |||||
| HRESULT hr = clientToUse->IsFormatSupported (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE | |||||
| : AUDCLNT_SHAREMODE_SHARED, | |||||
| (WAVEFORMATEX*) &format, | (WAVEFORMATEX*) &format, | ||||
| useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat); | |||||
| isExclusiveMode (deviceMode) ? nullptr | |||||
| : &nearestFormat); | |||||
| logFailure (hr); | logFailure (hr); | ||||
| if (hr == S_FALSE && format.Format.nSamplesPerSec == nearestFormat->Format.nSamplesPerSec) | |||||
| auto supportsSRC = supportsSampleRateConversion (deviceMode); | |||||
| if (hr == S_FALSE | |||||
| && nearestFormat != nullptr | |||||
| && (format.Format.nSamplesPerSec == nearestFormat->nSamplesPerSec | |||||
| || supportsSRC)) | |||||
| { | { | ||||
| copyWavFormat (format, (const WAVEFORMATEX*) nearestFormat); | |||||
| copyWavFormat (format, nearestFormat); | |||||
| if (supportsSRC) | |||||
| { | |||||
| format.Format.nSamplesPerSec = (DWORD) newSampleRate; | |||||
| format.Format.nAvgBytesPerSec = (DWORD) (format.Format.nSamplesPerSec * format.Format.nBlockAlign); | |||||
| } | |||||
| hr = S_OK; | hr = S_OK; | ||||
| } | } | ||||
| CoTaskMemFree (nearestFormat); | CoTaskMemFree (nearestFormat); | ||||
| return check (hr); | |||||
| return hr == S_OK; | |||||
| } | } | ||||
| bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate, | bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate, | ||||
| @@ -652,50 +772,88 @@ private: | |||||
| return false; | return false; | ||||
| } | } | ||||
| bool tryInitialisingWithBufferSize (int bufferSizeSamples) | |||||
| DWORD getStreamFlags() | |||||
| { | { | ||||
| WAVEFORMATEXTENSIBLE format; | |||||
| DWORD streamFlags = 0x40000; /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/ | |||||
| if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format)) | |||||
| if (supportsSampleRateConversion (deviceMode)) | |||||
| streamFlags |= (0x80000000 /*AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM*/ | |||||
| | 0x8000000); /*AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY*/ | |||||
| return streamFlags; | |||||
| } | |||||
| bool initialiseLowLatencyClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format) | |||||
| { | |||||
| if (auto audioClient3 = client.getInterface<IAudioClient3>()) | |||||
| return check (audioClient3->InitializeSharedAudioStream (getStreamFlags(), | |||||
| bufferSizeSamples, | |||||
| (WAVEFORMATEX*) &format, | |||||
| nullptr)); | |||||
| return false; | |||||
| } | |||||
| bool initialiseStandardClient (int bufferSizeSamples, WAVEFORMATEXTENSIBLE format) | |||||
| { | |||||
| REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; | |||||
| check (client->GetDevicePeriod (&defaultPeriod, &minPeriod)); | |||||
| if (isExclusiveMode (deviceMode) && bufferSizeSamples > 0) | |||||
| defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec)); | |||||
| for (;;) | |||||
| { | { | ||||
| REFERENCE_TIME defaultPeriod = 0, minPeriod = 0; | |||||
| GUID session; | |||||
| auto hr = client->Initialize (isExclusiveMode (deviceMode) ? AUDCLNT_SHAREMODE_EXCLUSIVE | |||||
| : AUDCLNT_SHAREMODE_SHARED, | |||||
| getStreamFlags(), | |||||
| defaultPeriod, | |||||
| isExclusiveMode (deviceMode) ? defaultPeriod : 0, | |||||
| (WAVEFORMATEX*) &format, | |||||
| &session); | |||||
| if (check (hr)) | |||||
| return true; | |||||
| check (client->GetDevicePeriod (&defaultPeriod, &minPeriod)); | |||||
| // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks) | |||||
| if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED | |||||
| break; | |||||
| if (useExclusiveMode && bufferSizeSamples > 0) | |||||
| defaultPeriod = jmax (minPeriod, samplesToRefTime (bufferSizeSamples, format.Format.nSamplesPerSec)); | |||||
| UINT32 numFrames = 0; | |||||
| if (! check (client->GetBufferSize (&numFrames))) | |||||
| break; | |||||
| for (;;) | |||||
| { | |||||
| GUID session; | |||||
| HRESULT hr = client->Initialize (useExclusiveMode ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED, | |||||
| 0x40000 /*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/, | |||||
| defaultPeriod, useExclusiveMode ? defaultPeriod : 0, (WAVEFORMATEX*) &format, &session); | |||||
| // Recreate client | |||||
| client = nullptr; | |||||
| client = createClient(); | |||||
| if (check (hr)) | |||||
| { | |||||
| actualNumChannels = format.Format.nChannels; | |||||
| const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; | |||||
| bytesPerSample = format.Format.wBitsPerSample / 8; | |||||
| bytesPerFrame = format.Format.nBlockAlign; | |||||
| defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec); | |||||
| } | |||||
| updateFormat (isFloat); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| // Handle the "alignment dance" : http://msdn.microsoft.com/en-us/library/windows/desktop/dd370875(v=vs.85).aspx (see Remarks) | |||||
| if (hr != MAKE_HRESULT (1, 0x889, 0x19)) // AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED | |||||
| break; | |||||
| bool tryInitialisingWithBufferSize (int bufferSizeSamples) | |||||
| { | |||||
| WAVEFORMATEXTENSIBLE format; | |||||
| UINT32 numFrames = 0; | |||||
| if (! check (client->GetBufferSize (&numFrames))) | |||||
| break; | |||||
| if (findSupportedFormat (client, sampleRate, mixFormatChannelMask, format)) | |||||
| { | |||||
| auto isInitialised = isLowLatencyMode (deviceMode) ? initialiseLowLatencyClient (bufferSizeSamples, format) | |||||
| : initialiseStandardClient (bufferSizeSamples, format); | |||||
| if (isInitialised) | |||||
| { | |||||
| actualNumChannels = format.Format.nChannels; | |||||
| const bool isFloat = format.Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE && format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; | |||||
| bytesPerSample = format.Format.wBitsPerSample / 8; | |||||
| bytesPerFrame = format.Format.nBlockAlign; | |||||
| // Recreate client | |||||
| client = nullptr; | |||||
| client = createClient(); | |||||
| updateFormat (isFloat); | |||||
| defaultPeriod = samplesToRefTime (numFrames, format.Format.nSamplesPerSec); | |||||
| return true; | |||||
| } | } | ||||
| } | } | ||||
| @@ -709,8 +867,8 @@ private: | |||||
| class WASAPIInputDevice : public WASAPIDeviceBase | class WASAPIInputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode) | |||||
| WASAPIInputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode) | |||||
| : WASAPIDeviceBase (d, mode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -872,8 +1030,8 @@ private: | |||||
| class WASAPIOutputDevice : public WASAPIDeviceBase | class WASAPIOutputDevice : public WASAPIDeviceBase | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, bool exclusiveMode) | |||||
| : WASAPIDeviceBase (d, exclusiveMode) | |||||
| WASAPIOutputDevice (const ComSmartPtr<IMMDevice>& d, WASAPIDeviceMode mode) | |||||
| : WASAPIDeviceBase (d, mode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -931,7 +1089,7 @@ public: | |||||
| if (numChannels <= 0) | if (numChannels <= 0) | ||||
| return 0; | return 0; | ||||
| if (! useExclusiveMode) | |||||
| if (! isExclusiveMode (deviceMode)) | |||||
| { | { | ||||
| UINT32 padding = 0; | UINT32 padding = 0; | ||||
| @@ -953,7 +1111,7 @@ public: | |||||
| while (bufferSize > 0) | while (bufferSize > 0) | ||||
| { | { | ||||
| // This is needed in order not to drop any input data if the output device endpoint buffer was full | // This is needed in order not to drop any input data if the output device endpoint buffer was full | ||||
| if ((! useExclusiveMode) && inputDevice != nullptr | |||||
| if ((! isExclusiveMode (deviceMode)) && inputDevice != nullptr | |||||
| && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | ||||
| inputDevice->handleDeviceBuffer(); | inputDevice->handleDeviceBuffer(); | ||||
| @@ -968,7 +1126,7 @@ public: | |||||
| break; | break; | ||||
| } | } | ||||
| if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) | |||||
| if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) | |||||
| break; | break; | ||||
| uint8* outputData = nullptr; | uint8* outputData = nullptr; | ||||
| @@ -1002,12 +1160,12 @@ public: | |||||
| const String& typeName, | const String& typeName, | ||||
| const String& outputDeviceID, | const String& outputDeviceID, | ||||
| const String& inputDeviceID, | const String& inputDeviceID, | ||||
| bool exclusiveMode) | |||||
| WASAPIDeviceMode mode) | |||||
| : AudioIODevice (deviceName, typeName), | : AudioIODevice (deviceName, typeName), | ||||
| Thread ("JUCE WASAPI"), | Thread ("JUCE WASAPI"), | ||||
| outputDeviceId (outputDeviceID), | outputDeviceId (outputDeviceID), | ||||
| inputDeviceId (inputDeviceID), | inputDeviceId (inputDeviceID), | ||||
| useExclusiveMode (exclusiveMode) | |||||
| deviceMode (mode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -1026,21 +1184,48 @@ public: | |||||
| { | { | ||||
| jassert (inputDevice != nullptr || outputDevice != nullptr); | jassert (inputDevice != nullptr || outputDevice != nullptr); | ||||
| sampleRates.clear(); | |||||
| if (inputDevice != nullptr && outputDevice != nullptr) | if (inputDevice != nullptr && outputDevice != nullptr) | ||||
| { | { | ||||
| defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate); | defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate); | ||||
| minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize); | |||||
| minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize); | |||||
| defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); | defaultBufferSize = jmax (inputDevice->defaultBufferSize, outputDevice->defaultBufferSize); | ||||
| sampleRates = inputDevice->rates; | |||||
| sampleRates.removeValuesNotIn (outputDevice->rates); | |||||
| if (isLowLatencyMode (deviceMode)) | |||||
| { | |||||
| lowLatencyMaxBufferSize = jmin (inputDevice->lowLatencyMaxBufferSize, outputDevice->lowLatencyMaxBufferSize); | |||||
| lowLatencyBufferSizeMultiple = jmax (inputDevice->lowLatencyBufferSizeMultiple, outputDevice->lowLatencyBufferSizeMultiple); | |||||
| } | |||||
| sampleRates.addArray (inputDevice->rates); | |||||
| if (supportsSampleRateConversion (deviceMode)) | |||||
| { | |||||
| for (auto r : outputDevice->rates) | |||||
| if (! sampleRates.contains (r)) | |||||
| sampleRates.addUsingDefaultSort (r); | |||||
| } | |||||
| else | |||||
| { | |||||
| sampleRates.removeValuesNotIn (outputDevice->rates); | |||||
| } | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| WASAPIDeviceBase* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice.get()) | |||||
| : static_cast<WASAPIDeviceBase*> (outputDevice.get()); | |||||
| auto* d = inputDevice != nullptr ? static_cast<WASAPIDeviceBase*> (inputDevice.get()) | |||||
| : static_cast<WASAPIDeviceBase*> (outputDevice.get()); | |||||
| defaultSampleRate = d->defaultSampleRate; | defaultSampleRate = d->defaultSampleRate; | ||||
| minBufferSize = d->minBufferSize; | minBufferSize = d->minBufferSize; | ||||
| defaultBufferSize = d->defaultBufferSize; | defaultBufferSize = d->defaultBufferSize; | ||||
| if (isLowLatencyMode (deviceMode)) | |||||
| { | |||||
| lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize; | |||||
| lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple; | |||||
| } | |||||
| sampleRates = d->rates; | sampleRates = d->rates; | ||||
| } | } | ||||
| @@ -1050,13 +1235,28 @@ public: | |||||
| if (minBufferSize != defaultBufferSize) | if (minBufferSize != defaultBufferSize) | ||||
| bufferSizes.addUsingDefaultSort (minBufferSize); | bufferSizes.addUsingDefaultSort (minBufferSize); | ||||
| int n = 64; | |||||
| for (int i = 0; i < 40; ++i) | |||||
| if (isLowLatencyMode (deviceMode)) | |||||
| { | { | ||||
| if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n)) | |||||
| bufferSizes.addUsingDefaultSort (n); | |||||
| auto size = minBufferSize; | |||||
| while (size < lowLatencyMaxBufferSize) | |||||
| { | |||||
| size += lowLatencyBufferSizeMultiple; | |||||
| n += (n < 512) ? 32 : (n < 1024 ? 64 : 128); | |||||
| if (! bufferSizes.contains (size)) | |||||
| bufferSizes.addUsingDefaultSort (size); | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| int n = 64; | |||||
| for (int i = 0; i < 40; ++i) | |||||
| { | |||||
| if (n >= minBufferSize && n <= 2048 && ! bufferSizes.contains (n)) | |||||
| bufferSizes.addUsingDefaultSort (n); | |||||
| n += (n < 512) ? 32 : (n < 1024 ? 64 : 128); | |||||
| } | |||||
| } | } | ||||
| return true; | return true; | ||||
| @@ -1131,7 +1331,7 @@ public: | |||||
| return lastError; | return lastError; | ||||
| } | } | ||||
| if (useExclusiveMode) | |||||
| if (isExclusiveMode (deviceMode)) | |||||
| { | { | ||||
| // This is to make sure that the callback uses actualBufferSize in case of exclusive mode | // This is to make sure that the callback uses actualBufferSize in case of exclusive mode | ||||
| if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize) | if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize) | ||||
| @@ -1297,7 +1497,7 @@ public: | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | |||||
| if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | |||||
| inputDevice->handleDeviceBuffer(); | inputDevice->handleDeviceBuffer(); | ||||
| } | } | ||||
| @@ -1347,9 +1547,10 @@ private: | |||||
| // Device stats... | // Device stats... | ||||
| std::unique_ptr<WASAPIInputDevice> inputDevice; | std::unique_ptr<WASAPIInputDevice> inputDevice; | ||||
| std::unique_ptr<WASAPIOutputDevice> outputDevice; | std::unique_ptr<WASAPIOutputDevice> outputDevice; | ||||
| const bool useExclusiveMode; | |||||
| WASAPIDeviceMode deviceMode; | |||||
| double defaultSampleRate = 0; | double defaultSampleRate = 0; | ||||
| int minBufferSize = 0, defaultBufferSize = 0; | int minBufferSize = 0, defaultBufferSize = 0; | ||||
| int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0; | |||||
| int latencyIn = 0, latencyOut = 0; | int latencyIn = 0, latencyOut = 0; | ||||
| Array<double> sampleRates; | Array<double> sampleRates; | ||||
| Array<int> bufferSizes; | Array<int> bufferSizes; | ||||
| @@ -1399,9 +1600,9 @@ private: | |||||
| auto flow = getDataFlow (device); | auto flow = getDataFlow (device); | ||||
| if (deviceId == inputDeviceId && flow == eCapture) | if (deviceId == inputDeviceId && flow == eCapture) | ||||
| inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode)); | |||||
| inputDevice.reset (new WASAPIInputDevice (device, deviceMode)); | |||||
| else if (deviceId == outputDeviceId && flow == eRender) | else if (deviceId == outputDeviceId && flow == eRender) | ||||
| outputDevice.reset (new WASAPIOutputDevice (device, useExclusiveMode)); | |||||
| outputDevice.reset (new WASAPIOutputDevice (device, deviceMode)); | |||||
| } | } | ||||
| return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) | return (outputDeviceId.isEmpty() || (outputDevice != nullptr && outputDevice->isOk())) | ||||
| @@ -1458,10 +1659,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, | |||||
| private DeviceChangeDetector | private DeviceChangeDetector | ||||
| { | { | ||||
| public: | public: | ||||
| WASAPIAudioIODeviceType (bool exclusive) | |||||
| : AudioIODeviceType (exclusive ? "Windows Audio (Exclusive Mode)" : "Windows Audio"), | |||||
| WASAPIAudioIODeviceType (WASAPIDeviceMode mode) | |||||
| : AudioIODeviceType (getDeviceTypename (mode)), | |||||
| DeviceChangeDetector (L"Windows Audio"), | DeviceChangeDetector (L"Windows Audio"), | ||||
| exclusiveMode (exclusive) | |||||
| deviceMode (mode) | |||||
| { | { | ||||
| } | } | ||||
| @@ -1529,7 +1730,7 @@ public: | |||||
| getTypeName(), | getTypeName(), | ||||
| outputDeviceIds [outputIndex], | outputDeviceIds [outputIndex], | ||||
| inputDeviceIds [inputIndex], | inputDeviceIds [inputIndex], | ||||
| exclusiveMode)); | |||||
| deviceMode)); | |||||
| if (! device->initialise()) | if (! device->initialise()) | ||||
| device = nullptr; | device = nullptr; | ||||
| @@ -1543,7 +1744,7 @@ public: | |||||
| StringArray inputDeviceNames, inputDeviceIds; | StringArray inputDeviceNames, inputDeviceIds; | ||||
| private: | private: | ||||
| const bool exclusiveMode; | |||||
| WASAPIDeviceMode deviceMode; | |||||
| bool hasScanned = false; | bool hasScanned = false; | ||||
| ComSmartPtr<IMMDeviceEnumerator> enumerator; | ComSmartPtr<IMMDeviceEnumerator> enumerator; | ||||
| @@ -1698,6 +1899,17 @@ private: | |||||
| callDeviceChangeListeners(); | callDeviceChangeListeners(); | ||||
| } | } | ||||
| //============================================================================== | |||||
| static String getDeviceTypename (WASAPIDeviceMode mode) | |||||
| { | |||||
| if (mode == WASAPIDeviceMode::shared) return "Windows Audio"; | |||||
| if (mode == WASAPIDeviceMode::sharedLowLatency) return "Windows Audio (Low Latency Mode)"; | |||||
| if (mode == WASAPIDeviceMode::exclusive) return "Windows Audio (Exclusive Mode)"; | |||||
| jassertfalse; | |||||
| return {}; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType) | JUCE_DECLARE_WEAK_REFERENCEABLE (WASAPIAudioIODeviceType) | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WASAPIAudioIODeviceType) | ||||
| @@ -1756,19 +1968,6 @@ struct MMDeviceMasterVolume | |||||
| } | } | ||||
| //============================================================================== | |||||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode) | |||||
| { | |||||
| #if ! JUCE_WASAPI_EXCLUSIVE | |||||
| if (exclusiveMode) | |||||
| return nullptr; | |||||
| #endif | |||||
| return SystemStats::getOperatingSystemType() >= SystemStats::WinVista | |||||
| ? new WasapiClasses::WASAPIAudioIODeviceType (exclusiveMode) | |||||
| : nullptr; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 | #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 | ||||
| float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); } | float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); } | ||||
| @@ -39,8 +39,7 @@ | |||||
| #ifndef JUCE_SUPPORTS_AUv3 | #ifndef JUCE_SUPPORTS_AUv3 | ||||
| #if __OBJC2__ \ | #if __OBJC2__ \ | ||||
| && ((defined (MAC_OS_X_VERSION_10_11) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11)) \ | |||||
| || (defined (__IPHONE_9_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_9_0))) | |||||
| && (JUCE_IOS || (defined (MAC_OS_X_VERSION_10_11) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11))) | |||||
| #define JUCE_SUPPORTS_AUv3 1 | #define JUCE_SUPPORTS_AUv3 1 | ||||
| #else | #else | ||||
| #define JUCE_SUPPORTS_AUv3 0 | #define JUCE_SUPPORTS_AUv3 0 | ||||
| @@ -79,7 +78,7 @@ namespace AudioUnitFormatHelpers | |||||
| static ThreadLocalValue<int> insideCallback; | static ThreadLocalValue<int> insideCallback; | ||||
| #endif | #endif | ||||
| String osTypeToString (OSType type) noexcept | |||||
| static String osTypeToString (OSType type) noexcept | |||||
| { | { | ||||
| const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), | const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), | ||||
| (juce_wchar) ((type >> 16) & 0xff), | (juce_wchar) ((type >> 16) & 0xff), | ||||
| @@ -88,7 +87,7 @@ namespace AudioUnitFormatHelpers | |||||
| return String (s, 4); | return String (s, 4); | ||||
| } | } | ||||
| OSType stringToOSType (String s) | |||||
| static OSType stringToOSType (String s) | |||||
| { | { | ||||
| if (s.trim().length() >= 4) // (to avoid trimming leading spaces) | if (s.trim().length() >= 4) // (to avoid trimming leading spaces) | ||||
| s = s.trim(); | s = s.trim(); | ||||
| @@ -103,7 +102,7 @@ namespace AudioUnitFormatHelpers | |||||
| static const char* auIdentifierPrefix = "AudioUnit:"; | static const char* auIdentifierPrefix = "AudioUnit:"; | ||||
| String createPluginIdentifier (const AudioComponentDescription& desc) | |||||
| static String createPluginIdentifier (const AudioComponentDescription& desc) | |||||
| { | { | ||||
| String s (auIdentifierPrefix); | String s (auIdentifierPrefix); | ||||
| @@ -128,7 +127,7 @@ namespace AudioUnitFormatHelpers | |||||
| return s; | return s; | ||||
| } | } | ||||
| void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) | |||||
| static void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) | |||||
| { | { | ||||
| CFStringRef cfName; | CFStringRef cfName; | ||||
| if (AudioComponentCopyName (comp, &cfName) == noErr) | if (AudioComponentCopyName (comp, &cfName) == noErr) | ||||
| @@ -147,8 +146,8 @@ namespace AudioUnitFormatHelpers | |||||
| name = "<Unknown>"; | name = "<Unknown>"; | ||||
| } | } | ||||
| bool getComponentDescFromIdentifier (const String& fileOrIdentifier, AudioComponentDescription& desc, | |||||
| String& name, String& version, String& manufacturer) | |||||
| static bool getComponentDescFromIdentifier (const String& fileOrIdentifier, AudioComponentDescription& desc, | |||||
| String& name, String& version, String& manufacturer) | |||||
| { | { | ||||
| if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) | if (fileOrIdentifier.startsWithIgnoreCase (auIdentifierPrefix)) | ||||
| { | { | ||||
| @@ -193,8 +192,8 @@ namespace AudioUnitFormatHelpers | |||||
| return false; | return false; | ||||
| } | } | ||||
| bool getComponentDescFromFile (const String& fileOrIdentifier, AudioComponentDescription& desc, | |||||
| String& name, String& version, String& manufacturer) | |||||
| static bool getComponentDescFromFile (const String& fileOrIdentifier, AudioComponentDescription& desc, | |||||
| String& name, String& version, String& manufacturer) | |||||
| { | { | ||||
| zerostruct (desc); | zerostruct (desc); | ||||
| @@ -296,7 +295,7 @@ namespace AudioUnitFormatHelpers | |||||
| #endif | #endif | ||||
| } | } | ||||
| const char* getCategory (OSType type) noexcept | |||||
| static const char* getCategory (OSType type) noexcept | |||||
| { | { | ||||
| switch (type) | switch (type) | ||||
| { | { | ||||
| @@ -401,8 +401,8 @@ private: | |||||
| class MidiEventList : public Steinberg::Vst::IEventList | class MidiEventList : public Steinberg::Vst::IEventList | ||||
| { | { | ||||
| public: | public: | ||||
| MidiEventList() {} | |||||
| virtual ~MidiEventList() {} | |||||
| MidiEventList() = default; | |||||
| virtual ~MidiEventList() = default; | |||||
| JUCE_DECLARE_VST3_COM_REF_METHODS | JUCE_DECLARE_VST3_COM_REF_METHODS | ||||
| JUCE_DECLARE_VST3_COM_QUERY_METHODS | JUCE_DECLARE_VST3_COM_QUERY_METHODS | ||||
| @@ -455,9 +455,43 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| static void hostToPluginEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer, | |||||
| Steinberg::Vst::IParameterChanges* parameterChanges, | |||||
| Steinberg::Vst::IMidiMapping* midiMapping) | |||||
| { | |||||
| toEventList (result, | |||||
| midiBuffer, | |||||
| parameterChanges, | |||||
| midiMapping, | |||||
| EventConversionKind::hostToPlugin); | |||||
| } | |||||
| static void pluginToHostEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer) | |||||
| { | |||||
| toEventList (result, | |||||
| midiBuffer, | |||||
| nullptr, | |||||
| nullptr, | |||||
| EventConversionKind::pluginToHost); | |||||
| } | |||||
| private: | |||||
| enum class EventConversionKind | |||||
| { | |||||
| // Hosted plugins don't expect to receive LegacyMIDICCEvents messages from the host, | |||||
| // so if we're converting midi from the host to an eventlist, this mode will avoid | |||||
| // converting to Legacy events where possible. | |||||
| hostToPlugin, | |||||
| // If plugins generate MIDI internally, then where possible we should preserve | |||||
| // these messages as LegacyMIDICCOut events. | |||||
| pluginToHost | |||||
| }; | |||||
| static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer, | static void toEventList (Steinberg::Vst::IEventList& result, MidiBuffer& midiBuffer, | ||||
| Steinberg::Vst::IParameterChanges* parameterChanges = nullptr, | |||||
| Steinberg::Vst::IMidiMapping* midiMapping = nullptr) | |||||
| Steinberg::Vst::IParameterChanges* parameterChanges, | |||||
| Steinberg::Vst::IMidiMapping* midiMapping, | |||||
| EventConversionKind kind) | |||||
| { | { | ||||
| enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once | enum { maxNumEvents = 2048 }; // Steinberg's Host Checker states that no more than 2048 events are allowed at once | ||||
| int numEvents = 0; | int numEvents = 0; | ||||
| @@ -491,7 +525,7 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| auto maybeEvent = createVstEvent (msg, metadata.data); | |||||
| auto maybeEvent = createVstEvent (msg, metadata.data, kind); | |||||
| if (! maybeEvent.isValid) | if (! maybeEvent.isValid) | ||||
| continue; | continue; | ||||
| @@ -503,7 +537,6 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| private: | |||||
| Array<Steinberg::Vst::Event, CriticalSection> events; | Array<Steinberg::Vst::Event, CriticalSection> events; | ||||
| Atomic<int> refCount; | Atomic<int> refCount; | ||||
| @@ -555,10 +588,21 @@ private: | |||||
| { | { | ||||
| Steinberg::Vst::Event e{}; | Steinberg::Vst::Event e{}; | ||||
| e.type = Steinberg::Vst::Event::kLegacyMIDICCOutEvent; | e.type = Steinberg::Vst::Event::kLegacyMIDICCOutEvent; | ||||
| e.midiCCOut.channel = int8 (createSafeChannel (channel)); | |||||
| e.midiCCOut.channel = Steinberg::int8 (createSafeChannel (channel)); | |||||
| e.midiCCOut.controlNumber = uint8 (jlimit (0, 255, controlNumber)); | e.midiCCOut.controlNumber = uint8 (jlimit (0, 255, controlNumber)); | ||||
| e.midiCCOut.value = int8 (createSafeNote (value)); | |||||
| e.midiCCOut.value2 = int8 (createSafeNote (value2)); | |||||
| e.midiCCOut.value = Steinberg::int8 (createSafeNote (value)); | |||||
| e.midiCCOut.value2 = Steinberg::int8 (createSafeNote (value2)); | |||||
| return e; | |||||
| } | |||||
| static Steinberg::Vst::Event createPolyPressureEvent (const MidiMessage& msg) | |||||
| { | |||||
| Steinberg::Vst::Event e{}; | |||||
| e.type = Steinberg::Vst::Event::kPolyPressureEvent; | |||||
| e.polyPressure.channel = createSafeChannel (msg.getChannel()); | |||||
| e.polyPressure.pitch = createSafeNote (msg.getNoteNumber()); | |||||
| e.polyPressure.pressure = normaliseMidiValue (msg.getAfterTouchValue()); | |||||
| e.polyPressure.noteId = -1; | |||||
| return e; | return e; | ||||
| } | } | ||||
| @@ -610,14 +654,15 @@ private: | |||||
| struct BasicOptional final | struct BasicOptional final | ||||
| { | { | ||||
| BasicOptional() noexcept = default; | BasicOptional() noexcept = default; | ||||
| BasicOptional (const Item& i) noexcept : item ( i ), isValid ( true ) {} | |||||
| BasicOptional (const Item& i) noexcept : item { i }, isValid { true } {} | |||||
| Item item; | Item item; | ||||
| bool isValid{}; | bool isValid{}; | ||||
| }; | }; | ||||
| static BasicOptional<Steinberg::Vst::Event> createVstEvent (const MidiMessage& msg, | static BasicOptional<Steinberg::Vst::Event> createVstEvent (const MidiMessage& msg, | ||||
| const uint8* midiEventData) noexcept | |||||
| const uint8* midiEventData, | |||||
| EventConversionKind kind) noexcept | |||||
| { | { | ||||
| if (msg.isNoteOn()) | if (msg.isNoteOn()) | ||||
| return createNoteOnEvent (msg); | return createNoteOnEvent (msg); | ||||
| @@ -643,11 +688,20 @@ private: | |||||
| if (msg.isQuarterFrame()) | if (msg.isQuarterFrame()) | ||||
| return createCtrlQuarterFrameEvent (msg); | return createCtrlQuarterFrameEvent (msg); | ||||
| // VST3 gives us two ways to communicate poly pressure changes. | |||||
| // There's a dedicated PolyPressureEvent, and also a LegacyMIDICCOutEvent with a | |||||
| // `controlNumber` of `kCtrlPolyPressure`. We're sending the LegacyMIDI version. | |||||
| if (msg.isAftertouch()) | if (msg.isAftertouch()) | ||||
| return createCtrlPolyPressureEvent (msg); | |||||
| { | |||||
| switch (kind) | |||||
| { | |||||
| case EventConversionKind::hostToPlugin: | |||||
| return createPolyPressureEvent (msg); | |||||
| case EventConversionKind::pluginToHost: | |||||
| return createCtrlPolyPressureEvent (msg); | |||||
| } | |||||
| jassertfalse; | |||||
| return {}; | |||||
| } | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| @@ -738,13 +792,26 @@ private: | |||||
| static bool toVst3ControlEvent (const MidiMessage& msg, Vst3MidiControlEvent& result) | static bool toVst3ControlEvent (const MidiMessage& msg, Vst3MidiControlEvent& result) | ||||
| { | { | ||||
| result.controllerNumber = -1; | |||||
| if (msg.isController()) | |||||
| { | |||||
| result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0}; | |||||
| return true; | |||||
| } | |||||
| if (msg.isController()) result = { (Steinberg::Vst::CtrlNumber) msg.getControllerNumber(), msg.getControllerValue() / 127.0}; | |||||
| else if (msg.isPitchWheel()) result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0}; | |||||
| else if (msg.isAftertouch()) result = { Steinberg::Vst::kAfterTouch, msg.getAfterTouchValue() / 127.0}; | |||||
| if (msg.isPitchWheel()) | |||||
| { | |||||
| result = { Steinberg::Vst::kPitchBend, msg.getPitchWheelValue() / 16383.0}; | |||||
| return true; | |||||
| } | |||||
| if (msg.isChannelPressure()) | |||||
| { | |||||
| result = { Steinberg::Vst::kAfterTouch, msg.getChannelPressureValue() / 127.0}; | |||||
| return true; | |||||
| } | |||||
| return (result.controllerNumber != -1); | |||||
| result.controllerNumber = -1; | |||||
| return false; | |||||
| } | } | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiEventList) | ||||
| @@ -55,7 +55,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor", | |||||
| "-Wformat", | "-Wformat", | ||||
| "-Wpedantic", | "-Wpedantic", | ||||
| "-Wextra", | "-Wextra", | ||||
| "-Wclass-memaccess") | |||||
| "-Wclass-memaccess", | |||||
| "-Wmissing-prototypes", | |||||
| "-Wtype-limits") | |||||
| #undef DEVELOPMENT | #undef DEVELOPMENT | ||||
| #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | ||||
| @@ -818,65 +818,43 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| struct DLLHandle | struct DLLHandle | ||||
| { | { | ||||
| DLLHandle (const String& modulePath) | |||||
| DLLHandle (const File& fileToOpen) | |||||
| : dllFile (fileToOpen) | |||||
| { | { | ||||
| if (modulePath.trim().isNotEmpty()) | |||||
| open (modulePath); | |||||
| open(); | |||||
| } | } | ||||
| ~DLLHandle() | ~DLLHandle() | ||||
| { | { | ||||
| typedef bool (PLUGIN_API *ExitModuleFn) (); | |||||
| #if JUCE_WINDOWS || JUCE_LINUX | |||||
| releaseFactory(); | |||||
| #if JUCE_WINDOWS | |||||
| if (auto exitFn = (ExitModuleFn) getFunction ("ExitDll")) | |||||
| #else | |||||
| if (auto exitFn = (ExitModuleFn) getFunction ("ModuleExit")) | |||||
| #endif | |||||
| exitFn(); | |||||
| library.close(); | |||||
| #elif JUCE_MAC | |||||
| #if JUCE_MAC | |||||
| if (bundleRef != nullptr) | if (bundleRef != nullptr) | ||||
| #endif | |||||
| { | { | ||||
| releaseFactory(); | |||||
| if (factory != nullptr) | |||||
| factory->release(); | |||||
| if (auto exitFn = (ExitModuleFn) getFunction ("bundleExit")) | |||||
| using ExitModuleFn = bool (PLUGIN_API*) (); | |||||
| if (auto* exitFn = (ExitModuleFn) getFunction (exitFnName)) | |||||
| exitFn(); | exitFn(); | ||||
| CFBundleUnloadExecutable (bundleRef); | |||||
| #if JUCE_WINDOWS || JUCE_LINUX | |||||
| library.close(); | |||||
| #elif JUCE_MAC | |||||
| CFRelease (bundleRef); | CFRelease (bundleRef); | ||||
| bundleRef = nullptr; | bundleRef = nullptr; | ||||
| #endif | |||||
| } | } | ||||
| #endif | |||||
| } | |||||
| void open (const PluginDescription& description) | |||||
| { | |||||
| #if JUCE_WINDOWS || JUCE_LINUX | |||||
| jassert (description.fileOrIdentifier.isNotEmpty()); | |||||
| jassert (File (description.fileOrIdentifier).existsAsFile()); | |||||
| library.open (description.fileOrIdentifier); | |||||
| #elif JUCE_MAC | |||||
| open (description.fileOrIdentifier); | |||||
| #endif | |||||
| } | } | ||||
| /** @note The factory should begin with a refCount of 1, | |||||
| so don't increment the reference count | |||||
| (ie: don't use a ComSmartPtr in here)! | |||||
| Its lifetime will be handled by this DllHandle, | |||||
| when such will be destroyed. | |||||
| @see releaseFactory | |||||
| //============================================================================== | |||||
| /** The factory should begin with a refCount of 1, so don't increment the reference count | |||||
| (ie: don't use a ComSmartPtr in here)! Its lifetime will be handled by this DLLHandle. | |||||
| */ | */ | ||||
| IPluginFactory* JUCE_CALLTYPE getPluginFactory() | IPluginFactory* JUCE_CALLTYPE getPluginFactory() | ||||
| { | { | ||||
| if (factory == nullptr) | if (factory == nullptr) | ||||
| if (auto proc = (GetFactoryProc) getFunction ("GetPluginFactory")) | |||||
| if (auto* proc = (GetFactoryProc) getFunction (factoryFnName)) | |||||
| factory = proc(); | factory = proc(); | ||||
| // The plugin NEEDS to provide a factory to be able to be called a VST3! | // The plugin NEEDS to provide a factory to be able to be called a VST3! | ||||
| @@ -894,38 +872,56 @@ struct DLLHandle | |||||
| if (bundleRef == nullptr) | if (bundleRef == nullptr) | ||||
| return nullptr; | return nullptr; | ||||
| CFStringRef name = String (functionName).toCFString(); | |||||
| void* fn = CFBundleGetFunctionPointerForName (bundleRef, name); | |||||
| CFRelease (name); | |||||
| return fn; | |||||
| ScopedCFString name (functionName); | |||||
| return CFBundleGetFunctionPointerForName (bundleRef, name.cfString); | |||||
| #endif | #endif | ||||
| } | } | ||||
| File getFile() const noexcept { return dllFile; } | |||||
| private: | private: | ||||
| File dllFile; | |||||
| IPluginFactory* factory = nullptr; | IPluginFactory* factory = nullptr; | ||||
| void releaseFactory() | |||||
| { | |||||
| if (factory != nullptr) | |||||
| factory->release(); | |||||
| } | |||||
| static constexpr const char* factoryFnName = "GetPluginFactory"; | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| static constexpr const char* entryFnName = "InitDll"; | |||||
| static constexpr const char* exitFnName = "ExitDll"; | |||||
| using EntryProc = bool (PLUGIN_API*) (); | |||||
| #elif JUCE_LINUX | |||||
| static constexpr const char* entryFnName = "ModuleEntry"; | |||||
| static constexpr const char* exitFnName = "ModuleExit"; | |||||
| using EntryProc = bool (PLUGIN_API*) (void*); | |||||
| #elif JUCE_MAC | |||||
| static constexpr const char* entryFnName = "bundleEntry"; | |||||
| static constexpr const char* exitFnName = "bundleExit"; | |||||
| using EntryProc = bool (*) (CFBundleRef); | |||||
| #endif | |||||
| //============================================================================== | |||||
| #if JUCE_WINDOWS || JUCE_LINUX | |||||
| DynamicLibrary library; | DynamicLibrary library; | ||||
| bool open (const String& filePath) | |||||
| bool open() | |||||
| { | { | ||||
| if (library.open (filePath)) | |||||
| if (library.open (dllFile.getFullPathName())) | |||||
| { | { | ||||
| typedef bool (PLUGIN_API *InitModuleProc) (); | |||||
| if (auto proc = (InitModuleProc) getFunction ("InitDll")) | |||||
| if (auto* proc = (EntryProc) getFunction (entryFnName)) | |||||
| { | { | ||||
| #if JUCE_WINDOWS | |||||
| if (proc()) | if (proc()) | ||||
| #else | |||||
| if (proc (library.getNativeHandle())) | |||||
| #endif | |||||
| return true; | return true; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| // this is required for some plug-ins which don't export the dll entry point function | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -937,12 +933,14 @@ private: | |||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| CFBundleRef bundleRef; | CFBundleRef bundleRef; | ||||
| bool open (const String& filePath) | |||||
| bool open() | |||||
| { | { | ||||
| const File file (filePath); | |||||
| const char* const utf8 = file.getFullPathName().toRawUTF8(); | |||||
| auto* utf8 = dllFile.getFullPathName().toRawUTF8(); | |||||
| if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (nullptr, (const UInt8*) utf8, (CFIndex) std::strlen (utf8), file.isDirectory())) | |||||
| if (CFURLRef url = CFURLCreateFromFileSystemRepresentation (nullptr, | |||||
| (const UInt8*) utf8, | |||||
| (CFIndex) std::strlen (utf8), | |||||
| dllFile.isDirectory())) | |||||
| { | { | ||||
| bundleRef = CFBundleCreate (kCFAllocatorDefault, url); | bundleRef = CFBundleCreate (kCFAllocatorDefault, url); | ||||
| CFRelease (url); | CFRelease (url); | ||||
| @@ -953,17 +951,11 @@ private: | |||||
| if (CFBundleLoadExecutableAndReturnError (bundleRef, &error)) | if (CFBundleLoadExecutableAndReturnError (bundleRef, &error)) | ||||
| { | { | ||||
| using BundleEntryProc = bool (*)(CFBundleRef); | |||||
| if (auto proc = (BundleEntryProc) getFunction ("bundleEntry")) | |||||
| if (auto* proc = (EntryProc) getFunction (entryFnName)) | |||||
| { | { | ||||
| if (proc (bundleRef)) | if (proc (bundleRef)) | ||||
| return true; | return true; | ||||
| } | } | ||||
| else | |||||
| { | |||||
| return true; | |||||
| } | |||||
| } | } | ||||
| if (error != nullptr) | if (error != nullptr) | ||||
| @@ -984,127 +976,122 @@ private: | |||||
| return false; | return false; | ||||
| } | } | ||||
| #elif JUCE_LINUX | |||||
| DynamicLibrary library; | |||||
| #endif | |||||
| String getMachineName() | |||||
| { | |||||
| struct utsname unameData; | |||||
| auto res = uname (&unameData); | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DLLHandle) | |||||
| }; | |||||
| if (res != 0) | |||||
| return {}; | |||||
| struct DLLHandleCache : public DeletedAtShutdown | |||||
| { | |||||
| DLLHandleCache() = default; | |||||
| ~DLLHandleCache() override { clearSingletonInstance(); } | |||||
| return unameData.machine; | |||||
| } | |||||
| JUCE_DECLARE_SINGLETON (DLLHandleCache, false) | |||||
| bool open (const String& bundlePath) | |||||
| DLLHandle& findOrCreateHandle (const String& modulePath) | |||||
| { | { | ||||
| File file (bundlePath); | |||||
| if (! file.exists() || ! file.isDirectory()) | |||||
| return false; | |||||
| #if JUCE_LINUX | |||||
| File file (getDLLFileFromBundle (modulePath)); | |||||
| #else | |||||
| File file (modulePath); | |||||
| #endif | |||||
| auto pluginName = file.getFileNameWithoutExtension(); | |||||
| auto it = std::find_if (openHandles.begin(), openHandles.end(), | |||||
| [&] (const std::unique_ptr<DLLHandle>& handle) | |||||
| { | |||||
| return file == handle->getFile(); | |||||
| }); | |||||
| file = file.getChildFile ("Contents") | |||||
| .getChildFile (getMachineName() + "-linux") | |||||
| .getChildFile (pluginName + ".so"); | |||||
| if (it != openHandles.end()) | |||||
| return *it->get(); | |||||
| if (! file.exists()) | |||||
| return false; | |||||
| openHandles.push_back (std::make_unique<DLLHandle> (file)); | |||||
| return *openHandles.back().get(); | |||||
| } | |||||
| if (library.open (file.getFullPathName())) | |||||
| private: | |||||
| #if JUCE_LINUX | |||||
| File getDLLFileFromBundle (const String& bundlePath) const | |||||
| { | |||||
| auto machineName = []() -> String | |||||
| { | { | ||||
| typedef bool (PLUGIN_API *InitModuleProc) (void*); | |||||
| struct utsname unameData; | |||||
| auto res = uname (&unameData); | |||||
| if (auto* proc = (InitModuleProc) getFunction ("ModuleEntry")) | |||||
| { | |||||
| if (proc (library.getNativeHandle())) | |||||
| return true; | |||||
| } | |||||
| else | |||||
| { | |||||
| return true; | |||||
| } | |||||
| if (res != 0) | |||||
| return {}; | |||||
| library.close(); | |||||
| } | |||||
| return unameData.machine; | |||||
| }(); | |||||
| return false; | |||||
| File file (bundlePath); | |||||
| return file.getChildFile ("Contents") | |||||
| .getChildFile (machineName + "-linux") | |||||
| .getChildFile (file.getFileNameWithoutExtension() + ".so"); | |||||
| } | } | ||||
| #endif | #endif | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DLLHandle) | |||||
| std::vector<std::unique_ptr<DLLHandle>> openHandles; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DLLHandleCache) | |||||
| }; | }; | ||||
| JUCE_IMPLEMENT_SINGLETON (DLLHandleCache) | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VST3ModuleHandle : public ReferenceCountedObject | struct VST3ModuleHandle : public ReferenceCountedObject | ||||
| { | { | ||||
| explicit VST3ModuleHandle (const File& pluginFile) : file (pluginFile) | |||||
| explicit VST3ModuleHandle (const File& pluginFile, const PluginDescription& pluginDesc) | |||||
| : file (pluginFile) | |||||
| { | { | ||||
| getActiveModules().add (this); | |||||
| if (open (pluginDesc)) | |||||
| { | |||||
| isOpen = true; | |||||
| getActiveModules().add (this); | |||||
| } | |||||
| } | } | ||||
| ~VST3ModuleHandle() | ~VST3ModuleHandle() | ||||
| { | { | ||||
| getActiveModules().removeFirstMatchingValue (this); | |||||
| } | |||||
| /** | |||||
| Since there is no apparent indication if a VST3 plugin is a shell or not, | |||||
| we're stuck iterating through a VST3's factory, creating a description | |||||
| for every housed plugin. | |||||
| */ | |||||
| static bool getAllDescriptionsForFile (OwnedArray<PluginDescription>& results, | |||||
| const String& fileOrIdentifier) | |||||
| { | |||||
| DLLHandle tempModule (fileOrIdentifier); | |||||
| ComSmartPtr<IPluginFactory> pluginFactory (tempModule.getPluginFactory()); | |||||
| if (pluginFactory != nullptr) | |||||
| { | |||||
| ComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||||
| DescriptionLister lister (host, pluginFactory); | |||||
| auto result = lister.findDescriptionsAndPerform (File (fileOrIdentifier)); | |||||
| results.addCopiesOf (lister.list); | |||||
| return result.wasOk(); | |||||
| } | |||||
| jassertfalse; | |||||
| return false; | |||||
| if (isOpen) | |||||
| getActiveModules().removeFirstMatchingValue (this); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| using Ptr = ReferenceCountedObjectPtr<VST3ModuleHandle>; | using Ptr = ReferenceCountedObjectPtr<VST3ModuleHandle>; | ||||
| static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, const PluginDescription& description) | |||||
| static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, | |||||
| const PluginDescription& description) | |||||
| { | { | ||||
| for (auto* module : getActiveModules()) | for (auto* module : getActiveModules()) | ||||
| { | |||||
| // VST3s are basically shells, you must therefore check their name along with their file: | // VST3s are basically shells, you must therefore check their name along with their file: | ||||
| if (module->file == file && module->name == description.name) | if (module->file == file && module->name == description.name) | ||||
| return module; | return module; | ||||
| } | |||||
| VST3ModuleHandle::Ptr m (new VST3ModuleHandle (file)); | |||||
| VST3ModuleHandle::Ptr modulePtr (new VST3ModuleHandle (file, description)); | |||||
| if (! m->open (file, description)) | |||||
| m = nullptr; | |||||
| if (! modulePtr->isOpen) | |||||
| modulePtr = nullptr; | |||||
| return m; | |||||
| return modulePtr; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| IPluginFactory* getPluginFactory() { return dllHandle->getPluginFactory(); } | |||||
| IPluginFactory* getPluginFactory() | |||||
| { | |||||
| return DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()).getPluginFactory(); | |||||
| } | |||||
| File file; | |||||
| String name; | |||||
| File getFile() const noexcept { return file; } | |||||
| String getName() const noexcept { return name; } | |||||
| private: | private: | ||||
| std::unique_ptr<DLLHandle> dllHandle; | |||||
| //============================================================================== | //============================================================================== | ||||
| static Array<VST3ModuleHandle*>& getActiveModules() | static Array<VST3ModuleHandle*>& getActiveModules() | ||||
| { | { | ||||
| @@ -1113,11 +1100,10 @@ private: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool open (const File& f, const PluginDescription& description) | |||||
| bool open (const PluginDescription& description) | |||||
| { | { | ||||
| dllHandle.reset (new DLLHandle (f.getFullPathName())); | |||||
| ComSmartPtr<IPluginFactory> pluginFactory (dllHandle->getPluginFactory()); | |||||
| ComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (file.getFullPathName()) | |||||
| .getPluginFactory()); | |||||
| if (pluginFactory != nullptr) | if (pluginFactory != nullptr) | ||||
| { | { | ||||
| @@ -1132,7 +1118,7 @@ private: | |||||
| continue; | continue; | ||||
| if (toString (info.name).trim() == description.name | if (toString (info.name).trim() == description.name | ||||
| && getHashForTUID (info.cid) == description.uid) | |||||
| && getHashForTUID (info.cid) == description.uid) | |||||
| { | { | ||||
| name = description.name; | name = description.name; | ||||
| return true; | return true; | ||||
| @@ -1143,6 +1129,11 @@ private: | |||||
| return false; | return false; | ||||
| } | } | ||||
| File file; | |||||
| String name; | |||||
| bool isOpen = false; | |||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) | ||||
| }; | }; | ||||
| @@ -1618,7 +1609,7 @@ struct VST3ComponentHolder | |||||
| PFactoryInfo factoryInfo; | PFactoryInfo factoryInfo; | ||||
| factory->getFactoryInfo (&factoryInfo); | factory->getFactoryInfo (&factoryInfo); | ||||
| auto classIdx = getClassIndex (module->name); | |||||
| auto classIdx = getClassIndex (module->getName()); | |||||
| if (classIdx >= 0) | if (classIdx >= 0) | ||||
| { | { | ||||
| @@ -1667,8 +1658,8 @@ struct VST3ComponentHolder | |||||
| if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) | if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) | ||||
| totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); | totalNumOutputChannels += ((bus.flags & Vst::BusInfo::kDefaultActive) != 0 ? bus.channelCount : 0); | ||||
| createPluginDescription (description, module->file, | |||||
| factoryInfo.vendor, module->name, | |||||
| createPluginDescription (description, module->getFile(), | |||||
| factoryInfo.vendor, module->getName(), | |||||
| info, info2.get(), infoW.get(), | info, info2.get(), infoW.get(), | ||||
| totalNumInputChannels, | totalNumInputChannels, | ||||
| totalNumOutputChannels); | totalNumOutputChannels); | ||||
| @@ -1695,7 +1686,7 @@ struct VST3ComponentHolder | |||||
| factory = ComSmartPtr<IPluginFactory> (module->getPluginFactory()); | factory = ComSmartPtr<IPluginFactory> (module->getPluginFactory()); | ||||
| int classIdx; | int classIdx; | ||||
| if ((classIdx = getClassIndex (module->name)) < 0) | |||||
| if ((classIdx = getClassIndex (module->getName())) < 0) | |||||
| return false; | return false; | ||||
| PClassInfo info; | PClassInfo info; | ||||
| @@ -1991,7 +1982,7 @@ public: | |||||
| const String getName() const override | const String getName() const override | ||||
| { | { | ||||
| auto& module = holder->module; | auto& module = holder->module; | ||||
| return module != nullptr ? module->name : String(); | |||||
| return module != nullptr ? module->getName() : String(); | |||||
| } | } | ||||
| void repopulateArrangements (Array<Vst::SpeakerArrangement>& inputArrangements, Array<Vst::SpeakerArrangement>& outputArrangements) const | void repopulateArrangements (Array<Vst::SpeakerArrangement>& inputArrangements, Array<Vst::SpeakerArrangement>& outputArrangements) const | ||||
| @@ -2979,9 +2970,10 @@ private: | |||||
| midiOutputs->clear(); | midiOutputs->clear(); | ||||
| if (acceptsMidi()) | if (acceptsMidi()) | ||||
| MidiEventList::toEventList (*midiInputs, midiBuffer, | |||||
| destination.inputParameterChanges, | |||||
| midiMapping); | |||||
| MidiEventList::hostToPluginEventList (*midiInputs, | |||||
| midiBuffer, | |||||
| destination.inputParameterChanges, | |||||
| midiMapping); | |||||
| destination.inputEvents = midiInputs; | destination.inputEvents = midiInputs; | ||||
| destination.outputEvents = midiOutputs; | destination.outputEvents = midiOutputs; | ||||
| @@ -3315,7 +3307,29 @@ bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, cons | |||||
| void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) | void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) | ||||
| { | { | ||||
| if (fileMightContainThisPluginType (fileOrIdentifier)) | if (fileMightContainThisPluginType (fileOrIdentifier)) | ||||
| VST3ModuleHandle::getAllDescriptionsForFile (results, fileOrIdentifier); | |||||
| { | |||||
| /** | |||||
| Since there is no apparent indication if a VST3 plugin is a shell or not, | |||||
| we're stuck iterating through a VST3's factory, creating a description | |||||
| for every housed plugin. | |||||
| */ | |||||
| ComSmartPtr<IPluginFactory> pluginFactory (DLLHandleCache::getInstance()->findOrCreateHandle (fileOrIdentifier) | |||||
| .getPluginFactory()); | |||||
| if (pluginFactory != nullptr) | |||||
| { | |||||
| ComSmartPtr<VST3HostContext> host (new VST3HostContext()); | |||||
| DescriptionLister lister (host, pluginFactory); | |||||
| lister.findDescriptionsAndPerform (File (fileOrIdentifier)); | |||||
| results.addCopiesOf (lister.list); | |||||
| } | |||||
| else | |||||
| { | |||||
| jassertfalse; | |||||
| } | |||||
| } | |||||
| } | } | ||||
| void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | void VST3PluginFormat::createPluginInstance (const PluginDescription& description, | ||||
| @@ -26,13 +26,6 @@ | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| //============================================================================== | |||||
| #if JUCE_BIG_ENDIAN | |||||
| #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (a | (((uint32) b) << 8) | (((uint32) c) << 16) | (((uint32) d) << 24)) | |||||
| #else | |||||
| #define JUCE_MULTICHAR_CONSTANT(a, b, c, d) (d | (((uint32) c) << 8) | (((uint32) b) << 16) | (((uint32) a) << 24)) | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Structure for VST speaker mappings | /** Structure for VST speaker mappings | ||||
| @@ -24,6 +24,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||||
| #define JUCE_VSTINTERFACE_H_INCLUDED | #define JUCE_VSTINTERFACE_H_INCLUDED | ||||
| using namespace juce; | using namespace juce; | ||||
| @@ -505,7 +506,7 @@ enum PresonusExtensionConstants | |||||
| @tags{Audio} | @tags{Audio} | ||||
| */ | */ | ||||
| struct vst2FxBank | |||||
| struct fxBank | |||||
| { | { | ||||
| int32 magic1; | int32 magic1; | ||||
| int32 size; | int32 size; | ||||
| @@ -527,3 +528,5 @@ struct vst2FxBank | |||||
| #else | #else | ||||
| #pragma pack(pop) | #pragma pack(pop) | ||||
| #endif | #endif | ||||
| #endif // JUCE_VSTINTERFACE_H_INCLUDED | |||||
| @@ -173,7 +173,7 @@ namespace | |||||
| #elif JUCE_LINUX || JUCE_IOS || JUCE_ANDROID | #elif JUCE_LINUX || JUCE_IOS || JUCE_ANDROID | ||||
| timeval micro; | timeval micro; | ||||
| gettimeofday (µ, nullptr); | gettimeofday (µ, nullptr); | ||||
| return micro.tv_usec * 1000.0; | |||||
| return (double) micro.tv_usec * 1000.0; | |||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| UnsignedWide micro; | UnsignedWide micro; | ||||
| Microseconds (µ); | Microseconds (µ); | ||||
| @@ -441,8 +441,8 @@ private: | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| entry->range.low = curEntry / (float) numEntries; | |||||
| entry->range.high = (curEntry + 1) / (float) numEntries; | |||||
| entry->range.low = (float) curEntry / (float) numEntries; | |||||
| entry->range.high = (float) (curEntry + 1) / (float) numEntries; | |||||
| entry->range.inclusiveLow = true; | entry->range.inclusiveLow = true; | ||||
| entry->range.inclusiveHigh = (curEntry == numEntries - 1); | entry->range.inclusiveHigh = (curEntry == numEntries - 1); | ||||
| @@ -2872,8 +2872,8 @@ public: | |||||
| { | { | ||||
| X11Symbols::getInstance()->xMoveResizeWindow (display, pluginWindow, | X11Symbols::getInstance()->xMoveResizeWindow (display, pluginWindow, | ||||
| pos.getX(), pos.getY(), | pos.getX(), pos.getY(), | ||||
| static_cast<unsigned int> (roundToInt (getWidth() * nativeScaleFactor)), | |||||
| static_cast<unsigned int> (roundToInt (getHeight() * nativeScaleFactor))); | |||||
| static_cast<unsigned int> (roundToInt ((float) getWidth() * nativeScaleFactor)), | |||||
| static_cast<unsigned int> (roundToInt ((float) getHeight() * nativeScaleFactor))); | |||||
| X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | ||||
| X11Symbols::getInstance()->xFlush (display); | X11Symbols::getInstance()->xFlush (display); | ||||
| @@ -2939,8 +2939,8 @@ public: | |||||
| if (pluginRespondsToDPIChanges) | if (pluginRespondsToDPIChanges) | ||||
| dispatch (Vst2::plugInOpcodeManufacturerSpecific, | dispatch (Vst2::plugInOpcodeManufacturerSpecific, | ||||
| JUCE_MULTICHAR_CONSTANT ('P', 'r', 'e', 'S'), | |||||
| JUCE_MULTICHAR_CONSTANT ('A', 'e', 'C', 's'), | |||||
| (int) ByteOrder::bigEndianInt ("PreS"), | |||||
| (int) ByteOrder::bigEndianInt ("AeCs"), | |||||
| nullptr, nativeScaleFactor); | nullptr, nativeScaleFactor); | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -3164,8 +3164,8 @@ private: | |||||
| X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | ||||
| #endif | #endif | ||||
| w = roundToInt (w / nativeScaleFactor); | |||||
| h = roundToInt (h / nativeScaleFactor); | |||||
| w = roundToInt ((float) w / nativeScaleFactor); | |||||
| h = roundToInt ((float) h / nativeScaleFactor); | |||||
| // double-check it's not too tiny | // double-check it's not too tiny | ||||
| w = jmax (w, 32); | w = jmax (w, 32); | ||||
| @@ -3631,7 +3631,6 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() | |||||
| FileSearchPath paths; | FileSearchPath paths; | ||||
| paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath")); | paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath")); | ||||
| paths.addIfNotAlreadyThere (programFiles + "\\Steinberg\\VstPlugins"); | paths.addIfNotAlreadyThere (programFiles + "\\Steinberg\\VstPlugins"); | ||||
| paths.removeNonExistentPaths(); | |||||
| paths.addIfNotAlreadyThere (programFiles + "\\VstPlugins"); | paths.addIfNotAlreadyThere (programFiles + "\\VstPlugins"); | ||||
| paths.removeRedundantPaths(); | paths.removeRedundantPaths(); | ||||
| return paths; | return paths; | ||||
| @@ -35,7 +35,7 @@ | |||||
| ID: juce_audio_processors | ID: juce_audio_processors | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE audio processor classes | name: JUCE audio processor classes | ||||
| description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -1567,35 +1567,4 @@ void AudioProcessorParameter::removeListener (AudioProcessorParameter::Listener* | |||||
| listeners.removeFirstMatchingValue (listenerToRemove); | listeners.removeFirstMatchingValue (listenerToRemove); | ||||
| } | } | ||||
| //============================================================================== | |||||
| bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept | |||||
| { | |||||
| return timeInSamples == other.timeInSamples | |||||
| && ppqPosition == other.ppqPosition | |||||
| && editOriginTime == other.editOriginTime | |||||
| && ppqPositionOfLastBarStart == other.ppqPositionOfLastBarStart | |||||
| && frameRate == other.frameRate | |||||
| && isPlaying == other.isPlaying | |||||
| && isRecording == other.isRecording | |||||
| && bpm == other.bpm | |||||
| && timeSigNumerator == other.timeSigNumerator | |||||
| && timeSigDenominator == other.timeSigDenominator | |||||
| && ppqLoopStart == other.ppqLoopStart | |||||
| && ppqLoopEnd == other.ppqLoopEnd | |||||
| && isLooping == other.isLooping; | |||||
| } | |||||
| bool AudioPlayHead::CurrentPositionInfo::operator!= (const CurrentPositionInfo& other) const noexcept | |||||
| { | |||||
| return ! operator== (other); | |||||
| } | |||||
| void AudioPlayHead::CurrentPositionInfo::resetToDefault() | |||||
| { | |||||
| zerostruct (*this); | |||||
| timeSigNumerator = 4; | |||||
| timeSigDenominator = 4; | |||||
| bpm = 120; | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -191,6 +191,7 @@ ComboBoxParameterAttachment::ComboBoxParameterAttachment (RangedAudioParameter& | |||||
| ComboBox& c, | ComboBox& c, | ||||
| UndoManager* um) | UndoManager* um) | ||||
| : comboBox (c), | : comboBox (c), | ||||
| storedParameter (param), | |||||
| attachment (param, [this] (float f) { setValue (f); }, um) | attachment (param, [this] (float f) { setValue (f); }, um) | ||||
| { | { | ||||
| sendInitialUpdate(); | sendInitialUpdate(); | ||||
| @@ -209,7 +210,8 @@ void ComboBoxParameterAttachment::sendInitialUpdate() | |||||
| void ComboBoxParameterAttachment::setValue (float newValue) | void ComboBoxParameterAttachment::setValue (float newValue) | ||||
| { | { | ||||
| const auto index = roundToInt (newValue); | |||||
| const auto normValue = storedParameter.convertTo0to1 (newValue); | |||||
| const auto index = roundToInt (normValue * (float) (comboBox.getNumItems() - 1)); | |||||
| if (index == comboBox.getSelectedItemIndex()) | if (index == comboBox.getSelectedItemIndex()) | ||||
| return; | return; | ||||
| @@ -223,7 +225,12 @@ void ComboBoxParameterAttachment::comboBoxChanged (ComboBox*) | |||||
| if (ignoreCallbacks) | if (ignoreCallbacks) | ||||
| return; | return; | ||||
| attachment.setValueAsCompleteGesture ((float) comboBox.getSelectedItemIndex()); | |||||
| const auto numItems = comboBox.getNumItems(); | |||||
| const auto selected = (float) comboBox.getSelectedItemIndex(); | |||||
| const auto newValue = numItems > 1 ? selected / (float) (numItems - 1) | |||||
| : 0.0f; | |||||
| attachment.setValueAsCompleteGesture (storedParameter.convertFrom0to1 (newValue)); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -203,6 +203,7 @@ private: | |||||
| void comboBoxChanged (ComboBox*) override; | void comboBoxChanged (ComboBox*) override; | ||||
| ComboBox& comboBox; | ComboBox& comboBox; | ||||
| RangedAudioParameter& storedParameter; | |||||
| ParameterAttachment attachment; | ParameterAttachment attachment; | ||||
| bool ignoreCallbacks = false; | bool ignoreCallbacks = false; | ||||
| }; | }; | ||||
| @@ -87,14 +87,14 @@ namespace ArrayBaseTestsHelpers | |||||
| }; | }; | ||||
| } | } | ||||
| bool operator== (const ArrayBaseTestsHelpers::TriviallyCopyableType& tct, | |||||
| const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct) | |||||
| static bool operator== (const ArrayBaseTestsHelpers::TriviallyCopyableType& tct, | |||||
| const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct) | |||||
| { | { | ||||
| return tct.getValue() == ntct.getValue(); | return tct.getValue() == ntct.getValue(); | ||||
| } | } | ||||
| bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, | |||||
| const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) | |||||
| static bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, | |||||
| const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) | |||||
| { | { | ||||
| return tct == ntct; | return tct == ntct; | ||||
| } | } | ||||
| @@ -257,8 +257,8 @@ public: | |||||
| } | } | ||||
| private: | private: | ||||
| static const String* getString (const ValueUnion& data) noexcept { return reinterpret_cast<const String*> (data.stringValue); } | |||||
| static String* getString (ValueUnion& data) noexcept { return reinterpret_cast<String*> (data.stringValue); } | |||||
| static const String* getString (const ValueUnion& data) noexcept { return unalignedPointerCast<const String*> (data.stringValue); } | |||||
| static String* getString (ValueUnion& data) noexcept { return unalignedPointerCast<String*> (data.stringValue); } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -20,7 +20,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) | |||||
| #if __LP64__ | #if __LP64__ | ||||
| using OSType = unsigned int; | using OSType = unsigned int; | ||||
| #else | #else | ||||
| @@ -23,6 +23,14 @@ | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| float DirectoryEntry::getEstimatedProgress() const | |||||
| { | |||||
| if (auto it = iterator.lock()) | |||||
| return it->getEstimatedProgress(); | |||||
| return 0.0f; | |||||
| } | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | ||||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996) | ||||
| @@ -37,6 +45,7 @@ RangedDirectoryIterator::RangedDirectoryIterator (const File& directory, | |||||
| wildCard, | wildCard, | ||||
| whatToLookFor)) | whatToLookFor)) | ||||
| { | { | ||||
| entry.iterator = iterator; | |||||
| increment(); | increment(); | ||||
| } | } | ||||
| @@ -53,7 +53,13 @@ public: | |||||
| /** True if the item is read-only, false otherwise. */ | /** True if the item is read-only, false otherwise. */ | ||||
| bool isReadOnly() const { return readOnly; } | bool isReadOnly() const { return readOnly; } | ||||
| /** The estimated proportion of the range that has been visited | |||||
| by the iterator, from 0.0 to 1.0. | |||||
| */ | |||||
| float getEstimatedProgress() const; | |||||
| private: | private: | ||||
| std::weak_ptr<DirectoryIterator> iterator; | |||||
| File file; | File file; | ||||
| Time modTime; | Time modTime; | ||||
| Time creationTime; | Time creationTime; | ||||
| @@ -32,7 +32,7 @@ | |||||
| ID: juce_core | ID: juce_core | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE core classes | name: JUCE core classes | ||||
| description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. | description: The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -20,7 +20,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) | |||||
| #include <libkern/OSByteOrder.h> | #include <libkern/OSByteOrder.h> | ||||
| #endif | #endif | ||||
| @@ -39,13 +39,6 @@ inline void zerostruct (Type& structure) noexcept { memset ((v | |||||
| template <typename Type> | template <typename Type> | ||||
| inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } | inline void deleteAndZero (Type& pointer) { delete pointer; pointer = nullptr; } | ||||
| /** A handy function which adds a number of bytes to any type of pointer and returns the result. | |||||
| This can be useful to avoid casting pointers to a char* and back when you want to move them by | |||||
| a specific number of bytes, | |||||
| */ | |||||
| template <typename Type, typename IntegerType> | |||||
| inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept { return reinterpret_cast<Type*> (const_cast<char*> (reinterpret_cast<const char*> (basePointer)) + bytes); } | |||||
| /** A handy function to round up a pointer to the nearest multiple of a given number of bytes. | /** A handy function to round up a pointer to the nearest multiple of a given number of bytes. | ||||
| alignmentBytes must be a power of two. */ | alignmentBytes must be a power of two. */ | ||||
| template <typename Type, typename IntegerType> | template <typename Type, typename IntegerType> | ||||
| @@ -83,6 +76,53 @@ inline void writeUnaligned (void* dstPtr, Type value) noexcept | |||||
| memcpy (dstPtr, &value, sizeof (Type)); | memcpy (dstPtr, &value, sizeof (Type)); | ||||
| } | } | ||||
| //============================================================================== | |||||
| /** Casts a pointer to another type via `void*`, which suppresses the cast-align | |||||
| warning which sometimes arises when casting pointers to types with different | |||||
| alignment. | |||||
| You should only use this when you know for a fact that the input pointer points | |||||
| to a region that has suitable alignment for `Type`, e.g. regions returned from | |||||
| malloc/calloc that should be suitable for any non-over-aligned type. | |||||
| */ | |||||
| template <typename Type, typename std::enable_if<std::is_pointer<Type>::value, int>::type = 0> | |||||
| inline Type unalignedPointerCast (void* ptr) noexcept | |||||
| { | |||||
| return reinterpret_cast<Type> (ptr); | |||||
| } | |||||
| /** Casts a pointer to another type via `void*`, which suppresses the cast-align | |||||
| warning which sometimes arises when casting pointers to types with different | |||||
| alignment. | |||||
| You should only use this when you know for a fact that the input pointer points | |||||
| to a region that has suitable alignment for `Type`, e.g. regions returned from | |||||
| malloc/calloc that should be suitable for any non-over-aligned type. | |||||
| */ | |||||
| template <typename Type, typename std::enable_if<std::is_pointer<Type>::value, int>::type = 0> | |||||
| inline Type unalignedPointerCast (const void* ptr) noexcept | |||||
| { | |||||
| return reinterpret_cast<Type> (ptr); | |||||
| } | |||||
| /** A handy function which adds a number of bytes to any type of pointer and returns the result. | |||||
| This can be useful to avoid casting pointers to a char* and back when you want to move them by | |||||
| a specific number of bytes, | |||||
| */ | |||||
| template <typename Type, typename IntegerType> | |||||
| inline Type* addBytesToPointer (Type* basePointer, IntegerType bytes) noexcept | |||||
| { | |||||
| return unalignedPointerCast<Type*> (reinterpret_cast<char*> (basePointer) + bytes); | |||||
| } | |||||
| /** A handy function which adds a number of bytes to any type of pointer and returns the result. | |||||
| This can be useful to avoid casting pointers to a char* and back when you want to move them by | |||||
| a specific number of bytes, | |||||
| */ | |||||
| template <typename Type, typename IntegerType> | |||||
| inline const Type* addBytesToPointer (const Type* basePointer, IntegerType bytes) noexcept | |||||
| { | |||||
| return unalignedPointerCast<const Type*> (reinterpret_cast<const char*> (basePointer) + bytes); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC || JUCE_IOS || DOXYGEN | #if JUCE_MAC || JUCE_IOS || DOXYGEN | ||||
| @@ -215,8 +215,8 @@ double Time::getMillisecondCounterHiRes() noexcept | |||||
| bool Time::setSystemTimeToThisTime() const | bool Time::setSystemTimeToThisTime() const | ||||
| { | { | ||||
| timeval t; | timeval t; | ||||
| t.tv_sec = millisSinceEpoch / 1000; | |||||
| t.tv_usec = (millisSinceEpoch - t.tv_sec * 1000) * 1000; | |||||
| t.tv_sec = decltype (timeval::tv_sec) (millisSinceEpoch / 1000); | |||||
| t.tv_usec = decltype (timeval::tv_usec) ((millisSinceEpoch - t.tv_sec * 1000) * 1000); | |||||
| return settimeofday (&t, nullptr) == 0; | return settimeofday (&t, nullptr) == 0; | ||||
| } | } | ||||
| @@ -424,13 +424,23 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||||
| StringArray params; | StringArray params; | ||||
| params.addTokens (parameters, true); | params.addTokens (parameters, true); | ||||
| NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; | |||||
| NSMutableArray* paramArray = [[NSMutableArray new] autorelease]; | NSMutableArray* paramArray = [[NSMutableArray new] autorelease]; | ||||
| for (int i = 0; i < params.size(); ++i) | for (int i = 0; i < params.size(); ++i) | ||||
| [paramArray addObject: juceStringToNS (params[i])]; | [paramArray addObject: juceStringToNS (params[i])]; | ||||
| #if (defined MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_15 | |||||
| auto config = [NSWorkspaceOpenConfiguration configuration]; | |||||
| [config setCreatesNewApplicationInstance: YES]; | |||||
| config.arguments = paramArray; | |||||
| [workspace openApplicationAtURL: filenameAsURL | |||||
| configuration: config | |||||
| completionHandler: nil]; | |||||
| return true; | |||||
| #else | |||||
| NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; | |||||
| [dict setObject: paramArray | [dict setObject: paramArray | ||||
| forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")]; | forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")]; | ||||
| @@ -438,6 +448,7 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||||
| options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance | options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance | ||||
| configuration: dict | configuration: dict | ||||
| error: nil]; | error: nil]; | ||||
| #endif | |||||
| } | } | ||||
| if (file.exists()) | if (file.exists()) | ||||
| @@ -137,11 +137,17 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() | |||||
| StringArray parts; | StringArray parts; | ||||
| parts.addTokens (getOSXVersion(), ".", StringRef()); | parts.addTokens (getOSXVersion(), ".", StringRef()); | ||||
| jassert (parts[0].getIntValue() == 10); | |||||
| const int major = parts[1].getIntValue(); | |||||
| jassert (major > 2); | |||||
| const auto major = parts[0].getIntValue(); | |||||
| const auto minor = parts[1].getIntValue(); | |||||
| return (OperatingSystemType) (major + MacOSX_10_4 - 4); | |||||
| if (major == 10) | |||||
| { | |||||
| jassert (minor > 2); | |||||
| return (OperatingSystemType) (minor + MacOSX_10_7 - 7); | |||||
| } | |||||
| jassert (major == 11 && minor == 0); | |||||
| return MacOSX_11_0; | |||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -199,10 +205,8 @@ bool SystemStats::isOperatingSystem64Bit() | |||||
| { | { | ||||
| #if JUCE_IOS | #if JUCE_IOS | ||||
| return false; | return false; | ||||
| #elif JUCE_64BIT | |||||
| return true; | |||||
| #else | #else | ||||
| return getOperatingSystemType() >= MacOSX_10_6; | |||||
| return true; | |||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -193,29 +193,33 @@ NSRect makeNSRect (const RectangleType& r) noexcept | |||||
| static_cast<CGFloat> (r.getHeight())); | static_cast<CGFloat> (r.getHeight())); | ||||
| } | } | ||||
| #endif | #endif | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| // This is necessary as on iOS builds, some arguments may be passed on registers | |||||
| // depending on the argument type. The re-cast objc_msgSendSuper to a function | |||||
| // take the same arguments as the target method. | |||||
| template <typename ReturnValue, typename... Params> | |||||
| ReturnValue ObjCMsgSendSuper (struct objc_super* s, SEL sel, Params... params) | |||||
| { | |||||
| using SuperFn = ReturnValue (*)(struct objc_super*, SEL, Params...); | |||||
| SuperFn fn = reinterpret_cast<SuperFn> (objc_msgSendSuper); | |||||
| return fn (s, sel, params...); | |||||
| } | |||||
| #if JUCE_INTEL | |||||
| template <typename T> | |||||
| struct NeedsStret { static constexpr auto value = sizeof (T) > 16; }; | |||||
| template<> | |||||
| struct NeedsStret<void> { static constexpr auto value = false; }; | |||||
| // These hacks are a workaround for newer Xcode builds which by default prevent calls to these objc functions.. | |||||
| typedef id (*MsgSendSuperFn) (struct objc_super*, SEL, ...); | |||||
| inline MsgSendSuperFn getMsgSendSuperFn() noexcept { return (MsgSendSuperFn) (void*) objc_msgSendSuper; } | |||||
| template <typename T, bool b = NeedsStret<T>::value> | |||||
| struct MetaSuperFn { static constexpr auto value = objc_msgSendSuper_stret; }; | |||||
| #if ! JUCE_IOS | |||||
| typedef double (*MsgSendFPRetFn) (id, SEL op, ...); | |||||
| inline MsgSendFPRetFn getMsgSendFPRetFn() noexcept { return (MsgSendFPRetFn) (void*) objc_msgSend_fpret; } | |||||
| #endif | |||||
| template <typename T> | |||||
| struct MetaSuperFn<T, false> { static constexpr auto value = objc_msgSendSuper; }; | |||||
| #else | |||||
| template <typename> | |||||
| struct MetaSuperFn { static constexpr auto value = objc_msgSendSuper; }; | |||||
| #endif | #endif | ||||
| template <typename SuperType, typename ReturnType, typename... Params> | |||||
| static ReturnType ObjCMsgSendSuper (id self, SEL sel, Params... params) | |||||
| { | |||||
| using SuperFn = ReturnType (*) (struct objc_super*, SEL, Params...); | |||||
| const auto fn = reinterpret_cast<SuperFn> (MetaSuperFn<ReturnType>::value); | |||||
| objc_super s = { self, [SuperType class] }; | |||||
| return fn (&s, sel, params...); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| struct NSObjectDeleter | struct NSObjectDeleter | ||||
| { | { | ||||
| @@ -236,7 +240,10 @@ struct ObjCClass | |||||
| ~ObjCClass() | ~ObjCClass() | ||||
| { | { | ||||
| objc_disposeClassPair (cls); | |||||
| auto kvoSubclassName = String ("NSKVONotifying_") + class_getName (cls); | |||||
| if (objc_getClass (kvoSubclassName.toUTF8()) == nullptr) | |||||
| objc_disposeClassPair (cls); | |||||
| } | } | ||||
| void registerClass() | void registerClass() | ||||
| @@ -287,13 +294,11 @@ struct ObjCClass | |||||
| jassert (b); ignoreUnused (b); | jassert (b); ignoreUnused (b); | ||||
| } | } | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| static id sendSuperclassMessage (id self, SEL selector) | |||||
| template <typename ReturnType, typename... Params> | |||||
| static ReturnType sendSuperclassMessage (id self, SEL sel, Params... params) | |||||
| { | { | ||||
| objc_super s = { self, [SuperclassType class] }; | |||||
| return getMsgSendSuperFn() (&s, selector); | |||||
| return ObjCMsgSendSuper<SuperclassType, ReturnType, Params...> (self, sel, params...); | |||||
| } | } | ||||
| #endif | |||||
| template <typename Type> | template <typename Type> | ||||
| static Type getIvar (id self, const char* name) | static Type getIvar (id self, const char* name) | ||||
| @@ -330,18 +335,14 @@ struct ObjCLifetimeManagedClass : public ObjCClass<NSObject> | |||||
| addMethod (@selector (dealloc), dealloc, "v@:"); | addMethod (@selector (dealloc), dealloc, "v@:"); | ||||
| registerClass(); | registerClass(); | ||||
| } | } | ||||
| static id initWithJuceObject (id _self, SEL, JuceClass* obj) | static id initWithJuceObject (id _self, SEL, JuceClass* obj) | ||||
| { | { | ||||
| NSObject* self = _self; | |||||
| objc_super s = { self, [NSObject class] }; | |||||
| self = ObjCMsgSendSuper<NSObject*> (&s, @selector(init)); | |||||
| NSObject* self = sendSuperclassMessage<NSObject*> (_self, @selector (init)); | |||||
| object_setInstanceVariable (self, "cppObject", obj); | object_setInstanceVariable (self, "cppObject", obj); | ||||
| return self; | return self; | ||||
| } | } | ||||
| @@ -353,11 +354,9 @@ struct ObjCLifetimeManagedClass : public ObjCClass<NSObject> | |||||
| object_setInstanceVariable (_self, "cppObject", nullptr); | object_setInstanceVariable (_self, "cppObject", nullptr); | ||||
| } | } | ||||
| objc_super s = { _self, [NSObject class] }; | |||||
| ObjCMsgSendSuper<void> (&s, @selector(dealloc)); | |||||
| sendSuperclassMessage<void> (_self, @selector (dealloc)); | |||||
| } | } | ||||
| static ObjCLifetimeManagedClass objCLifetimeManagedClass; | static ObjCLifetimeManagedClass objCLifetimeManagedClass; | ||||
| }; | }; | ||||
| @@ -71,8 +71,8 @@ namespace | |||||
| { | { | ||||
| if (ifa->ifa_addr->sa_family == AF_INET) | if (ifa->ifa_addr->sa_family == AF_INET) | ||||
| { | { | ||||
| auto interfaceAddressInfo = reinterpret_cast<sockaddr_in*> (ifa->ifa_addr); | |||||
| auto broadcastAddressInfo = reinterpret_cast<sockaddr_in*> (ifa->ifa_dstaddr); | |||||
| auto interfaceAddressInfo = unalignedPointerCast<sockaddr_in*> (ifa->ifa_addr); | |||||
| auto broadcastAddressInfo = unalignedPointerCast<sockaddr_in*> (ifa->ifa_dstaddr); | |||||
| if (interfaceAddressInfo->sin_addr.s_addr != INADDR_NONE) | if (interfaceAddressInfo->sin_addr.s_addr != INADDR_NONE) | ||||
| { | { | ||||
| @@ -83,8 +83,8 @@ namespace | |||||
| } | } | ||||
| else if (ifa->ifa_addr->sa_family == AF_INET6) | else if (ifa->ifa_addr->sa_family == AF_INET6) | ||||
| { | { | ||||
| interfaceInfo.interfaceAddress = makeAddress (reinterpret_cast<sockaddr_in6*> (ifa->ifa_addr)); | |||||
| interfaceInfo.broadcastAddress = makeAddress (reinterpret_cast<sockaddr_in6*> (ifa->ifa_dstaddr)); | |||||
| interfaceInfo.interfaceAddress = makeAddress (unalignedPointerCast<sockaddr_in6*> (ifa->ifa_addr)); | |||||
| interfaceInfo.broadcastAddress = makeAddress (unalignedPointerCast<sockaddr_in6*> (ifa->ifa_dstaddr)); | |||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| @@ -134,6 +134,17 @@ public: | |||||
| return this->QueryInterface (__uuidof (OtherComClass), destObject); | return this->QueryInterface (__uuidof (OtherComClass), destObject); | ||||
| } | } | ||||
| template<class OtherComClass> | |||||
| ComSmartPtr<OtherComClass> getInterface() const | |||||
| { | |||||
| ComSmartPtr<OtherComClass> destObject; | |||||
| if (QueryInterface (destObject) == S_OK) | |||||
| return destObject; | |||||
| return nullptr; | |||||
| } | |||||
| private: | private: | ||||
| ComClass* p = nullptr; | ComClass* p = nullptr; | ||||
| @@ -283,9 +283,6 @@ String SystemStats::getOperatingSystemName() | |||||
| case Android: JUCE_FALLTHROUGH | case Android: JUCE_FALLTHROUGH | ||||
| case iOS: JUCE_FALLTHROUGH | case iOS: JUCE_FALLTHROUGH | ||||
| case MacOSX_10_4: JUCE_FALLTHROUGH | |||||
| case MacOSX_10_5: JUCE_FALLTHROUGH | |||||
| case MacOSX_10_6: JUCE_FALLTHROUGH | |||||
| case MacOSX_10_7: JUCE_FALLTHROUGH | case MacOSX_10_7: JUCE_FALLTHROUGH | ||||
| case MacOSX_10_8: JUCE_FALLTHROUGH | case MacOSX_10_8: JUCE_FALLTHROUGH | ||||
| case MacOSX_10_9: JUCE_FALLTHROUGH | case MacOSX_10_9: JUCE_FALLTHROUGH | ||||
| @@ -448,7 +448,7 @@ public: | |||||
| if (! isRunning()) | if (! isRunning()) | ||||
| break; | break; | ||||
| Thread::yield(); | |||||
| Thread::sleep (1); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -66,12 +66,14 @@ namespace juce | |||||
| @see jassert() | @see jassert() | ||||
| */ | */ | ||||
| #define JUCE_BREAK_IN_DEBUGGER { ::kill (0, SIGTRAP); } | #define JUCE_BREAK_IN_DEBUGGER { ::kill (0, SIGTRAP); } | ||||
| #elif JUCE_MAC && JUCE_CLANG && JUCE_ARM | |||||
| #define JUCE_BREAK_IN_DEBUGGER { __builtin_debugtrap(); } | |||||
| #elif JUCE_MSVC | #elif JUCE_MSVC | ||||
| #ifndef __INTEL_COMPILER | #ifndef __INTEL_COMPILER | ||||
| #pragma intrinsic (__debugbreak) | #pragma intrinsic (__debugbreak) | ||||
| #endif | #endif | ||||
| #define JUCE_BREAK_IN_DEBUGGER { __debugbreak(); } | #define JUCE_BREAK_IN_DEBUGGER { __debugbreak(); } | ||||
| #elif JUCE_GCC || JUCE_MAC | |||||
| #elif JUCE_INTEL && (JUCE_GCC || JUCE_MAC) | |||||
| #if JUCE_NO_INLINE_ASM | #if JUCE_NO_INLINE_ASM | ||||
| #define JUCE_BREAK_IN_DEBUGGER { } | #define JUCE_BREAK_IN_DEBUGGER { } | ||||
| #else | #else | ||||
| @@ -29,7 +29,7 @@ | |||||
| */ | */ | ||||
| #define JUCE_MAJOR_VERSION 6 | #define JUCE_MAJOR_VERSION 6 | ||||
| #define JUCE_MINOR_VERSION 0 | #define JUCE_MINOR_VERSION 0 | ||||
| #define JUCE_BUILDNUMBER 0 | |||||
| #define JUCE_BUILDNUMBER 4 | |||||
| /** Current JUCE version number. | /** Current JUCE version number. | ||||
| @@ -52,9 +52,6 @@ public: | |||||
| Android = 0x0800, | Android = 0x0800, | ||||
| iOS = 0x1000, | iOS = 0x1000, | ||||
| MacOSX_10_4 = MacOSX | 4, | |||||
| MacOSX_10_5 = MacOSX | 5, | |||||
| MacOSX_10_6 = MacOSX | 6, | |||||
| MacOSX_10_7 = MacOSX | 7, | MacOSX_10_7 = MacOSX | 7, | ||||
| MacOSX_10_8 = MacOSX | 8, | MacOSX_10_8 = MacOSX | 8, | ||||
| MacOSX_10_9 = MacOSX | 9, | MacOSX_10_9 = MacOSX | 9, | ||||
| @@ -63,6 +60,8 @@ public: | |||||
| MacOSX_10_12 = MacOSX | 12, | MacOSX_10_12 = MacOSX | 12, | ||||
| MacOSX_10_13 = MacOSX | 13, | MacOSX_10_13 = MacOSX | 13, | ||||
| MacOSX_10_14 = MacOSX | 14, | MacOSX_10_14 = MacOSX | 14, | ||||
| MacOSX_10_15 = MacOSX | 15, | |||||
| MacOSX_11_0 = MacOSX | 16, | |||||
| Win2000 = Windows | 1, | Win2000 = Windows | 1, | ||||
| WinXP = Windows | 2, | WinXP = Windows | 2, | ||||
| @@ -58,7 +58,6 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #if defined (_WIN32) || defined (_WIN64) | #if defined (_WIN32) || defined (_WIN64) | ||||
| #define JUCE_WIN32 1 | |||||
| #define JUCE_WINDOWS 1 | #define JUCE_WINDOWS 1 | ||||
| #elif defined (JUCE_ANDROID) | #elif defined (JUCE_ANDROID) | ||||
| #undef JUCE_ANDROID | #undef JUCE_ANDROID | ||||
| @@ -68,7 +68,7 @@ public: | |||||
| static CharPointerType createUninitialisedBytes (size_t numBytes) | static CharPointerType createUninitialisedBytes (size_t numBytes) | ||||
| { | { | ||||
| numBytes = (numBytes + 3) & ~(size_t) 3; | numBytes = (numBytes + 3) & ~(size_t) 3; | ||||
| auto s = reinterpret_cast<StringHolder*> (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); | |||||
| auto s = unalignedPointerCast<StringHolder*> (new char [sizeof (StringHolder) - sizeof (CharType) + numBytes]); | |||||
| s->refCount.value = 0; | s->refCount.value = 0; | ||||
| s->allocatedNumBytes = numBytes; | s->allocatedNumBytes = numBytes; | ||||
| return CharPointerType (s->text); | return CharPointerType (s->text); | ||||
| @@ -210,7 +210,7 @@ private: | |||||
| static StringHolder* bufferFromText (const CharPointerType text) noexcept | static StringHolder* bufferFromText (const CharPointerType text) noexcept | ||||
| { | { | ||||
| // (Can't use offsetof() here because of warnings about this not being a POD) | // (Can't use offsetof() here because of warnings about this not being a POD) | ||||
| return reinterpret_cast<StringHolder*> (reinterpret_cast<char*> (text.getAddress()) | |||||
| return unalignedPointerCast<StringHolder*> (reinterpret_cast<char*> (text.getAddress()) | |||||
| - (reinterpret_cast<size_t> (reinterpret_cast<StringHolder*> (128)->text) - 128)); | - (reinterpret_cast<size_t> (reinterpret_cast<StringHolder*> (128)->text) - 128)); | ||||
| } | } | ||||
| @@ -1991,7 +1991,7 @@ String String::createStringFromData (const void* const unknownData, int size) | |||||
| StringCreationHelper builder ((size_t) numChars); | StringCreationHelper builder ((size_t) numChars); | ||||
| auto src = reinterpret_cast<const uint16*> (data + 2); | |||||
| auto src = unalignedPointerCast<const uint16*> (data + 2); | |||||
| if (CharPointer_UTF16::isByteOrderMarkBigEndian (data)) | if (CharPointer_UTF16::isByteOrderMarkBigEndian (data)) | ||||
| { | { | ||||
| @@ -2061,19 +2061,19 @@ struct StringEncodingConverter | |||||
| template <> | template <> | ||||
| struct StringEncodingConverter<CharPointer_UTF8, CharPointer_UTF8> | struct StringEncodingConverter<CharPointer_UTF8, CharPointer_UTF8> | ||||
| { | { | ||||
| static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 (reinterpret_cast<CharPointer_UTF8::CharType*> (source.getCharPointer().getAddress())); } | |||||
| static CharPointer_UTF8 convert (const String& source) noexcept { return CharPointer_UTF8 (unalignedPointerCast<CharPointer_UTF8::CharType*> (source.getCharPointer().getAddress())); } | |||||
| }; | }; | ||||
| template <> | template <> | ||||
| struct StringEncodingConverter<CharPointer_UTF16, CharPointer_UTF16> | struct StringEncodingConverter<CharPointer_UTF16, CharPointer_UTF16> | ||||
| { | { | ||||
| static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 (reinterpret_cast<CharPointer_UTF16::CharType*> (source.getCharPointer().getAddress())); } | |||||
| static CharPointer_UTF16 convert (const String& source) noexcept { return CharPointer_UTF16 (unalignedPointerCast<CharPointer_UTF16::CharType*> (source.getCharPointer().getAddress())); } | |||||
| }; | }; | ||||
| template <> | template <> | ||||
| struct StringEncodingConverter<CharPointer_UTF32, CharPointer_UTF32> | struct StringEncodingConverter<CharPointer_UTF32, CharPointer_UTF32> | ||||
| { | { | ||||
| static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 (reinterpret_cast<CharPointer_UTF32::CharType*> (source.getCharPointer().getAddress())); } | |||||
| static CharPointer_UTF32 convert (const String& source) noexcept { return CharPointer_UTF32 (unalignedPointerCast<CharPointer_UTF32::CharType*> (source.getCharPointer().getAddress())); } | |||||
| }; | }; | ||||
| CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter<CharPointerType, CharPointer_UTF8 >::convert (*this); } | CharPointer_UTF8 String::toUTF8() const { return StringEncodingConverter<CharPointerType, CharPointer_UTF8 >::convert (*this); } | ||||
| @@ -20,7 +20,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) | |||||
| // Annoyingly we can only forward-declare a typedef by forward-declaring the | // Annoyingly we can only forward-declare a typedef by forward-declaring the | ||||
| // aliased type | // aliased type | ||||
| #if __has_attribute(objc_bridge) | #if __has_attribute(objc_bridge) | ||||
| @@ -35,7 +35,7 @@ | |||||
| ID: juce_data_structures | ID: juce_data_structures | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE data model helper classes | name: JUCE data model helper classes | ||||
| description: Classes for undo/redo management, and smart data structures. | description: Classes for undo/redo management, and smart data structures. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -32,7 +32,7 @@ | |||||
| ID: juce_events | ID: juce_events | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE message and event handling classes | name: JUCE message and event handling classes | ||||
| description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -144,7 +144,11 @@ private: | |||||
| { | { | ||||
| if (notification.userInfo != nil) | if (notification.userInfo != nil) | ||||
| { | { | ||||
| NSUserNotification* userNotification = [notification.userInfo objectForKey: nsStringLiteral ("NSApplicationLaunchUserNotificationKey")]; | |||||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||||
| // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a | |||||
| // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type | |||||
| NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; | |||||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||||
| if (userNotification != nil && userNotification.userInfo != nil) | if (userNotification != nil && userNotification.userInfo != nil) | ||||
| didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | ||||
| @@ -303,7 +303,6 @@ public: | |||||
| /** Returns true if the font is underlined. */ | /** Returns true if the font is underlined. */ | ||||
| bool isUnderlined() const noexcept; | bool isUnderlined() const noexcept; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the font's horizontal scale. | /** Returns the font's horizontal scale. | ||||
| A value of 1.0 is the normal scale, less than this will be narrower, greater | A value of 1.0 is the normal scale, less than this will be narrower, greater | ||||
| @@ -464,7 +463,6 @@ public: | |||||
| */ | */ | ||||
| static Font fromString (const String& fontDescription); | static Font fromString (const String& fontDescription); | ||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| class SharedFontInternal; | class SharedFontInternal; | ||||
| @@ -120,12 +120,22 @@ public: | |||||
| Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; } | Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; } | ||||
| /** Returns a point whose coordinates are multiplied by a given scalar value. */ | /** Returns a point whose coordinates are multiplied by a given scalar value. */ | ||||
| template <typename FloatType> | |||||
| constexpr Point operator* (FloatType multiplier) const noexcept { return Point ((ValueType) ((FloatType) x * multiplier), (ValueType) ((FloatType) y * multiplier)); } | |||||
| template <typename OtherType> | |||||
| constexpr Point operator* (OtherType multiplier) const noexcept | |||||
| { | |||||
| using CommonType = typename std::common_type<ValueType, OtherType>::type; | |||||
| return Point ((ValueType) ((CommonType) x * (CommonType) multiplier), | |||||
| (ValueType) ((CommonType) y * (CommonType) multiplier)); | |||||
| } | |||||
| /** Returns a point whose coordinates are divided by a given scalar value. */ | /** Returns a point whose coordinates are divided by a given scalar value. */ | ||||
| template <typename FloatType> | |||||
| constexpr Point operator/ (FloatType divisor) const noexcept { return Point ((ValueType) ((FloatType) x / divisor), (ValueType) ((FloatType) y / divisor)); } | |||||
| template <typename OtherType> | |||||
| constexpr Point operator/ (OtherType divisor) const noexcept | |||||
| { | |||||
| using CommonType = typename std::common_type<ValueType, OtherType>::type; | |||||
| return Point ((ValueType) ((CommonType) x / (CommonType) divisor), | |||||
| (ValueType) ((CommonType) y / (CommonType) divisor)); | |||||
| } | |||||
| /** Multiplies the point's coordinates by a scalar value. */ | /** Multiplies the point's coordinates by a scalar value. */ | ||||
| template <typename FloatType> | template <typename FloatType> | ||||
| @@ -75,10 +75,6 @@ | |||||
| #import <QuartzCore/QuartzCore.h> | #import <QuartzCore/QuartzCore.h> | ||||
| #import <CoreText/CoreText.h> | #import <CoreText/CoreText.h> | ||||
| #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_3_2 | |||||
| #error "JUCE no longer supports targets earlier than iOS 3.2" | |||||
| #endif | |||||
| #elif JUCE_LINUX | #elif JUCE_LINUX | ||||
| #ifndef JUCE_USE_FREETYPE | #ifndef JUCE_USE_FREETYPE | ||||
| #define JUCE_USE_FREETYPE 1 | #define JUCE_USE_FREETYPE 1 | ||||
| @@ -35,7 +35,7 @@ | |||||
| ID: juce_graphics | ID: juce_graphics | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE graphics classes | name: JUCE graphics classes | ||||
| description: Classes for 2D vector graphics, image loading/saving, font handling, etc. | description: Classes for 2D vector graphics, image loading/saving, font handling, etc. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -79,7 +79,7 @@ | |||||
| /** Config: JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | /** Config: JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | ||||
| Setting this flag will turn off CoreGraphics font smoothing, which some people | |||||
| Setting this flag will turn off CoreGraphics font smoothing on macOS, which some people | |||||
| find makes the text too 'fat' for their taste. | find makes the text too 'fat' for their taste. | ||||
| */ | */ | ||||
| #ifndef JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | #ifndef JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | ||||
| @@ -189,6 +189,15 @@ ImagePixelData::Ptr NativeImageType::create (Image::PixelFormat format, int widt | |||||
| return *new CoreGraphicsPixelData (format == Image::RGB ? Image::ARGB : format, width, height, clearImage); | return *new CoreGraphicsPixelData (format == Image::RGB ? Image::ARGB : format, width, height, clearImage); | ||||
| } | } | ||||
| //============================================================================== | |||||
| struct ScopedCGContextState | |||||
| { | |||||
| explicit ScopedCGContextState (CGContextRef c) : context (c) { CGContextSaveGState (context); } | |||||
| ~ScopedCGContextState() { CGContextRestoreGState (context); } | |||||
| CGContextRef context; | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) | CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) | ||||
| : context (c), | : context (c), | ||||
| @@ -198,15 +207,18 @@ CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) | |||||
| CGContextRetain (context.get()); | CGContextRetain (context.get()); | ||||
| CGContextSaveGState (context.get()); | CGContextSaveGState (context.get()); | ||||
| #if JUCE_MAC | |||||
| bool enableFontSmoothing | bool enableFontSmoothing | ||||
| #if JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | |||||
| = false; | |||||
| #else | |||||
| = true; | |||||
| #endif | |||||
| #if JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING | |||||
| = false; | |||||
| #else | |||||
| = true; | |||||
| #endif | |||||
| CGContextSetShouldSmoothFonts (context.get(), enableFontSmoothing); | CGContextSetShouldSmoothFonts (context.get(), enableFontSmoothing); | ||||
| CGContextSetAllowsFontSmoothing (context.get(), enableFontSmoothing); | CGContextSetAllowsFontSmoothing (context.get(), enableFontSmoothing); | ||||
| #endif | |||||
| CGContextSetShouldAntialias (context.get(), true); | CGContextSetShouldAntialias (context.get(), true); | ||||
| CGContextSetBlendMode (context.get(), kCGBlendModeNormal); | CGContextSetBlendMode (context.get(), kCGBlendModeNormal); | ||||
| rgbColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceSRGB)); | rgbColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceSRGB)); | ||||
| @@ -455,26 +467,23 @@ void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, bool replaceExisting | |||||
| { | { | ||||
| CGContextFillRect (context.get(), cgRect); | CGContextFillRect (context.get(), cgRect); | ||||
| } | } | ||||
| else if (state->fillType.isGradient()) | |||||
| { | |||||
| CGContextSaveGState (context.get()); | |||||
| CGContextClipToRect (context.get(), cgRect); | |||||
| drawGradient(); | |||||
| CGContextRestoreGState (context.get()); | |||||
| } | |||||
| else | else | ||||
| { | { | ||||
| CGContextSaveGState (context.get()); | |||||
| ScopedCGContextState scopedState (context.get()); | |||||
| CGContextClipToRect (context.get(), cgRect); | CGContextClipToRect (context.get(), cgRect); | ||||
| drawImage (state->fillType.image, state->fillType.transform, true); | |||||
| CGContextRestoreGState (context.get()); | |||||
| if (state->fillType.isGradient()) | |||||
| drawGradient(); | |||||
| else | |||||
| drawImage (state->fillType.image, state->fillType.transform, true); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform) | void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& transform) | ||||
| { | { | ||||
| CGContextSaveGState (context.get()); | |||||
| ScopedCGContextState scopedState (context.get()); | |||||
| if (state->fillType.isColour()) | if (state->fillType.isColour()) | ||||
| { | { | ||||
| @@ -501,8 +510,6 @@ void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& tra | |||||
| else | else | ||||
| drawImage (state->fillType.image, state->fillType.transform, true); | drawImage (state->fillType.image, state->fillType.transform, true); | ||||
| } | } | ||||
| CGContextRestoreGState (context.get()); | |||||
| } | } | ||||
| void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) | void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) | ||||
| @@ -519,7 +526,7 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans | |||||
| : rgbColourSpace.get(); | : rgbColourSpace.get(); | ||||
| auto image = detail::ImagePtr { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) }; | auto image = detail::ImagePtr { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) }; | ||||
| CGContextSaveGState (context.get()); | |||||
| ScopedCGContextState scopedState (context.get()); | |||||
| CGContextSetAlpha (context.get(), state->fillType.getOpacity()); | CGContextSetAlpha (context.get(), state->fillType.getOpacity()); | ||||
| flip(); | flip(); | ||||
| @@ -563,8 +570,6 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans | |||||
| { | { | ||||
| CGContextDrawImage (context.get(), imageRect, image.get()); | CGContextDrawImage (context.get(), imageRect, image.get()); | ||||
| } | } | ||||
| CGContextRestoreGState (context.get()); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -588,19 +593,16 @@ void CoreGraphicsContext::fillRectList (const RectangleList<float>& list) | |||||
| { | { | ||||
| CGContextFillRects (context.get(), rects, num); | CGContextFillRects (context.get(), rects, num); | ||||
| } | } | ||||
| else if (state->fillType.isGradient()) | |||||
| { | |||||
| CGContextSaveGState (context.get()); | |||||
| CGContextClipToRects (context.get(), rects, num); | |||||
| drawGradient(); | |||||
| CGContextRestoreGState (context.get()); | |||||
| } | |||||
| else | else | ||||
| { | { | ||||
| CGContextSaveGState (context.get()); | |||||
| ScopedCGContextState scopedState (context.get()); | |||||
| CGContextClipToRects (context.get(), rects, num); | CGContextClipToRects (context.get(), rects, num); | ||||
| drawImage (state->fillType.image, state->fillType.transform, true); | |||||
| CGContextRestoreGState (context.get()); | |||||
| if (state->fillType.isGradient()) | |||||
| drawGradient(); | |||||
| else | |||||
| drawImage (state->fillType.image, state->fillType.transform, true); | |||||
| } | } | ||||
| } | } | ||||
| @@ -650,7 +652,7 @@ void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& tra | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| CGContextSaveGState (context.get()); | |||||
| ScopedCGContextState scopedState (context.get()); | |||||
| flip(); | flip(); | ||||
| applyTransform (transform); | applyTransform (transform); | ||||
| @@ -662,8 +664,6 @@ void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& tra | |||||
| CGGlyph glyphs[1] = { (CGGlyph) glyphNumber }; | CGGlyph glyphs[1] = { (CGGlyph) glyphNumber }; | ||||
| CGPoint positions[1] = { { 0.0f, 0.0f } }; | CGPoint positions[1] = { { 0.0f, 0.0f } }; | ||||
| CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1); | CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1); | ||||
| CGContextRestoreGState (context.get()); | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| @@ -234,6 +234,15 @@ namespace CoreTextTypeLayout | |||||
| ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef)); | ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (ctFontRef)); | ||||
| CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef); | CFAttributedStringSetAttribute (attribString, range, kCTFontAttributeName, ctFontRef); | ||||
| if (attr.font.isUnderlined()) | |||||
| { | |||||
| auto underline = kCTUnderlineStyleSingle; | |||||
| auto numberRef = CFNumberCreate (nullptr, kCFNumberIntType, &underline); | |||||
| CFAttributedStringSetAttribute (attribString, range, kCTUnderlineStyleAttributeName, numberRef); | |||||
| CFRelease (numberRef); | |||||
| } | |||||
| auto extraKerning = attr.font.getExtraKerningFactor(); | auto extraKerning = attr.font.getExtraKerningFactor(); | ||||
| if (extraKerning != 0) | if (extraKerning != 0) | ||||
| @@ -463,6 +472,26 @@ namespace CoreTextTypeLayout | |||||
| String::fromCFString (cfsFontStyle), | String::fromCFString (cfsFontStyle), | ||||
| (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); | (float) (CTFontGetSize (ctRunFont) / fontHeightToPointsFactor)); | ||||
| auto isUnderlined = [&] | |||||
| { | |||||
| CFNumberRef underlineStyle; | |||||
| if (CFDictionaryGetValueIfPresent (runAttributes, kCTUnderlineStyleAttributeName, (const void**) &underlineStyle)) | |||||
| { | |||||
| if (CFGetTypeID (underlineStyle) == CFNumberGetTypeID()) | |||||
| { | |||||
| int value = 0; | |||||
| CFNumberGetValue (underlineStyle, kCFNumberLongType, (void*) &value); | |||||
| return value != 0; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| }(); | |||||
| glyphRun->font.setUnderline (isUnderlined); | |||||
| CFRelease (cfsFontStyle); | CFRelease (cfsFontStyle); | ||||
| CFRelease (cfsFontFamily); | CFRelease (cfsFontFamily); | ||||
| } | } | ||||
| @@ -216,6 +216,22 @@ struct ScalingHelpers | |||||
| roundToInt ((float) pos.getHeight() * scale)) : pos; | roundToInt ((float) pos.getHeight() * scale)) : pos; | ||||
| } | } | ||||
| static Rectangle<float> unscaledScreenPosToScaled (float scale, Rectangle<float> pos) noexcept | |||||
| { | |||||
| return scale != 1.0f ? Rectangle<float> (pos.getX() / scale, | |||||
| pos.getY() / scale, | |||||
| pos.getWidth() / scale, | |||||
| pos.getHeight() / scale) : pos; | |||||
| } | |||||
| static Rectangle<float> scaledScreenPosToUnscaled (float scale, Rectangle<float> pos) noexcept | |||||
| { | |||||
| return scale != 1.0f ? Rectangle<float> (pos.getX() * scale, | |||||
| pos.getY() * scale, | |||||
| pos.getWidth() * scale, | |||||
| pos.getHeight() * scale) : pos; | |||||
| } | |||||
| template <typename PointOrRect> | template <typename PointOrRect> | ||||
| static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept | static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept | ||||
| { | { | ||||
| @@ -766,7 +782,7 @@ bool Component::isOpaque() const noexcept | |||||
| //============================================================================== | //============================================================================== | ||||
| struct StandardCachedComponentImage : public CachedComponentImage | struct StandardCachedComponentImage : public CachedComponentImage | ||||
| { | { | ||||
| StandardCachedComponentImage (Component& c) noexcept : owner (c), scale (1.0f) {} | |||||
| StandardCachedComponentImage (Component& c) noexcept : owner (c) {} | |||||
| void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
| { | { | ||||
| @@ -820,7 +836,7 @@ private: | |||||
| Image image; | Image image; | ||||
| RectangleList<int> validArea; | RectangleList<int> validArea; | ||||
| Component& owner; | Component& owner; | ||||
| float scale; | |||||
| float scale = 1.0f; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandardCachedComponentImage) | ||||
| }; | }; | ||||
| @@ -1051,13 +1067,15 @@ int Component::getScreenY() const { return getScreenPositi | |||||
| Point<int> Component::getScreenPosition() const { return localPointToGlobal (Point<int>()); } | Point<int> Component::getScreenPosition() const { return localPointToGlobal (Point<int>()); } | ||||
| Rectangle<int> Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } | Rectangle<int> Component::getScreenBounds() const { return localAreaToGlobal (getLocalBounds()); } | ||||
| Point<int> Component::getLocalPoint (const Component* source, Point<int> point) const { return ComponentHelpers::convertCoordinate (this, source, point); } | |||||
| Point<float> Component::getLocalPoint (const Component* source, Point<float> point) const { return ComponentHelpers::convertCoordinate (this, source, point); } | |||||
| Rectangle<int> Component::getLocalArea (const Component* source, Rectangle<int> area) const { return ComponentHelpers::convertCoordinate (this, source, area); } | |||||
| Point<int> Component::getLocalPoint (const Component* source, Point<int> point) const { return ComponentHelpers::convertCoordinate (this, source, point); } | |||||
| Point<float> Component::getLocalPoint (const Component* source, Point<float> point) const { return ComponentHelpers::convertCoordinate (this, source, point); } | |||||
| Rectangle<int> Component::getLocalArea (const Component* source, Rectangle<int> area) const { return ComponentHelpers::convertCoordinate (this, source, area); } | |||||
| Rectangle<float> Component::getLocalArea (const Component* source, Rectangle<float> area) const { return ComponentHelpers::convertCoordinate (this, source, area); } | |||||
| Point<int> Component::localPointToGlobal (Point<int> point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } | |||||
| Point<float> Component::localPointToGlobal (Point<float> point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } | |||||
| Rectangle<int> Component::localAreaToGlobal (Rectangle<int> area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } | |||||
| Point<int> Component::localPointToGlobal (Point<int> point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } | |||||
| Point<float> Component::localPointToGlobal (Point<float> point) const { return ComponentHelpers::convertCoordinate (nullptr, this, point); } | |||||
| Rectangle<int> Component::localAreaToGlobal (Rectangle<int> area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } | |||||
| Rectangle<float> Component::localAreaToGlobal (Rectangle<float> area) const { return ComponentHelpers::convertCoordinate (nullptr, this, area); } | |||||
| //============================================================================== | //============================================================================== | ||||
| void Component::setBounds (int x, int y, int w, int h) | void Component::setBounds (int x, int y, int w, int h) | ||||
| @@ -369,6 +369,19 @@ public: | |||||
| Rectangle<int> getLocalArea (const Component* sourceComponent, | Rectangle<int> getLocalArea (const Component* sourceComponent, | ||||
| Rectangle<int> areaRelativeToSourceComponent) const; | Rectangle<int> areaRelativeToSourceComponent) const; | ||||
| /** Converts a rectangle to be relative to this component's coordinate space. | |||||
| This takes a rectangle that is relative to a different component, and returns its position relative | |||||
| to this component. If the sourceComponent parameter is null, the source rectangle is assumed to be | |||||
| a screen coordinate. | |||||
| If you've used setTransform() to apply one or more transforms to components, then the source rectangle | |||||
| may not actually be rectangular when converted to the target space, so in that situation this will return | |||||
| the smallest rectangle that fully contains the transformed area. | |||||
| */ | |||||
| Rectangle<float> getLocalArea (const Component* sourceComponent, | |||||
| Rectangle<float> areaRelativeToSourceComponent) const; | |||||
| /** Converts a point relative to this component's top-left into a screen coordinate. | /** Converts a point relative to this component's top-left into a screen coordinate. | ||||
| @see getLocalPoint, localAreaToGlobal | @see getLocalPoint, localAreaToGlobal | ||||
| */ | */ | ||||
| @@ -388,6 +401,15 @@ public: | |||||
| */ | */ | ||||
| Rectangle<int> localAreaToGlobal (Rectangle<int> localArea) const; | Rectangle<int> localAreaToGlobal (Rectangle<int> localArea) const; | ||||
| /** Converts a rectangle from this component's coordinate space to a screen coordinate. | |||||
| If you've used setTransform() to apply one or more transforms to components, then the source rectangle | |||||
| may not actually be rectangular when converted to the target space, so in that situation this will return | |||||
| the smallest rectangle that fully contains the transformed area. | |||||
| @see getLocalPoint, localPointToGlobal | |||||
| */ | |||||
| Rectangle<float> localAreaToGlobal (Rectangle<float> localArea) const; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Moves the component to a new position. | /** Moves the component to a new position. | ||||
| @@ -368,7 +368,7 @@ public: | |||||
| if (parseNextNumber (d, num, false)) | if (parseNextNumber (d, num, false)) | ||||
| { | { | ||||
| auto angle = degreesToRadians (num.getFloatValue()); | |||||
| auto angle = degreesToRadians (parseSafeFloat (num)); | |||||
| if (parseNextFlag (d, flagValue)) | if (parseNextFlag (d, flagValue)) | ||||
| { | { | ||||
| @@ -460,7 +460,7 @@ private: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable, const bool shouldParseClip = true) | |||||
| void parseSubElements (const XmlPath& xml, DrawableComposite& parentDrawable, bool shouldParseClip = true) | |||||
| { | { | ||||
| forEachXmlChildElement (*xml, e) | forEachXmlChildElement (*xml, e) | ||||
| { | { | ||||
| @@ -618,7 +618,7 @@ private: | |||||
| line.lineTo (x2, y2); | line.lineTo (x2, y2); | ||||
| } | } | ||||
| void parsePolygon (const XmlPath& xml, const bool isPolyline, Path& path) const | |||||
| void parsePolygon (const XmlPath& xml, bool isPolyline, Path& path) const | |||||
| { | { | ||||
| auto pointsAtt = xml->getStringAttribute ("points"); | auto pointsAtt = xml->getStringAttribute ("points"); | ||||
| auto points = pointsAtt.getCharPointer(); | auto points = pointsAtt.getCharPointer(); | ||||
| @@ -683,7 +683,7 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| Drawable* parseShape (const XmlPath& xml, Path& path, | Drawable* parseShape (const XmlPath& xml, Path& path, | ||||
| const bool shouldParseTransform = true, | |||||
| bool shouldParseTransform = true, | |||||
| AffineTransform* additonalTransform = nullptr) const | AffineTransform* additonalTransform = nullptr) const | ||||
| { | { | ||||
| if (shouldParseTransform && xml->hasAttribute ("transform")) | if (shouldParseTransform && xml->hasAttribute ("transform")) | ||||
| @@ -835,14 +835,14 @@ private: | |||||
| auto col = parseColour (fillXml.getChild (e), "stop-color", Colours::black); | auto col = parseColour (fillXml.getChild (e), "stop-color", Colours::black); | ||||
| auto opacity = getStyleAttribute (fillXml.getChild (e), "stop-opacity", "1"); | auto opacity = getStyleAttribute (fillXml.getChild (e), "stop-opacity", "1"); | ||||
| col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, opacity.getFloatValue())); | |||||
| col = col.withMultipliedAlpha (jlimit (0.0f, 1.0f, parseSafeFloat (opacity))); | |||||
| double offset = e->getDoubleAttribute ("offset"); | |||||
| auto offset = parseSafeFloat (e->getStringAttribute ("offset")); | |||||
| if (e->getStringAttribute ("offset").containsChar ('%')) | if (e->getStringAttribute ("offset").containsChar ('%')) | ||||
| offset *= 0.01; | |||||
| offset *= 0.01f; | |||||
| cg.addColour (jlimit (0.0, 1.0, offset), col); | |||||
| cg.addColour (jlimit (0.0f, 1.0f, offset), col); | |||||
| result = true; | result = true; | ||||
| } | } | ||||
| } | } | ||||
| @@ -983,10 +983,10 @@ private: | |||||
| float opacity = 1.0f; | float opacity = 1.0f; | ||||
| if (overallOpacity.isNotEmpty()) | if (overallOpacity.isNotEmpty()) | ||||
| opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue()); | |||||
| opacity = jlimit (0.0f, 1.0f, parseSafeFloat (overallOpacity)); | |||||
| if (fillOpacity.isNotEmpty()) | if (fillOpacity.isNotEmpty()) | ||||
| opacity *= (jlimit (0.0f, 1.0f, fillOpacity.getFloatValue())); | |||||
| opacity *= jlimit (0.0f, 1.0f, parseSafeFloat (fillOpacity)); | |||||
| String fill (getStyleAttribute (xml, fillAttribute)); | String fill (getStyleAttribute (xml, fillAttribute)); | ||||
| String urlID = parseURL (fill); | String urlID = parseURL (fill); | ||||
| @@ -1035,11 +1035,10 @@ private: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| Drawable* useText (const XmlPath& xml) const | Drawable* useText (const XmlPath& xml) const | ||||
| { | { | ||||
| auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), | |||||
| (float) xml->getDoubleAttribute ("y", 0.0)); | |||||
| auto translation = AffineTransform::translation (parseSafeFloat (xml->getStringAttribute ("x")), | |||||
| parseSafeFloat (xml->getStringAttribute ("y"))); | |||||
| UseTextOp op = { this, &translation, nullptr }; | UseTextOp op = { this, &translation, nullptr }; | ||||
| @@ -1099,7 +1098,7 @@ private: | |||||
| dt->setTransform (transform); | dt->setTransform (transform); | ||||
| dt->setColour (parseColour (xml, "fill", Colours::black) | dt->setColour (parseColour (xml, "fill", Colours::black) | ||||
| .withMultipliedAlpha (getStyleAttribute (xml, "fill-opacity", "1").getFloatValue())); | |||||
| .withMultipliedAlpha (parseSafeFloat (getStyleAttribute (xml, "fill-opacity", "1")))); | |||||
| Rectangle<float> bounds (xCoords[0], yCoords[0] - font.getAscent(), | Rectangle<float> bounds (xCoords[0], yCoords[0] - font.getAscent(), | ||||
| font.getStringWidthFloat (text), font.getHeight()); | font.getStringWidthFloat (text), font.getHeight()); | ||||
| @@ -1138,8 +1137,8 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| Drawable* useImage (const XmlPath& xml) const | Drawable* useImage (const XmlPath& xml) const | ||||
| { | { | ||||
| auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), | |||||
| (float) xml->getDoubleAttribute ("y", 0.0)); | |||||
| auto translation = AffineTransform::translation (parseSafeFloat (xml->getStringAttribute ("x")), | |||||
| parseSafeFloat (xml->getStringAttribute ("y"))); | |||||
| UseImageOp op = { this, &translation, nullptr }; | UseImageOp op = { this, &translation, nullptr }; | ||||
| @@ -1210,10 +1209,13 @@ private: | |||||
| setCommonAttributes (*di, xml); | setCommonAttributes (*di, xml); | ||||
| Rectangle<float> imageBounds ((float) xml->getDoubleAttribute ("x", 0.0), (float) xml->getDoubleAttribute ("y", 0.0), | |||||
| (float) xml->getDoubleAttribute ("width", image.getWidth()), (float) xml->getDoubleAttribute ("height", image.getHeight())); | |||||
| Rectangle<float> imageBounds (parseSafeFloat (xml->getStringAttribute ("x")), | |||||
| parseSafeFloat (xml->getStringAttribute ("y")), | |||||
| parseSafeFloat (xml->getStringAttribute ("width", String (image.getWidth()))), | |||||
| parseSafeFloat (xml->getStringAttribute ("height", String (image.getHeight())))); | |||||
| di->setImage (image.rescaled ((int) imageBounds.getWidth(), (int) imageBounds.getHeight())); | |||||
| di->setImage (image.rescaled ((int) imageBounds.getWidth(), | |||||
| (int) imageBounds.getHeight())); | |||||
| di->setTransformToFit (imageBounds, RectanglePlacement (parsePlacementFlags (xml->getStringAttribute ("preserveAspectRatio").trim()))); | di->setTransformToFit (imageBounds, RectanglePlacement (parsePlacementFlags (xml->getStringAttribute ("preserveAspectRatio").trim()))); | ||||
| @@ -1237,7 +1239,7 @@ private: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| bool parseCoord (String::CharPointerType& s, float& value, const bool allowUnits, const bool isX) const | |||||
| bool parseCoord (String::CharPointerType& s, float& value, bool allowUnits, bool isX) const | |||||
| { | { | ||||
| String number; | String number; | ||||
| @@ -1251,13 +1253,13 @@ private: | |||||
| return true; | return true; | ||||
| } | } | ||||
| bool parseCoords (String::CharPointerType& s, Point<float>& p, const bool allowUnits) const | |||||
| bool parseCoords (String::CharPointerType& s, Point<float>& p, bool allowUnits) const | |||||
| { | { | ||||
| return parseCoord (s, p.x, allowUnits, true) | return parseCoord (s, p.x, allowUnits, true) | ||||
| && parseCoord (s, p.y, allowUnits, false); | && parseCoord (s, p.y, allowUnits, false); | ||||
| } | } | ||||
| bool parseCoordsOrSkip (String::CharPointerType& s, Point<float>& p, const bool allowUnits) const | |||||
| bool parseCoordsOrSkip (String::CharPointerType& s, Point<float>& p, bool allowUnits) const | |||||
| { | { | ||||
| if (parseCoords (s, p, allowUnits)) | if (parseCoords (s, p, allowUnits)) | ||||
| return true; | return true; | ||||
| @@ -1268,8 +1270,8 @@ private: | |||||
| float getCoordLength (const String& s, const float sizeForProportions) const noexcept | float getCoordLength (const String& s, const float sizeForProportions) const noexcept | ||||
| { | { | ||||
| float n = s.getFloatValue(); | |||||
| const int len = s.length(); | |||||
| auto n = parseSafeFloat (s); | |||||
| auto len = s.length(); | |||||
| if (len > 2) | if (len > 2) | ||||
| { | { | ||||
| @@ -1293,7 +1295,7 @@ private: | |||||
| return getCoordLength (xml->getStringAttribute (attName), sizeForProportions); | return getCoordLength (xml->getStringAttribute (attName), sizeForProportions); | ||||
| } | } | ||||
| void getCoordList (Array<float>& coords, const String& list, bool allowUnits, const bool isX) const | |||||
| void getCoordList (Array<float>& coords, const String& list, bool allowUnits, bool isX) const | |||||
| { | { | ||||
| auto text = list.getCharPointer(); | auto text = list.getCharPointer(); | ||||
| float value; | float value; | ||||
| @@ -1302,6 +1304,12 @@ private: | |||||
| coords.add (value); | coords.add (value); | ||||
| } | } | ||||
| static float parseSafeFloat (const String& s) | |||||
| { | |||||
| auto n = s.getFloatValue(); | |||||
| return (std::isnan (n) || std::isinf (n)) ? 0.0f : n; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void parseCSSStyle (const XmlPath& xml) | void parseCSSStyle (const XmlPath& xml) | ||||
| { | { | ||||
| @@ -1452,7 +1460,7 @@ private: | |||||
| return CharacterFunctions::isDigit (c) || c == '-' || c == '+'; | return CharacterFunctions::isDigit (c) || c == '-' || c == '+'; | ||||
| } | } | ||||
| static bool parseNextNumber (String::CharPointerType& text, String& value, const bool allowUnits) | |||||
| static bool parseNextNumber (String::CharPointerType& text, String& value, bool allowUnits) | |||||
| { | { | ||||
| auto s = text; | auto s = text; | ||||
| @@ -1574,21 +1582,21 @@ private: | |||||
| auto alpha = [&tokens, &text] | auto alpha = [&tokens, &text] | ||||
| { | { | ||||
| if ((text.startsWith ("rgba") || text.startsWith ("hsla")) && tokens.size() == 4) | if ((text.startsWith ("rgba") || text.startsWith ("hsla")) && tokens.size() == 4) | ||||
| return tokens[3].getFloatValue(); | |||||
| return parseSafeFloat (tokens[3]); | |||||
| return 1.0f; | return 1.0f; | ||||
| }(); | }(); | ||||
| if (text.startsWith ("hsl")) | if (text.startsWith ("hsl")) | ||||
| return Colour::fromHSL ((float) (tokens[0].getDoubleValue() / 360.0), | |||||
| (float) (tokens[1].getDoubleValue() / 100.0), | |||||
| (float) (tokens[2].getDoubleValue() / 100.0), | |||||
| return Colour::fromHSL (parseSafeFloat (tokens[0]) / 360.0f, | |||||
| parseSafeFloat (tokens[1]) / 100.0f, | |||||
| parseSafeFloat (tokens[2]) / 100.0f, | |||||
| alpha); | alpha); | ||||
| if (tokens[0].containsChar ('%')) | if (tokens[0].containsChar ('%')) | ||||
| return Colour ((uint8) roundToInt (2.55 * tokens[0].getDoubleValue()), | |||||
| (uint8) roundToInt (2.55 * tokens[1].getDoubleValue()), | |||||
| (uint8) roundToInt (2.55 * tokens[2].getDoubleValue()), | |||||
| return Colour ((uint8) roundToInt (2.55f * parseSafeFloat (tokens[0])), | |||||
| (uint8) roundToInt (2.55f * parseSafeFloat (tokens[1])), | |||||
| (uint8) roundToInt (2.55f * parseSafeFloat (tokens[2])), | |||||
| alpha); | alpha); | ||||
| return Colour ((uint8) tokens[0].getIntValue(), | return Colour ((uint8) tokens[0].getIntValue(), | ||||
| @@ -1623,7 +1631,7 @@ private: | |||||
| float numbers[6]; | float numbers[6]; | ||||
| for (int i = 0; i < numElementsInArray (numbers); ++i) | for (int i = 0; i < numElementsInArray (numbers); ++i) | ||||
| numbers[i] = tokens[i].getFloatValue(); | |||||
| numbers[i] = parseSafeFloat (tokens[i]); | |||||
| AffineTransform trans; | AffineTransform trans; | ||||
| @@ -367,7 +367,7 @@ void FileBrowserComponent::lookAndFeelChanged() | |||||
| currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId)); | currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId)); | ||||
| filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId)); | filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId)); | ||||
| filenameBox.setColour (TextEditor::textColourId, findColour (filenameBoxTextColourId)); | |||||
| filenameBox.applyColourToAllText (findColour (filenameBoxTextColourId)); | |||||
| resized(); | resized(); | ||||
| repaint(); | repaint(); | ||||
| @@ -35,7 +35,7 @@ | |||||
| ID: juce_gui_basics | ID: juce_gui_basics | ||||
| vendor: juce | vendor: juce | ||||
| version: 6.0.0 | |||||
| version: 6.0.4 | |||||
| name: JUCE GUI core classes | name: JUCE GUI core classes | ||||
| description: Basic user-interface components and related classes. | description: Basic user-interface components and related classes. | ||||
| website: http://www.juce.com/juce | website: http://www.juce.com/juce | ||||
| @@ -43,7 +43,7 @@ | |||||
| dependencies: juce_graphics juce_data_structures | dependencies: juce_graphics juce_data_structures | ||||
| OSXFrameworks: Cocoa Carbon QuartzCore | OSXFrameworks: Cocoa Carbon QuartzCore | ||||
| iOSFrameworks: UIKit MobileCoreServices | |||||
| iOSFrameworks: UIKit CoreServices | |||||
| END_JUCE_MODULE_DECLARATION | END_JUCE_MODULE_DECLARATION | ||||
| @@ -382,11 +382,11 @@ struct MenuWindow : public Component | |||||
| { | { | ||||
| if (key.isKeyCode (KeyPress::downKey)) | if (key.isKeyCode (KeyPress::downKey)) | ||||
| { | { | ||||
| selectNextItem (1); | |||||
| selectNextItem (MenuSelectionDirection::forwards); | |||||
| } | } | ||||
| else if (key.isKeyCode (KeyPress::upKey)) | else if (key.isKeyCode (KeyPress::upKey)) | ||||
| { | { | ||||
| selectNextItem (-1); | |||||
| selectNextItem (MenuSelectionDirection::backwards); | |||||
| } | } | ||||
| else if (key.isKeyCode (KeyPress::leftKey)) | else if (key.isKeyCode (KeyPress::leftKey)) | ||||
| { | { | ||||
| @@ -414,14 +414,14 @@ struct MenuWindow : public Component | |||||
| if (showSubMenuFor (currentChild)) | if (showSubMenuFor (currentChild)) | ||||
| { | { | ||||
| if (isSubMenuVisible()) | if (isSubMenuVisible()) | ||||
| activeSubMenu->selectNextItem (0); | |||||
| activeSubMenu->selectNextItem (MenuSelectionDirection::current); | |||||
| } | } | ||||
| else if (componentAttachedTo != nullptr) | else if (componentAttachedTo != nullptr) | ||||
| { | { | ||||
| componentAttachedTo->keyPressed (key); | componentAttachedTo->keyPressed (key); | ||||
| } | } | ||||
| } | } | ||||
| else if (key.isKeyCode (KeyPress::returnKey)) | |||||
| else if (key.isKeyCode (KeyPress::returnKey) || key.isKeyCode (KeyPress::spaceKey)) | |||||
| { | { | ||||
| triggerCurrentlyHighlightedItem(); | triggerCurrentlyHighlightedItem(); | ||||
| } | } | ||||
| @@ -948,24 +948,46 @@ struct MenuWindow : public Component | |||||
| } | } | ||||
| } | } | ||||
| void selectNextItem (int delta) | |||||
| enum class MenuSelectionDirection | |||||
| { | |||||
| forwards, | |||||
| backwards, | |||||
| current | |||||
| }; | |||||
| void selectNextItem (MenuSelectionDirection direction) | |||||
| { | { | ||||
| disableTimerUntilMouseMoves(); | disableTimerUntilMouseMoves(); | ||||
| auto start = jmax (0, items.indexOf (currentChild)); | |||||
| auto start = [&] | |||||
| { | |||||
| auto index = items.indexOf (currentChild); | |||||
| if (index >= 0) | |||||
| return index; | |||||
| return direction == MenuSelectionDirection::backwards ? items.size() - 1 | |||||
| : 0; | |||||
| }(); | |||||
| auto preIncrement = (direction != MenuSelectionDirection::current && currentChild != nullptr); | |||||
| for (int i = items.size(); --i >= 0;) | for (int i = items.size(); --i >= 0;) | ||||
| { | { | ||||
| start += delta; | |||||
| if (preIncrement) | |||||
| start += (direction == MenuSelectionDirection::backwards ? -1 : 1); | |||||
| if (auto* mic = items.getUnchecked ((start + items.size()) % items.size())) | if (auto* mic = items.getUnchecked ((start + items.size()) % items.size())) | ||||
| { | { | ||||
| if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item)) | if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item)) | ||||
| { | { | ||||
| setCurrentlyHighlightedChild (mic); | setCurrentlyHighlightedChild (mic); | ||||
| break; | |||||
| return; | |||||
| } | } | ||||
| } | } | ||||
| if (! preIncrement) | |||||
| preIncrement = true; | |||||
| } | } | ||||
| } | } | ||||
| @@ -29,12 +29,12 @@ namespace juce | |||||
| /* | /* | ||||
| ============================================================================== | ============================================================================== | ||||
| In accordance with the terms of the JUCE 5 End-Use License Agreement, the | |||||
| In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||||
| JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | ||||
| ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | ||||
| under the GPL v3 license. | under the GPL v3 license. | ||||
| End User License Agreement: www.juce.com/juce-5-licence | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| @@ -26,12 +26,12 @@ | |||||
| /* | /* | ||||
| ============================================================================== | ============================================================================== | ||||
| In accordance with the terms of the JUCE 5 End-Use License Agreement, the | |||||
| In accordance with the terms of the JUCE 6 End-Use License Agreement, the | |||||
| JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | JUCE Code in SECTION A cannot be removed, changed or otherwise rendered | ||||
| ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | ||||
| under the GPL v3 license. | under the GPL v3 license. | ||||
| End User License Agreement: www.juce.com/juce-5-licence | |||||
| End User License Agreement: www.juce.com/juce-6-licence | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| @@ -124,6 +124,7 @@ private: | |||||
| setBounds (bounds); | setBounds (bounds); | ||||
| setAlwaysOnTop (true); | setAlwaysOnTop (true); | ||||
| setVisible (true); | |||||
| addToDesktop (0); | addToDesktop (0); | ||||
| enterModalState (true, | enterModalState (true, | ||||
| @@ -26,19 +26,22 @@ | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| class FileChooser::Native : private Component, | |||||
| public FileChooser::Pimpl | |||||
| class FileChooser::Native : public FileChooser::Pimpl, | |||||
| public Component, | |||||
| private AsyncUpdater | |||||
| { | { | ||||
| public: | public: | ||||
| Native (FileChooser& fileChooser, int flags) | Native (FileChooser& fileChooser, int flags) | ||||
| : owner (fileChooser) | : owner (fileChooser) | ||||
| { | { | ||||
| String firstFileExtension; | |||||
| static FileChooserDelegateClass cls; | |||||
| delegate.reset ([cls.createInstance() init]); | |||||
| static FileChooserDelegateClass delegateClass; | |||||
| delegate.reset ([delegateClass.createInstance() init]); | |||||
| FileChooserDelegateClass::setOwner (delegate.get(), this); | FileChooserDelegateClass::setOwner (delegate.get(), this); | ||||
| static FileChooserControllerClass controllerClass; | |||||
| auto* controllerClassInstance = controllerClass.createInstance(); | |||||
| String firstFileExtension; | |||||
| auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); | auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); | ||||
| if ((flags & FileBrowserComponent::saveMode) != 0) | if ((flags & FileBrowserComponent::saveMode) != 0) | ||||
| @@ -69,47 +72,51 @@ public: | |||||
| } | } | ||||
| auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())]; | auto url = [[NSURL alloc] initFileURLWithPath: juceStringToNS (currentFileOrDirectory.getFullPathName())]; | ||||
| controller.reset ([[UIDocumentPickerViewController alloc] initWithURL: url | |||||
| inMode: pickerMode]); | |||||
| controller.reset ([controllerClassInstance initWithURL: url | |||||
| inMode: pickerMode]); | |||||
| [url release]; | [url release]; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| controller.reset ([[UIDocumentPickerViewController alloc] initWithDocumentTypes: utTypeArray | |||||
| inMode: UIDocumentPickerModeOpen]); | |||||
| controller.reset ([controllerClassInstance initWithDocumentTypes: utTypeArray | |||||
| inMode: UIDocumentPickerModeOpen]); | |||||
| } | } | ||||
| FileChooserControllerClass::setOwner (controller.get(), this); | |||||
| [controller.get() setDelegate: delegate.get()]; | [controller.get() setDelegate: delegate.get()]; | ||||
| [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve]; | [controller.get() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve]; | ||||
| setOpaque (false); | setOpaque (false); | ||||
| if (SystemStats::isRunningInAppExtensionSandbox()) | |||||
| if (fileChooser.parent != nullptr) | |||||
| { | { | ||||
| if (fileChooser.parent != nullptr) | |||||
| { | |||||
| [controller.get() setModalPresentationStyle:UIModalPresentationFullScreen]; | |||||
| [controller.get() setModalPresentationStyle: UIModalPresentationFullScreen]; | |||||
| auto chooserBounds = fileChooser.parent->getBounds(); | |||||
| setBounds (chooserBounds); | |||||
| auto chooserBounds = fileChooser.parent->getBounds(); | |||||
| setBounds (chooserBounds); | |||||
| setAlwaysOnTop (true); | |||||
| fileChooser.parent->addAndMakeVisible (this); | |||||
| } | |||||
| else | |||||
| setAlwaysOnTop (true); | |||||
| fileChooser.parent->addAndMakeVisible (this); | |||||
| } | |||||
| else | |||||
| { | |||||
| if (SystemStats::isRunningInAppExtensionSandbox()) | |||||
| { | { | ||||
| // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a | // Opening a native top-level window in an AUv3 is not allowed (sandboxing). You need to specify a | ||||
| // parent component (for example your editor) to parent the native file chooser window. To do this | // parent component (for example your editor) to parent the native file chooser window. To do this | ||||
| // specify a parent component in the FileChooser's constructor! | // specify a parent component in the FileChooser's constructor! | ||||
| jassert (fileChooser.parent != nullptr); | |||||
| jassertfalse; | |||||
| return; | |||||
| } | } | ||||
| } | |||||
| else | |||||
| { | |||||
| auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea; | auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea; | ||||
| setBounds (chooserBounds); | setBounds (chooserBounds); | ||||
| setAlwaysOnTop (true); | setAlwaysOnTop (true); | ||||
| setVisible (true); | |||||
| addToDesktop (0); | addToDesktop (0); | ||||
| } | } | ||||
| } | } | ||||
| @@ -131,8 +138,6 @@ public: | |||||
| #endif | #endif | ||||
| } | } | ||||
| private: | |||||
| //============================================================================== | |||||
| void parentHierarchyChanged() override | void parentHierarchyChanged() override | ||||
| { | { | ||||
| auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer()); | auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer()); | ||||
| @@ -141,11 +146,23 @@ private: | |||||
| { | { | ||||
| peer = newPeer; | peer = newPeer; | ||||
| if (auto* parentController = peer->controller) | |||||
| [parentController showViewController: controller.get() sender: parentController]; | |||||
| if (peer != nullptr) | |||||
| { | |||||
| if (auto* parentController = peer->controller) | |||||
| [parentController showViewController: controller.get() sender: parentController]; | |||||
| peer->toFront (false); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| private: | |||||
| //============================================================================== | |||||
| void handleAsyncUpdate() override | |||||
| { | |||||
| pickerWasCancelled(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension) | static StringArray getUTTypesForWildcards (const String& filterWildcards, String& firstExtension) | ||||
| { | { | ||||
| @@ -182,7 +199,9 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| else | else | ||||
| { | |||||
| result.add ("public.data"); | result.add ("public.data"); | ||||
| } | |||||
| return result; | return result; | ||||
| } | } | ||||
| @@ -207,6 +226,8 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| void didPickDocumentAtURL (NSURL* url) | void didPickDocumentAtURL (NSURL* url) | ||||
| { | { | ||||
| cancelPendingUpdate(); | |||||
| bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService | bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService | ||||
| | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService; | | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService; | ||||
| @@ -267,9 +288,9 @@ private: | |||||
| void pickerWasCancelled() | void pickerWasCancelled() | ||||
| { | { | ||||
| Array<URL> chooserResults; | |||||
| cancelPendingUpdate(); | |||||
| owner.finished (chooserResults); | |||||
| owner.finished ({}); | |||||
| exitModalState (0); | exitModalState (0); | ||||
| } | } | ||||
| @@ -294,21 +315,40 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url) | static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url) | ||||
| { | { | ||||
| auto picker = getOwner (self); | |||||
| if (picker != nullptr) | |||||
| if (auto* picker = getOwner (self)) | |||||
| picker->didPickDocumentAtURL (url); | picker->didPickDocumentAtURL (url); | ||||
| } | } | ||||
| static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*) | static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*) | ||||
| { | { | ||||
| auto picker = getOwner (self); | |||||
| if (picker != nullptr) | |||||
| if (auto* picker = getOwner (self)) | |||||
| picker->pickerWasCancelled(); | picker->pickerWasCancelled(); | ||||
| } | } | ||||
| }; | }; | ||||
| struct FileChooserControllerClass : public ObjCClass<UIDocumentPickerViewController> | |||||
| { | |||||
| FileChooserControllerClass() : ObjCClass<UIDocumentPickerViewController> ("FileChooserController_") | |||||
| { | |||||
| addIvar<Native*> ("owner"); | |||||
| addMethod (@selector (viewDidDisappear:), viewDidDisappear, "v@:@c"); | |||||
| registerClass(); | |||||
| } | |||||
| static void setOwner (id self, Native* owner) { object_setInstanceVariable (self, "owner", owner); } | |||||
| static Native* getOwner (id self) { return getIvar<Native*> (self, "owner"); } | |||||
| //============================================================================== | |||||
| static void viewDidDisappear (id self, SEL, BOOL animated) | |||||
| { | |||||
| sendSuperclassMessage<void> (self, @selector (viewDidDisappear:), animated); | |||||
| if (auto* picker = getOwner (self)) | |||||
| picker->triggerAsyncUpdate(); | |||||
| } | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| FileChooser& owner; | FileChooser& owner; | ||||
| std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate; | std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate; | ||||
| @@ -316,6 +356,7 @@ private: | |||||
| UIViewComponentPeer* peer = nullptr; | UIViewComponentPeer* peer = nullptr; | ||||
| static FileChooserDelegateClass fileChooserDelegateClass; | static FileChooserDelegateClass fileChooserDelegateClass; | ||||
| static FileChooserControllerClass fileChooserControllerClass; | |||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | ||||
| @@ -28,14 +28,6 @@ namespace juce | |||||
| class UIViewComponentPeer; | class UIViewComponentPeer; | ||||
| // The way rotation works changed in iOS8.. | |||||
| static bool isUsingOldRotationMethod() noexcept | |||||
| { | |||||
| static bool isPreV8 = ([[[UIDevice currentDevice] systemVersion] compare: @"8.0" | |||||
| options: NSNumericSearch] == NSOrderedAscending); | |||||
| return isPreV8; | |||||
| } | |||||
| static UIInterfaceOrientation getWindowOrientation() | static UIInterfaceOrientation getWindowOrientation() | ||||
| { | { | ||||
| UIApplication* sharedApplication = [UIApplication sharedApplication]; | UIApplication* sharedApplication = [UIApplication sharedApplication]; | ||||
| @@ -79,28 +71,11 @@ namespace Orientations | |||||
| return UIInterfaceOrientationPortrait; | return UIInterfaceOrientationPortrait; | ||||
| } | } | ||||
| static CGAffineTransform getCGTransformFor (const Desktop::DisplayOrientation orientation) noexcept | |||||
| { | |||||
| if (isUsingOldRotationMethod()) | |||||
| { | |||||
| switch (orientation) | |||||
| { | |||||
| case Desktop::upsideDown: return CGAffineTransformMake (-1, 0, 0, -1, 0, 0); | |||||
| case Desktop::rotatedClockwise: return CGAffineTransformMake (0, -1, 1, 0, 0, 0); | |||||
| case Desktop::rotatedAntiClockwise: return CGAffineTransformMake (0, 1, -1, 0, 0, 0); | |||||
| case Desktop::upright: | |||||
| case Desktop::allOrientations: | |||||
| default: break; | |||||
| } | |||||
| } | |||||
| return CGAffineTransformIdentity; | |||||
| } | |||||
| static NSUInteger getSupportedOrientations() | static NSUInteger getSupportedOrientations() | ||||
| { | { | ||||
| NSUInteger allowed = 0; | NSUInteger allowed = 0; | ||||
| Desktop& d = Desktop::getInstance(); | |||||
| auto& d = Desktop::getInstance(); | |||||
| if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait; | if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait; | ||||
| if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown; | if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown; | ||||
| @@ -200,12 +175,10 @@ namespace juce | |||||
| struct UIViewPeerControllerReceiver | struct UIViewPeerControllerReceiver | ||||
| { | { | ||||
| virtual ~UIViewPeerControllerReceiver(); | |||||
| virtual ~UIViewPeerControllerReceiver() = default; | |||||
| virtual void setViewController (UIViewController*) = 0; | virtual void setViewController (UIViewController*) = 0; | ||||
| }; | }; | ||||
| UIViewPeerControllerReceiver::~UIViewPeerControllerReceiver() {} | |||||
| class UIViewComponentPeer : public ComponentPeer, | class UIViewComponentPeer : public ComponentPeer, | ||||
| public FocusChangeListener, | public FocusChangeListener, | ||||
| public UIViewPeerControllerReceiver | public UIViewPeerControllerReceiver | ||||
| @@ -259,7 +232,7 @@ public: | |||||
| void updateHiddenTextContent (TextInputTarget*); | void updateHiddenTextContent (TextInputTarget*); | ||||
| void globalFocusChanged (Component*) override; | void globalFocusChanged (Component*) override; | ||||
| void updateTransformAndScreenBounds(); | |||||
| void updateScreenBounds(); | |||||
| void handleTouches (UIEvent*, bool isDown, bool isUp, bool isCancel); | void handleTouches (UIEvent*, bool isDown, bool isUp, bool isCancel); | ||||
| @@ -268,10 +241,11 @@ public: | |||||
| void performAnyPendingRepaintsNow() override; | void performAnyPendingRepaintsNow() override; | ||||
| //============================================================================== | //============================================================================== | ||||
| UIWindow* window; | |||||
| JuceUIView* view; | |||||
| UIViewController* controller; | |||||
| bool isSharedWindow, fullScreen, insideDrawRect, isAppex; | |||||
| UIWindow* window = nil; | |||||
| JuceUIView* view = nil; | |||||
| UIViewController* controller = nil; | |||||
| const bool isSharedWindow, isAppex; | |||||
| bool fullScreen = false, insideDrawRect = false; | |||||
| static int64 getMouseTime (UIEvent* e) noexcept | static int64 getMouseTime (UIEvent* e) noexcept | ||||
| { | { | ||||
| @@ -279,73 +253,10 @@ public: | |||||
| + (int64) ([e timestamp] * 1000.0); | + (int64) ([e timestamp] * 1000.0); | ||||
| } | } | ||||
| static Rectangle<int> rotatedScreenPosToReal (const Rectangle<int>& r) | |||||
| { | |||||
| if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod()) | |||||
| { | |||||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||||
| switch (getWindowOrientation()) | |||||
| { | |||||
| case UIInterfaceOrientationPortrait: | |||||
| return r; | |||||
| case UIInterfaceOrientationPortraitUpsideDown: | |||||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||||
| r.getWidth(), r.getHeight()); | |||||
| case UIInterfaceOrientationLandscapeLeft: | |||||
| return Rectangle<int> (r.getY(), screen.getHeight() - r.getRight(), | |||||
| r.getHeight(), r.getWidth()); | |||||
| case UIInterfaceOrientationLandscapeRight: | |||||
| return Rectangle<int> (screen.getWidth() - r.getBottom(), r.getX(), | |||||
| r.getHeight(), r.getWidth()); | |||||
| case UIInterfaceOrientationUnknown: | |||||
| default: jassertfalse; // unknown orientation! | |||||
| } | |||||
| } | |||||
| return r; | |||||
| } | |||||
| static Rectangle<int> realScreenPosToRotated (const Rectangle<int>& r) | |||||
| { | |||||
| if (! SystemStats::isRunningInAppExtensionSandbox() && isUsingOldRotationMethod()) | |||||
| { | |||||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||||
| switch (getWindowOrientation()) | |||||
| { | |||||
| case UIInterfaceOrientationPortrait: | |||||
| return r; | |||||
| case UIInterfaceOrientationPortraitUpsideDown: | |||||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||||
| r.getWidth(), r.getHeight()); | |||||
| case UIInterfaceOrientationLandscapeLeft: | |||||
| return Rectangle<int> (screen.getHeight() - r.getBottom(), r.getX(), | |||||
| r.getHeight(), r.getWidth()); | |||||
| case UIInterfaceOrientationLandscapeRight: | |||||
| return Rectangle<int> (r.getY(), screen.getWidth() - r.getRight(), | |||||
| r.getHeight(), r.getWidth()); | |||||
| case UIInterfaceOrientationUnknown: | |||||
| default: jassertfalse; // unknown orientation! | |||||
| } | |||||
| } | |||||
| return r; | |||||
| } | |||||
| static MultiTouchMapper<UITouch*> currentTouches; | static MultiTouchMapper<UITouch*> currentTouches; | ||||
| private: | private: | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | |||||
| //============================================================================== | |||||
| class AsyncRepaintMessage : public CallbackMessage | class AsyncRepaintMessage : public CallbackMessage | ||||
| { | { | ||||
| public: | public: | ||||
| @@ -363,6 +274,9 @@ private: | |||||
| peer->repaint (rect); | peer->repaint (rect); | ||||
| } | } | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | |||||
| }; | }; | ||||
| static void sendScreenBoundsUpdate (JuceUIViewController* c) | static void sendScreenBoundsUpdate (JuceUIViewController* c) | ||||
| @@ -370,7 +284,7 @@ static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||||
| JuceUIView* juceView = (JuceUIView*) [c view]; | JuceUIView* juceView = (JuceUIView*) [c view]; | ||||
| if (juceView != nil && juceView->owner != nullptr) | if (juceView != nil && juceView->owner != nullptr) | ||||
| juceView->owner->updateTransformAndScreenBounds(); | |||||
| juceView->owner->updateScreenBounds(); | |||||
| } | } | ||||
| void AsyncBoundsUpdater::handleAsyncUpdate() | void AsyncBoundsUpdater::handleAsyncUpdate() | ||||
| @@ -614,14 +528,9 @@ bool KeyPress::isKeyCurrentlyDown (int) | |||||
| Point<float> juce_lastMousePos; | Point<float> juce_lastMousePos; | ||||
| //============================================================================== | //============================================================================== | ||||
| UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyleFlags, UIView* viewToAttachTo) | |||||
| UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo) | |||||
| : ComponentPeer (comp, windowStyleFlags), | : ComponentPeer (comp, windowStyleFlags), | ||||
| window (nil), | |||||
| view (nil), | |||||
| controller (nil), | |||||
| isSharedWindow (viewToAttachTo != nil), | isSharedWindow (viewToAttachTo != nil), | ||||
| fullScreen (false), | |||||
| insideDrawRect (false), | |||||
| isAppex (SystemStats::isRunningInAppExtensionSandbox()) | isAppex (SystemStats::isRunningInAppExtensionSandbox()) | ||||
| { | { | ||||
| CGRect r = convertToCGRect (component.getBounds()); | CGRect r = convertToCGRect (component.getBounds()); | ||||
| @@ -632,7 +541,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||||
| view.hidden = true; | view.hidden = true; | ||||
| view.opaque = component.isOpaque(); | view.opaque = component.isOpaque(); | ||||
| view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | ||||
| view.transform = CGAffineTransformIdentity; | |||||
| if (isSharedWindow) | if (isSharedWindow) | ||||
| { | { | ||||
| @@ -641,7 +549,7 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| r = convertToCGRect (rotatedScreenPosToReal (component.getBounds())); | |||||
| r = convertToCGRect (component.getBounds()); | |||||
| r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height); | r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height); | ||||
| window = [[JuceUIWindow alloc] initWithFrame: r]; | window = [[JuceUIWindow alloc] initWithFrame: r]; | ||||
| @@ -652,7 +560,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||||
| window.rootViewController = controller; | window.rootViewController = controller; | ||||
| window.hidden = true; | window.hidden = true; | ||||
| window.transform = Orientations::getCGTransformFor (Desktop::getInstance().getCurrentOrientation()); | |||||
| window.opaque = component.isOpaque(); | window.opaque = component.isOpaque(); | ||||
| window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | ||||
| @@ -660,8 +567,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||||
| window.windowLevel = UIWindowLevelAlert; | window.windowLevel = UIWindowLevelAlert; | ||||
| view.frame = CGRectMake (0, 0, r.size.width, r.size.height); | view.frame = CGRectMake (0, 0, r.size.width, r.size.height); | ||||
| [window addSubview: view]; | |||||
| } | } | ||||
| setTitle (component.getName()); | setTitle (component.getName()); | ||||
| @@ -718,7 +623,7 @@ void UIViewComponentPeer::setBounds (const Rectangle<int>& newBounds, const bool | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| window.frame = convertToCGRect (rotatedScreenPosToReal (newBounds)); | |||||
| window.frame = convertToCGRect (newBounds); | |||||
| view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight()); | view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight()); | ||||
| handleMovedOrResized(); | handleMovedOrResized(); | ||||
| @@ -733,8 +638,6 @@ Rectangle<int> UIViewComponentPeer::getBounds (const bool global) const | |||||
| { | { | ||||
| r = [view convertRect: r toView: view.window]; | r = [view convertRect: r toView: view.window]; | ||||
| r = [view.window convertRect: r toWindow: nil]; | r = [view.window convertRect: r toWindow: nil]; | ||||
| return realScreenPosToRotated (convertToRectInt (r)); | |||||
| } | } | ||||
| return convertToRectInt (r); | return convertToRectInt (r); | ||||
| @@ -759,8 +662,8 @@ void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen) | |||||
| { | { | ||||
| if (! isSharedWindow) | if (! isSharedWindow) | ||||
| { | { | ||||
| Rectangle<int> r (shouldBeFullScreen ? Desktop::getInstance().getDisplays().getMainDisplay().userArea | |||||
| : lastNonFullscreenBounds); | |||||
| auto r = shouldBeFullScreen ? Desktop::getInstance().getDisplays().getMainDisplay().userArea | |||||
| : lastNonFullscreenBounds; | |||||
| if ((! shouldBeFullScreen) && r.isEmpty()) | if ((! shouldBeFullScreen) && r.isEmpty()) | ||||
| r = getBounds(); | r = getBounds(); | ||||
| @@ -773,16 +676,14 @@ void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen) | |||||
| } | } | ||||
| } | } | ||||
| void UIViewComponentPeer::updateTransformAndScreenBounds() | |||||
| void UIViewComponentPeer::updateScreenBounds() | |||||
| { | { | ||||
| Desktop& desktop = Desktop::getInstance(); | |||||
| const Rectangle<int> oldArea (component.getBounds()); | |||||
| const Rectangle<int> oldDesktop (desktop.getDisplays().getMainDisplay().userArea); | |||||
| auto& desktop = Desktop::getInstance(); | |||||
| const_cast<Displays&> (desktop.getDisplays()).refresh(); | |||||
| auto oldArea = component.getBounds(); | |||||
| auto oldDesktop = desktop.getDisplays().getMainDisplay().userArea; | |||||
| window.transform = Orientations::getCGTransformFor (desktop.getCurrentOrientation()); | |||||
| view.transform = CGAffineTransformIdentity; | |||||
| const_cast<Displays&> (desktop.getDisplays()).refresh(); | |||||
| if (fullScreen) | if (fullScreen) | ||||
| { | { | ||||
| @@ -792,13 +693,13 @@ void UIViewComponentPeer::updateTransformAndScreenBounds() | |||||
| else if (! isSharedWindow) | else if (! isSharedWindow) | ||||
| { | { | ||||
| // this will re-centre the window, but leave its size unchanged | // this will re-centre the window, but leave its size unchanged | ||||
| const float centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth(); | |||||
| const float centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight(); | |||||
| auto centreRelX = oldArea.getCentreX() / (float) oldDesktop.getWidth(); | |||||
| auto centreRelY = oldArea.getCentreY() / (float) oldDesktop.getHeight(); | |||||
| const Rectangle<int> newDesktop (desktop.getDisplays().getMainDisplay().userArea); | |||||
| auto newDesktop = desktop.getDisplays().getMainDisplay().userArea; | |||||
| const int x = ((int) (newDesktop.getWidth() * centreRelX)) - (oldArea.getWidth() / 2); | |||||
| const int y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2); | |||||
| auto x = ((int) (newDesktop.getWidth() * centreRelX)) - (oldArea.getWidth() / 2); | |||||
| auto y = ((int) (newDesktop.getHeight() * centreRelY)) - (oldArea.getHeight() / 2); | |||||
| component.setBounds (oldArea.withPosition (x, y)); | component.setBounds (oldArea.withPosition (x, y)); | ||||
| } | } | ||||
| @@ -808,13 +709,8 @@ void UIViewComponentPeer::updateTransformAndScreenBounds() | |||||
| bool UIViewComponentPeer::contains (Point<int> localPos, bool trueIfInAChildWindow) const | bool UIViewComponentPeer::contains (Point<int> localPos, bool trueIfInAChildWindow) const | ||||
| { | { | ||||
| { | |||||
| Rectangle<int> localBounds = | |||||
| ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds()); | |||||
| if (! localBounds.contains (localPos)) | |||||
| return false; | |||||
| } | |||||
| if (! ScalingHelpers::scaledScreenPosToUnscaled (component, component.getLocalBounds()).contains (localPos)) | |||||
| return false; | |||||
| UIView* v = [view hitTest: convertToCGPoint (localPos) | UIView* v = [view hitTest: convertToCGPoint (localPos) | ||||
| withEvent: nil]; | withEvent: nil]; | ||||
| @@ -844,16 +740,10 @@ void UIViewComponentPeer::toFront (bool makeActiveWindow) | |||||
| void UIViewComponentPeer::toBehind (ComponentPeer* other) | void UIViewComponentPeer::toBehind (ComponentPeer* other) | ||||
| { | { | ||||
| if (UIViewComponentPeer* const otherPeer = dynamic_cast<UIViewComponentPeer*> (other)) | |||||
| if (auto* otherPeer = dynamic_cast<UIViewComponentPeer*> (other)) | |||||
| { | { | ||||
| if (isSharedWindow) | if (isSharedWindow) | ||||
| { | |||||
| [[view superview] insertSubview: view belowSubview: otherPeer->view]; | [[view superview] insertSubview: view belowSubview: otherPeer->view]; | ||||
| } | |||||
| else | |||||
| { | |||||
| // don't know how to do this | |||||
| } | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -869,23 +759,17 @@ void UIViewComponentPeer::setIcon (const Image& /*newIcon*/) | |||||
| //============================================================================== | //============================================================================== | ||||
| static float getMaximumTouchForce (UITouch* touch) noexcept | static float getMaximumTouchForce (UITouch* touch) noexcept | ||||
| { | { | ||||
| #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||||
| if ([touch respondsToSelector: @selector (maximumPossibleForce)]) | if ([touch respondsToSelector: @selector (maximumPossibleForce)]) | ||||
| return (float) touch.maximumPossibleForce; | return (float) touch.maximumPossibleForce; | ||||
| #endif | |||||
| ignoreUnused (touch); | |||||
| return 0.0f; | return 0.0f; | ||||
| } | } | ||||
| static float getTouchForce (UITouch* touch) noexcept | static float getTouchForce (UITouch* touch) noexcept | ||||
| { | { | ||||
| #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||||
| if ([touch respondsToSelector: @selector (force)]) | if ([touch respondsToSelector: @selector (force)]) | ||||
| return (float) touch.force; | return (float) touch.force; | ||||
| #endif | |||||
| ignoreUnused (touch); | |||||
| return 0.0f; | return 0.0f; | ||||
| } | } | ||||
| @@ -896,19 +780,19 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||||
| for (unsigned int i = 0; i < [touches count]; ++i) | for (unsigned int i = 0; i < [touches count]; ++i) | ||||
| { | { | ||||
| UITouch* touch = [touches objectAtIndex: i]; | UITouch* touch = [touches objectAtIndex: i]; | ||||
| const float maximumForce = getMaximumTouchForce (touch); | |||||
| auto maximumForce = getMaximumTouchForce (touch); | |||||
| if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0) | if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0) | ||||
| continue; | continue; | ||||
| CGPoint p = [touch locationInView: view]; | CGPoint p = [touch locationInView: view]; | ||||
| const Point<float> pos (static_cast<float> (p.x), static_cast<float> (p.y)); | |||||
| Point<float> pos ((float) p.x, (float) p.y); | |||||
| juce_lastMousePos = pos + getBounds (true).getPosition().toFloat(); | juce_lastMousePos = pos + getBounds (true).getPosition().toFloat(); | ||||
| const int64 time = getMouseTime (event); | |||||
| const int touchIndex = currentTouches.getIndexOfTouch (this, touch); | |||||
| auto time = getMouseTime (event); | |||||
| auto touchIndex = currentTouches.getIndexOfTouch (this, touch); | |||||
| ModifierKeys modsToSend (ModifierKeys::currentModifiers); | |||||
| auto modsToSend = ModifierKeys::currentModifiers; | |||||
| if (isDown) | if (isDown) | ||||
| { | { | ||||
| @@ -944,8 +828,8 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons | |||||
| } | } | ||||
| // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range: | // NB: some devices return 0 or 1.0 if pressure is unknown, so we'll clip our value to a believable range: | ||||
| float pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce) | |||||
| : MouseInputSource::invalidPressure; | |||||
| auto pressure = maximumForce > 0 ? jlimit (0.0001f, 0.9999f, getTouchForce (touch) / maximumForce) | |||||
| : MouseInputSource::invalidPressure; | |||||
| handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend, pressure, | handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, modsToSend, pressure, | ||||
| MouseInputSource::invalidOrientation, time, { }, touchIndex); | MouseInputSource::invalidOrientation, time, { }, touchIndex); | ||||
| @@ -1011,18 +895,13 @@ void UIViewComponentPeer::textInputRequired (Point<int>, TextInputTarget&) | |||||
| { | { | ||||
| } | } | ||||
| static bool isIOS4_1() noexcept | |||||
| { | |||||
| return [[[UIDevice currentDevice] systemVersion] doubleValue] >= 4.1; | |||||
| } | |||||
| static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept | static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept | ||||
| { | { | ||||
| switch (type) | switch (type) | ||||
| { | { | ||||
| case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet; | case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet; | ||||
| case TextInputTarget::numericKeyboard: return isIOS4_1() ? UIKeyboardTypeNumberPad : UIKeyboardTypeNumbersAndPunctuation; | |||||
| case TextInputTarget::decimalKeyboard: return isIOS4_1() ? UIKeyboardTypeDecimalPad : UIKeyboardTypeNumbersAndPunctuation; | |||||
| case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation; | |||||
| case TextInputTarget::decimalKeyboard: return UIKeyboardTypeNumbersAndPunctuation; | |||||
| case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL; | case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL; | ||||
| case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; | case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; | ||||
| case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; | case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; | ||||
| @@ -1041,9 +920,9 @@ void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target) | |||||
| BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const String& text) | BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const String& text) | ||||
| { | { | ||||
| if (TextInputTarget* const target = findCurrentTextInputTarget()) | |||||
| if (auto* target = findCurrentTextInputTarget()) | |||||
| { | { | ||||
| const Range<int> currentSelection (target->getHighlightedRegion()); | |||||
| auto currentSelection = target->getHighlightedRegion(); | |||||
| if (range.getLength() == 1 && text.isEmpty()) // (detect backspace) | if (range.getLength() == 1 && text.isEmpty()) // (detect backspace) | ||||
| if (currentSelection.isEmpty()) | if (currentSelection.isEmpty()) | ||||
| @@ -1062,15 +941,16 @@ BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const Str | |||||
| void UIViewComponentPeer::globalFocusChanged (Component*) | void UIViewComponentPeer::globalFocusChanged (Component*) | ||||
| { | { | ||||
| if (TextInputTarget* const target = findCurrentTextInputTarget()) | |||||
| if (auto* target = findCurrentTextInputTarget()) | |||||
| { | { | ||||
| Component* comp = dynamic_cast<Component*> (target); | |||||
| Point<int> pos (component.getLocalPoint (comp, Point<int>())); | |||||
| view->hiddenTextView.frame = CGRectMake (pos.x, pos.y, 0, 0); | |||||
| if (auto* comp = dynamic_cast<Component*> (target)) | |||||
| { | |||||
| auto pos = component.getLocalPoint (comp, Point<int>()); | |||||
| view->hiddenTextView.frame = CGRectMake (pos.x, pos.y, 0, 0); | |||||
| updateHiddenTextContent (target); | |||||
| [view->hiddenTextView becomeFirstResponder]; | |||||
| updateHiddenTextContent (target); | |||||
| [view->hiddenTextView becomeFirstResponder]; | |||||
| } | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| @@ -1078,7 +958,6 @@ void UIViewComponentPeer::globalFocusChanged (Component*) | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void UIViewComponentPeer::drawRect (CGRect r) | void UIViewComponentPeer::drawRect (CGRect r) | ||||
| { | { | ||||
| @@ -1091,9 +970,6 @@ void UIViewComponentPeer::drawRect (CGRect r) | |||||
| CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); | CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); | ||||
| CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight())); | CGContextConcatCTM (cg, CGAffineTransformMake (1, 0, 0, -1, 0, getComponent().getHeight())); | ||||
| // NB the CTM on iOS already includes a factor for the display scale, so | |||||
| // we'll tell the context that the scale is 1.0 to avoid it using it twice | |||||
| CoreGraphicsContext g (cg, getComponent().getHeight()); | CoreGraphicsContext g (cg, getComponent().getHeight()); | ||||
| insideDrawRect = true; | insideDrawRect = true; | ||||
| @@ -1111,9 +987,9 @@ void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, | |||||
| { | { | ||||
| displays->refresh(); | displays->refresh(); | ||||
| if (ComponentPeer* peer = kioskModeComp->getPeer()) | |||||
| if (auto* peer = kioskModeComp->getPeer()) | |||||
| { | { | ||||
| if (UIViewComponentPeer* uiViewPeer = dynamic_cast<UIViewComponentPeer*> (peer)) | |||||
| if (auto* uiViewPeer = dynamic_cast<UIViewComponentPeer*> (peer)) | |||||
| [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate]; | [uiViewPeer->controller setNeedsStatusBarAppearanceUpdate]; | ||||
| peer->setFullScreen (enableOrDisable); | peer->setFullScreen (enableOrDisable); | ||||
| @@ -1125,21 +1001,18 @@ void Desktop::allowedOrientationsChanged() | |||||
| // if the current orientation isn't allowed anymore then switch orientations | // if the current orientation isn't allowed anymore then switch orientations | ||||
| if (! isOrientationEnabled (getCurrentOrientation())) | if (! isOrientationEnabled (getCurrentOrientation())) | ||||
| { | { | ||||
| DisplayOrientation orientations[] = { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise }; | |||||
| const int n = sizeof (orientations) / sizeof (DisplayOrientation); | |||||
| int i; | |||||
| for (i = 0; i < n; ++i) | |||||
| if (isOrientationEnabled (orientations[i])) | |||||
| break; | |||||
| auto newOrientation = [this] | |||||
| { | |||||
| for (auto orientation : { upright, upsideDown, rotatedClockwise, rotatedAntiClockwise }) | |||||
| if (isOrientationEnabled (orientation)) | |||||
| return orientation; | |||||
| // you need to support at least one orientation | |||||
| jassert (i < n); | |||||
| i = jmin (n - 1, i); | |||||
| // you need to support at least one orientation | |||||
| jassertfalse; | |||||
| return upright; | |||||
| }(); | |||||
| NSNumber *value = [NSNumber numberWithInt: (int) Orientations::convertFromJuce (orientations[i])]; | |||||
| NSNumber* value = [NSNumber numberWithInt: (int) Orientations::convertFromJuce (newOrientation)]; | |||||
| [[UIDevice currentDevice] setValue:value forKey:@"orientation"]; | [[UIDevice currentDevice] setValue:value forKey:@"orientation"]; | ||||
| [value release]; | [value release]; | ||||
| } | } | ||||
| @@ -1164,77 +1037,77 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void* windowToAttachTo) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| const int KeyPress::spaceKey = ' '; | |||||
| const int KeyPress::returnKey = 0x0d; | |||||
| const int KeyPress::escapeKey = 0x1b; | |||||
| const int KeyPress::backspaceKey = 0x7f; | |||||
| const int KeyPress::leftKey = 0x1000; | |||||
| const int KeyPress::rightKey = 0x1001; | |||||
| const int KeyPress::upKey = 0x1002; | |||||
| const int KeyPress::downKey = 0x1003; | |||||
| const int KeyPress::pageUpKey = 0x1004; | |||||
| const int KeyPress::pageDownKey = 0x1005; | |||||
| const int KeyPress::endKey = 0x1006; | |||||
| const int KeyPress::homeKey = 0x1007; | |||||
| const int KeyPress::deleteKey = 0x1008; | |||||
| const int KeyPress::insertKey = -1; | |||||
| const int KeyPress::tabKey = 9; | |||||
| const int KeyPress::F1Key = 0x2001; | |||||
| const int KeyPress::F2Key = 0x2002; | |||||
| const int KeyPress::F3Key = 0x2003; | |||||
| const int KeyPress::F4Key = 0x2004; | |||||
| const int KeyPress::F5Key = 0x2005; | |||||
| const int KeyPress::F6Key = 0x2006; | |||||
| const int KeyPress::F7Key = 0x2007; | |||||
| const int KeyPress::F8Key = 0x2008; | |||||
| const int KeyPress::F9Key = 0x2009; | |||||
| const int KeyPress::F10Key = 0x200a; | |||||
| const int KeyPress::F11Key = 0x200b; | |||||
| const int KeyPress::F12Key = 0x200c; | |||||
| const int KeyPress::F13Key = 0x200d; | |||||
| const int KeyPress::F14Key = 0x200e; | |||||
| const int KeyPress::F15Key = 0x200f; | |||||
| const int KeyPress::F16Key = 0x2010; | |||||
| const int KeyPress::F17Key = 0x2011; | |||||
| const int KeyPress::F18Key = 0x2012; | |||||
| const int KeyPress::F19Key = 0x2013; | |||||
| const int KeyPress::F20Key = 0x2014; | |||||
| const int KeyPress::F21Key = 0x2015; | |||||
| const int KeyPress::F22Key = 0x2016; | |||||
| const int KeyPress::F23Key = 0x2017; | |||||
| const int KeyPress::F24Key = 0x2018; | |||||
| const int KeyPress::F25Key = 0x2019; | |||||
| const int KeyPress::F26Key = 0x201a; | |||||
| const int KeyPress::F27Key = 0x201b; | |||||
| const int KeyPress::F28Key = 0x201c; | |||||
| const int KeyPress::F29Key = 0x201d; | |||||
| const int KeyPress::F30Key = 0x201e; | |||||
| const int KeyPress::F31Key = 0x201f; | |||||
| const int KeyPress::F32Key = 0x2020; | |||||
| const int KeyPress::F33Key = 0x2021; | |||||
| const int KeyPress::F34Key = 0x2022; | |||||
| const int KeyPress::F35Key = 0x2023; | |||||
| const int KeyPress::numberPad0 = 0x30020; | |||||
| const int KeyPress::numberPad1 = 0x30021; | |||||
| const int KeyPress::numberPad2 = 0x30022; | |||||
| const int KeyPress::numberPad3 = 0x30023; | |||||
| const int KeyPress::numberPad4 = 0x30024; | |||||
| const int KeyPress::numberPad5 = 0x30025; | |||||
| const int KeyPress::numberPad6 = 0x30026; | |||||
| const int KeyPress::numberPad7 = 0x30027; | |||||
| const int KeyPress::numberPad8 = 0x30028; | |||||
| const int KeyPress::numberPad9 = 0x30029; | |||||
| const int KeyPress::numberPadAdd = 0x3002a; | |||||
| const int KeyPress::numberPadSubtract = 0x3002b; | |||||
| const int KeyPress::numberPadMultiply = 0x3002c; | |||||
| const int KeyPress::numberPadDivide = 0x3002d; | |||||
| const int KeyPress::numberPadSeparator = 0x3002e; | |||||
| const int KeyPress::numberPadDecimalPoint = 0x3002f; | |||||
| const int KeyPress::numberPadEquals = 0x30030; | |||||
| const int KeyPress::numberPadDelete = 0x30031; | |||||
| const int KeyPress::playKey = 0x30000; | |||||
| const int KeyPress::stopKey = 0x30001; | |||||
| const int KeyPress::fastForwardKey = 0x30002; | |||||
| const int KeyPress::rewindKey = 0x30003; | |||||
| const int KeyPress::spaceKey = ' '; | |||||
| const int KeyPress::returnKey = 0x0d; | |||||
| const int KeyPress::escapeKey = 0x1b; | |||||
| const int KeyPress::backspaceKey = 0x7f; | |||||
| const int KeyPress::leftKey = 0x1000; | |||||
| const int KeyPress::rightKey = 0x1001; | |||||
| const int KeyPress::upKey = 0x1002; | |||||
| const int KeyPress::downKey = 0x1003; | |||||
| const int KeyPress::pageUpKey = 0x1004; | |||||
| const int KeyPress::pageDownKey = 0x1005; | |||||
| const int KeyPress::endKey = 0x1006; | |||||
| const int KeyPress::homeKey = 0x1007; | |||||
| const int KeyPress::deleteKey = 0x1008; | |||||
| const int KeyPress::insertKey = -1; | |||||
| const int KeyPress::tabKey = 9; | |||||
| const int KeyPress::F1Key = 0x2001; | |||||
| const int KeyPress::F2Key = 0x2002; | |||||
| const int KeyPress::F3Key = 0x2003; | |||||
| const int KeyPress::F4Key = 0x2004; | |||||
| const int KeyPress::F5Key = 0x2005; | |||||
| const int KeyPress::F6Key = 0x2006; | |||||
| const int KeyPress::F7Key = 0x2007; | |||||
| const int KeyPress::F8Key = 0x2008; | |||||
| const int KeyPress::F9Key = 0x2009; | |||||
| const int KeyPress::F10Key = 0x200a; | |||||
| const int KeyPress::F11Key = 0x200b; | |||||
| const int KeyPress::F12Key = 0x200c; | |||||
| const int KeyPress::F13Key = 0x200d; | |||||
| const int KeyPress::F14Key = 0x200e; | |||||
| const int KeyPress::F15Key = 0x200f; | |||||
| const int KeyPress::F16Key = 0x2010; | |||||
| const int KeyPress::F17Key = 0x2011; | |||||
| const int KeyPress::F18Key = 0x2012; | |||||
| const int KeyPress::F19Key = 0x2013; | |||||
| const int KeyPress::F20Key = 0x2014; | |||||
| const int KeyPress::F21Key = 0x2015; | |||||
| const int KeyPress::F22Key = 0x2016; | |||||
| const int KeyPress::F23Key = 0x2017; | |||||
| const int KeyPress::F24Key = 0x2018; | |||||
| const int KeyPress::F25Key = 0x2019; | |||||
| const int KeyPress::F26Key = 0x201a; | |||||
| const int KeyPress::F27Key = 0x201b; | |||||
| const int KeyPress::F28Key = 0x201c; | |||||
| const int KeyPress::F29Key = 0x201d; | |||||
| const int KeyPress::F30Key = 0x201e; | |||||
| const int KeyPress::F31Key = 0x201f; | |||||
| const int KeyPress::F32Key = 0x2020; | |||||
| const int KeyPress::F33Key = 0x2021; | |||||
| const int KeyPress::F34Key = 0x2022; | |||||
| const int KeyPress::F35Key = 0x2023; | |||||
| const int KeyPress::numberPad0 = 0x30020; | |||||
| const int KeyPress::numberPad1 = 0x30021; | |||||
| const int KeyPress::numberPad2 = 0x30022; | |||||
| const int KeyPress::numberPad3 = 0x30023; | |||||
| const int KeyPress::numberPad4 = 0x30024; | |||||
| const int KeyPress::numberPad5 = 0x30025; | |||||
| const int KeyPress::numberPad6 = 0x30026; | |||||
| const int KeyPress::numberPad7 = 0x30027; | |||||
| const int KeyPress::numberPad8 = 0x30028; | |||||
| const int KeyPress::numberPad9 = 0x30029; | |||||
| const int KeyPress::numberPadAdd = 0x3002a; | |||||
| const int KeyPress::numberPadSubtract = 0x3002b; | |||||
| const int KeyPress::numberPadMultiply = 0x3002c; | |||||
| const int KeyPress::numberPadDivide = 0x3002d; | |||||
| const int KeyPress::numberPadSeparator = 0x3002e; | |||||
| const int KeyPress::numberPadDecimalPoint = 0x3002f; | |||||
| const int KeyPress::numberPadEquals = 0x30030; | |||||
| const int KeyPress::numberPadDelete = 0x30031; | |||||
| const int KeyPress::playKey = 0x30000; | |||||
| const int KeyPress::stopKey = 0x30001; | |||||
| const int KeyPress::fastForwardKey = 0x30002; | |||||
| const int KeyPress::rewindKey = 0x30003; | |||||
| } // namespace juce | } // namespace juce | ||||
| @@ -29,7 +29,7 @@ namespace juce | |||||
| struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules | struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules | ||||
| { | { | ||||
| virtual ~AppInactivityCallback() {} | |||||
| virtual ~AppInactivityCallback() = default; | |||||
| virtual void appBecomingInactive() = 0; | virtual void appBecomingInactive() = 0; | ||||
| }; | }; | ||||
| @@ -434,30 +434,6 @@ void LookAndFeel::playAlertSound() | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| class iOSMessageBox; | |||||
| #if defined (__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 | |||||
| #define JUCE_USE_NEW_IOS_ALERTWINDOW 1 | |||||
| #endif | |||||
| #if ! JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| } // (juce namespace) | |||||
| @interface JuceAlertBoxDelegate : NSObject <UIAlertViewDelegate> | |||||
| { | |||||
| @public | |||||
| iOSMessageBox* owner; | |||||
| } | |||||
| - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex; | |||||
| @end | |||||
| namespace juce | |||||
| { | |||||
| #endif | |||||
| class iOSMessageBox | class iOSMessageBox | ||||
| { | { | ||||
| public: | public: | ||||
| @@ -466,7 +442,6 @@ public: | |||||
| ModalComponentManager::Callback* cb, const bool async) | ModalComponentManager::Callback* cb, const bool async) | ||||
| : result (0), resultReceived (false), callback (cb), isAsync (async) | : result (0), resultReceived (false), callback (cb), isAsync (async) | ||||
| { | { | ||||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| if (currentlyFocusedPeer != nullptr) | if (currentlyFocusedPeer != nullptr) | ||||
| { | { | ||||
| UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title) | UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title) | ||||
| @@ -486,27 +461,6 @@ public: | |||||
| // have at least one window on screen when you use this | // have at least one window on screen when you use this | ||||
| jassertfalse; | jassertfalse; | ||||
| } | } | ||||
| #else | |||||
| delegate = [[JuceAlertBoxDelegate alloc] init]; | |||||
| delegate->owner = this; | |||||
| alert = [[UIAlertView alloc] initWithTitle: juceStringToNS (title) | |||||
| message: juceStringToNS (message) | |||||
| delegate: delegate | |||||
| cancelButtonTitle: button1 | |||||
| otherButtonTitles: button2, button3, nil]; | |||||
| [alert retain]; | |||||
| [alert show]; | |||||
| #endif | |||||
| } | |||||
| ~iOSMessageBox() | |||||
| { | |||||
| #if ! JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| [alert release]; | |||||
| [delegate release]; | |||||
| #endif | |||||
| } | } | ||||
| int getResult() | int getResult() | ||||
| @@ -515,11 +469,7 @@ public: | |||||
| JUCE_AUTORELEASEPOOL | JUCE_AUTORELEASEPOOL | ||||
| { | { | ||||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| while (! resultReceived) | while (! resultReceived) | ||||
| #else | |||||
| while (! (alert.hidden || resultReceived)) | |||||
| #endif | |||||
| [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | ||||
| } | } | ||||
| @@ -544,7 +494,6 @@ private: | |||||
| std::unique_ptr<ModalComponentManager::Callback> callback; | std::unique_ptr<ModalComponentManager::Callback> callback; | ||||
| const bool isAsync; | const bool isAsync; | ||||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| void addButton (UIAlertController* alert, NSString* text, int index) | void addButton (UIAlertController* alert, NSString* text, int index) | ||||
| { | { | ||||
| if (text != nil) | if (text != nil) | ||||
| @@ -552,33 +501,10 @@ private: | |||||
| style: UIAlertActionStyleDefault | style: UIAlertActionStyleDefault | ||||
| handler: ^(UIAlertAction*) { this->buttonClicked (index); }]]; | handler: ^(UIAlertAction*) { this->buttonClicked (index); }]]; | ||||
| } | } | ||||
| #else | |||||
| UIAlertView* alert; | |||||
| JuceAlertBoxDelegate* delegate; | |||||
| #endif | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) | ||||
| }; | }; | ||||
| #if ! JUCE_USE_NEW_IOS_ALERTWINDOW | |||||
| } // (juce namespace) | |||||
| @implementation JuceAlertBoxDelegate | |||||
| - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex | |||||
| { | |||||
| owner->buttonClicked ((int) buttonIndex); | |||||
| alertView.hidden = true; | |||||
| } | |||||
| @end | |||||
| namespace juce | |||||
| { | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MODAL_LOOPS_PERMITTED | #if JUCE_MODAL_LOOPS_PERMITTED | ||||
| void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType /*iconType*/, | void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType /*iconType*/, | ||||
| @@ -742,13 +668,9 @@ void Displays::findDisplays (float masterScale) | |||||
| UIScreen* s = [UIScreen mainScreen]; | UIScreen* s = [UIScreen mainScreen]; | ||||
| Display d; | Display d; | ||||
| d.userArea = d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; | |||||
| d.userArea = d.totalArea = convertToRectInt ([s bounds]) / masterScale; | |||||
| d.isMain = true; | d.isMain = true; | ||||
| d.scale = masterScale; | |||||
| if ([s respondsToSelector: @selector (scale)]) | |||||
| d.scale *= s.scale; | |||||
| d.scale = masterScale * s.scale; | |||||
| d.dpi = 160 * d.scale; | d.dpi = 160 * d.scale; | ||||
| displays.add (d); | displays.add (d); | ||||
| @@ -27,16 +27,18 @@ namespace juce | |||||
| { | { | ||||
| #if JUCE_MODAL_LOOPS_PERMITTED | #if JUCE_MODAL_LOOPS_PERMITTED | ||||
| static bool exeIsAvailable (const char* const executable) | |||||
| static bool exeIsAvailable (String executable) | |||||
| { | { | ||||
| ChildProcess child; | ChildProcess child; | ||||
| const bool ok = child.start ("which " + String (executable)) | |||||
| && child.readAllProcessOutput().trim().isNotEmpty(); | |||||
| child.waitForProcessToFinish (60 * 1000); | |||||
| return ok; | |||||
| } | |||||
| if (child.start ("which " + executable)) | |||||
| { | |||||
| child.waitForProcessToFinish (60 * 1000); | |||||
| return (child.getExitCode() == 0); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| class FileChooser::Native : public FileChooser::Pimpl, | class FileChooser::Native : public FileChooser::Pimpl, | ||||
| private Timer | private Timer | ||||
| @@ -68,7 +70,7 @@ public: | |||||
| child.start (args, ChildProcess::wantStdOut); | child.start (args, ChildProcess::wantStdOut); | ||||
| while (child.isRunning()) | while (child.isRunning()) | ||||
| if (! MessageManager::getInstance()->runDispatchLoopUntil(20)) | |||||
| if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) | |||||
| break; | break; | ||||
| finish (false); | finish (false); | ||||
| @@ -187,7 +189,7 @@ private: | |||||
| } | } | ||||
| args.add (startPath.getFullPathName()); | args.add (startPath.getFullPathName()); | ||||
| args.add (owner.filters.replaceCharacter (';', ' ')); | |||||
| args.add ("(" + owner.filters.replaceCharacter (';', ' ') + ")"); | |||||
| } | } | ||||
| void addZenityArgs() | void addZenityArgs() | ||||
| @@ -218,8 +220,7 @@ private: | |||||
| StringArray tokens; | StringArray tokens; | ||||
| tokens.addTokens (owner.filters, ";,|", "\""); | tokens.addTokens (owner.filters, ";,|", "\""); | ||||
| for (int i = 0; i < tokens.size(); ++i) | |||||
| args.add ("--file-filter=" + tokens[i]); | |||||
| args.add ("--file-filter=" + tokens.joinIntoString (" ")); | |||||
| } | } | ||||
| if (owner.startingFile.isDirectory()) | if (owner.startingFile.isDirectory()) | ||||
| @@ -330,11 +330,17 @@ private: | |||||
| class LinuxRepaintManager : public Timer | class LinuxRepaintManager : public Timer | ||||
| { | { | ||||
| public: | public: | ||||
| LinuxRepaintManager (LinuxComponentPeer& p) : peer (p) {} | |||||
| LinuxRepaintManager (LinuxComponentPeer& p) | |||||
| : peer (p), | |||||
| isSemiTransparentWindow ((peer.getStyleFlags() & ComponentPeer::windowIsSemiTransparent) != 0) | |||||
| { | |||||
| } | |||||
| void timerCallback() override | void timerCallback() override | ||||
| { | { | ||||
| if (XWindowSystem::getInstance()->getNumPaintsPending (peer.windowH) > 0) | |||||
| XWindowSystem::getInstance()->processPendingPaintsForWindow (peer.windowH); | |||||
| if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0) | |||||
| return; | return; | ||||
| if (! regionsNeedingRepaint.isEmpty()) | if (! regionsNeedingRepaint.isEmpty()) | ||||
| @@ -359,7 +365,7 @@ private: | |||||
| void performAnyPendingRepaintsNow() | void performAnyPendingRepaintsNow() | ||||
| { | { | ||||
| if (XWindowSystem::getInstance()->getNumPaintsPending (peer.windowH) > 0) | |||||
| if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0) | |||||
| { | { | ||||
| startTimer (repaintTimerPeriod); | startTimer (repaintTimerPeriod); | ||||
| return; | return; | ||||
| @@ -374,7 +380,8 @@ private: | |||||
| if (image.isNull() || image.getWidth() < totalArea.getWidth() | if (image.isNull() || image.getWidth() < totalArea.getWidth() | ||||
| || image.getHeight() < totalArea.getHeight()) | || image.getHeight() < totalArea.getHeight()) | ||||
| { | { | ||||
| image = XWindowSystem::getInstance()->createImage (totalArea.getWidth(), totalArea.getHeight(), | |||||
| image = XWindowSystem::getInstance()->createImage (isSemiTransparentWindow, | |||||
| totalArea.getWidth(), totalArea.getHeight(), | |||||
| useARGBImagesForRendering); | useARGBImagesForRendering); | ||||
| } | } | ||||
| @@ -407,6 +414,7 @@ private: | |||||
| enum { repaintTimerPeriod = 1000 / 100 }; | enum { repaintTimerPeriod = 1000 / 100 }; | ||||
| LinuxComponentPeer& peer; | LinuxComponentPeer& peer; | ||||
| const bool isSemiTransparentWindow; | |||||
| Image image; | Image image; | ||||
| uint32 lastTimeImageUsed = 0; | uint32 lastTimeImageUsed = 0; | ||||
| RectangleList<int> regionsNeedingRepaint; | RectangleList<int> regionsNeedingRepaint; | ||||
| @@ -283,7 +283,7 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| struct DelegateClass : ObjCClass<DelegateType> | struct DelegateClass : ObjCClass<DelegateType> | ||||
| { | { | ||||
| DelegateClass() : ObjCClass <DelegateType> ("JUCEFileChooser_") | |||||
| DelegateClass() : ObjCClass<DelegateType> ("JUCEFileChooser_") | |||||
| { | { | ||||
| addIvar<Native*> ("cppObject"); | addIvar<Native*> ("cppObject"); | ||||
| @@ -534,30 +534,7 @@ private: | |||||
| auto owner = getIvar<JuceMainMenuHandler*> (self, "owner"); | auto owner = getIvar<JuceMainMenuHandler*> (self, "owner"); | ||||
| if (auto* juceItem = getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject])) | if (auto* juceItem = getJuceClassFromNSObject<PopupMenu::Item> ([item representedObject])) | ||||
| { | |||||
| // If the menu is being triggered by a keypress, the OS will have picked it up before we had a chance to offer it to | |||||
| // our own components, which may have wanted to intercept it. So, rather than dispatching directly, we'll feed it back | |||||
| // into the focused component and let it trigger the menu item indirectly. | |||||
| NSEvent* e = [NSApp currentEvent]; | |||||
| if ([e type] == NSEventTypeKeyDown || [e type] == NSEventTypeKeyUp) | |||||
| { | |||||
| if (auto* focused = juce::Component::getCurrentlyFocusedComponent()) | |||||
| { | |||||
| if (auto peer = dynamic_cast<juce::NSViewComponentPeer*> (focused->getPeer())) | |||||
| { | |||||
| if ([e type] == NSEventTypeKeyDown) | |||||
| peer->redirectKeyDown (e); | |||||
| else | |||||
| peer->redirectKeyUp (e); | |||||
| return; | |||||
| } | |||||
| } | |||||
| } | |||||
| owner->invoke (*juceItem, static_cast<int> ([item tag])); | owner->invoke (*juceItem, static_cast<int> ([item tag])); | ||||
| } | |||||
| } | } | ||||
| static void menuNeedsUpdate (id self, SEL, NSMenu* menu) | static void menuNeedsUpdate (id self, SEL, NSMenu* menu) | ||||
| @@ -23,6 +23,11 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| @interface NSEvent (DeviceDelta) | |||||
| - (float)deviceDeltaX; | |||||
| - (float)deviceDeltaY; | |||||
| @end | |||||
| //============================================================================== | //============================================================================== | ||||
| #if defined (MAC_OS_X_VERSION_10_8) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) \ | #if defined (MAC_OS_X_VERSION_10_8) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_8) \ | ||||
| && USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_DRAW_ASYNC | && USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_DRAW_ASYNC | ||||
| @@ -680,8 +685,8 @@ public: | |||||
| } | } | ||||
| else if ([ev respondsToSelector: @selector (deviceDeltaX)]) | else if ([ev respondsToSelector: @selector (deviceDeltaX)]) | ||||
| { | { | ||||
| wheel.deltaX = checkDeviceDeltaReturnValue ((float) getMsgSendFPRetFn() (ev, @selector (deviceDeltaX))); | |||||
| wheel.deltaY = checkDeviceDeltaReturnValue ((float) getMsgSendFPRetFn() (ev, @selector (deviceDeltaY))); | |||||
| wheel.deltaX = checkDeviceDeltaReturnValue ([ev deviceDeltaX]); | |||||
| wheel.deltaY = checkDeviceDeltaReturnValue ([ev deviceDeltaY]); | |||||
| } | } | ||||
| } | } | ||||
| @catch (...) | @catch (...) | ||||
| @@ -1770,10 +1775,7 @@ private: | |||||
| owner->stringBeingComposed.clear(); | owner->stringBeingComposed.clear(); | ||||
| if (! (owner->textWasInserted || owner->redirectKeyDown (ev))) | if (! (owner->textWasInserted || owner->redirectKeyDown (ev))) | ||||
| { | |||||
| objc_super s = { self, [NSView class] }; | |||||
| getMsgSendSuperFn() (&s, @selector (keyDown:), ev); | |||||
| } | |||||
| sendSuperclassMessage<void> (self, @selector (keyDown:), ev); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1781,11 +1783,8 @@ private: | |||||
| { | { | ||||
| auto* owner = getOwner (self); | auto* owner = getOwner (self); | ||||
| if (owner == nullptr || ! owner->redirectKeyUp (ev)) | |||||
| { | |||||
| objc_super s = { self, [NSView class] }; | |||||
| getMsgSendSuperFn() (&s, @selector (keyUp:), ev); | |||||
| } | |||||
| if (! owner->redirectKeyUp (ev)) | |||||
| sendSuperclassMessage<void> (self, @selector (keyUp:), ev); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -2035,7 +2034,7 @@ private: | |||||
| static void becomeKeyWindow (id self, SEL) | static void becomeKeyWindow (id self, SEL) | ||||
| { | { | ||||
| sendSuperclassMessage (self, @selector (becomeKeyWindow)); | |||||
| sendSuperclassMessage<void> (self, @selector (becomeKeyWindow)); | |||||
| if (auto* owner = getOwner (self)) | if (auto* owner = getOwner (self)) | ||||
| { | { | ||||
| @@ -2057,10 +2056,15 @@ private: | |||||
| return owner == nullptr || owner->windowShouldClose(); | return owner == nullptr || owner->windowShouldClose(); | ||||
| } | } | ||||
| static NSRect constrainFrameRect (id self, SEL, NSRect frameRect, NSScreen*) | |||||
| static NSRect constrainFrameRect (id self, SEL, NSRect frameRect, NSScreen* screen) | |||||
| { | { | ||||
| if (auto* owner = getOwner (self)) | if (auto* owner = getOwner (self)) | ||||
| { | |||||
| frameRect = sendSuperclassMessage<NSRect, NSRect, NSScreen*> (self, @selector (constrainFrameRect:toScreen:), | |||||
| frameRect, screen); | |||||
| frameRect = owner->constrainRect (frameRect); | frameRect = owner->constrainRect (frameRect); | ||||
| } | |||||
| return frameRect; | return frameRect; | ||||
| } | } | ||||
| @@ -2104,10 +2108,10 @@ private: | |||||
| { | { | ||||
| if (auto* owner = getOwner (self)) | if (auto* owner = getOwner (self)) | ||||
| { | { | ||||
| owner->isZooming = true; | |||||
| objc_super s = { self, [NSWindow class] }; | |||||
| getMsgSendSuperFn() (&s, @selector (zoom:), sender); | |||||
| owner->isZooming = false; | |||||
| { | |||||
| const ScopedValueSetter<bool> svs (owner->isZooming, true); | |||||
| sendSuperclassMessage<void> (self, @selector (zoom:), sender); | |||||
| } | |||||
| owner->redirectMovedOrResized(); | owner->redirectMovedOrResized(); | ||||
| } | } | ||||
| @@ -217,7 +217,7 @@ private: | |||||
| delete getIvar<std::function<void()>*> (self, "callback"); | delete getIvar<std::function<void()>*> (self, "callback"); | ||||
| delete getIvar<NSDragOperation*> (self, "operation"); | delete getIvar<NSDragOperation*> (self, "operation"); | ||||
| sendSuperclassMessage (self, @selector (dealloc)); | |||||
| sendSuperclassMessage<void> (self, @selector (dealloc)); | |||||
| } | } | ||||
| static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type) | static void provideDataForType (id self, SEL, NSPasteboard* sender, NSPasteboardItem*, NSString* type) | ||||
| @@ -58,7 +58,7 @@ public: | |||||
| // Handle nonexistent root directories in the same way as existing ones | // Handle nonexistent root directories in the same way as existing ones | ||||
| files.calloc (static_cast<size_t> (charsAvailableForResult) + 1); | files.calloc (static_cast<size_t> (charsAvailableForResult) + 1); | ||||
| if (startingFile.isDirectory() ||startingFile.isRoot()) | |||||
| if (startingFile.isDirectory() || startingFile.isRoot()) | |||||
| { | { | ||||
| initialPath = startingFile.getFullPathName(); | initialPath = startingFile.getFullPathName(); | ||||
| } | } | ||||
| @@ -154,7 +154,7 @@ private: | |||||
| Component::SafePointer<Component> owner; | Component::SafePointer<Component> owner; | ||||
| String title, filtersString; | String title, filtersString; | ||||
| std::unique_ptr<CustomComponentHolder> customComponent; | std::unique_ptr<CustomComponentHolder> customComponent; | ||||
| String initialPath, returnedString, defaultExtension; | |||||
| String initialPath, returnedString; | |||||
| WaitableEvent threadHasReference; | WaitableEvent threadHasReference; | ||||
| CriticalSection deletingDialog; | CriticalSection deletingDialog; | ||||
| @@ -167,8 +167,144 @@ private: | |||||
| Atomic<HWND> nativeDialogRef; | Atomic<HWND> nativeDialogRef; | ||||
| Atomic<int> shouldCancel; | Atomic<int> shouldCancel; | ||||
| bool showDialog (IFileDialog& dialog, bool async) const | |||||
| { | |||||
| FILEOPENDIALOGOPTIONS flags = {}; | |||||
| if (FAILED (dialog.GetOptions (&flags))) | |||||
| return false; | |||||
| const auto setBit = [] (FILEOPENDIALOGOPTIONS& field, bool value, FILEOPENDIALOGOPTIONS option) | |||||
| { | |||||
| if (value) | |||||
| field |= option; | |||||
| else | |||||
| field &= ~option; | |||||
| }; | |||||
| setBit (flags, selectsDirectories, FOS_PICKFOLDERS); | |||||
| setBit (flags, warnAboutOverwrite, FOS_OVERWRITEPROMPT); | |||||
| setBit (flags, selectMultiple, FOS_ALLOWMULTISELECT); | |||||
| setBit (flags, customComponent != nullptr, FOS_FORCEPREVIEWPANEON); | |||||
| if (FAILED (dialog.SetOptions (flags)) || FAILED (dialog.SetTitle (title.toUTF16()))) | |||||
| return false; | |||||
| PIDLIST_ABSOLUTE pidl = {}; | |||||
| if (FAILED (SHParseDisplayName (initialPath.toWideCharPointer(), nullptr, &pidl, SFGAO_FOLDER, nullptr))) | |||||
| return false; | |||||
| const auto item = [&] | |||||
| { | |||||
| ComSmartPtr<IShellItem> ptr; | |||||
| SHCreateShellItem (nullptr, nullptr, pidl, ptr.resetAndGetPointerAddress()); | |||||
| return ptr; | |||||
| }(); | |||||
| if (item == nullptr || FAILED (dialog.SetFolder (item))) | |||||
| return false; | |||||
| String filename (files.getData()); | |||||
| if (FAILED (dialog.SetFileName (filename.toWideCharPointer()))) | |||||
| return false; | |||||
| auto extension = getDefaultFileExtension (filename); | |||||
| if (extension.isNotEmpty() && FAILED (dialog.SetDefaultExtension (extension.toWideCharPointer()))) | |||||
| return false; | |||||
| const COMDLG_FILTERSPEC spec[] { { filtersString.toWideCharPointer(), filtersString.toWideCharPointer() } }; | |||||
| if (! selectsDirectories && FAILED (dialog.SetFileTypes (numElementsInArray (spec), spec))) | |||||
| return false; | |||||
| return dialog.Show (static_cast<HWND> (async ? nullptr : owner->getWindowHandle())) == S_OK; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| Array<URL> openDialog (bool async) | |||||
| Array<URL> openDialogVistaAndUp (bool async) | |||||
| { | |||||
| const auto getUrl = [] (IShellItem& item) | |||||
| { | |||||
| struct Free | |||||
| { | |||||
| void operator() (LPWSTR ptr) const noexcept { CoTaskMemFree (ptr); } | |||||
| }; | |||||
| LPWSTR ptr = nullptr; | |||||
| item.GetDisplayName (SIGDN_URL, &ptr); | |||||
| return std::unique_ptr<WCHAR, Free> { ptr }; | |||||
| }; | |||||
| if (isSave) | |||||
| { | |||||
| const auto dialog = [&] | |||||
| { | |||||
| ComSmartPtr<IFileDialog> ptr; | |||||
| ptr.CoCreateInstance (CLSID_FileSaveDialog, CLSCTX_INPROC_SERVER); | |||||
| return ptr; | |||||
| }(); | |||||
| if (dialog == nullptr) | |||||
| return {}; | |||||
| showDialog (*dialog, async); | |||||
| const auto item = [&] | |||||
| { | |||||
| ComSmartPtr<IShellItem> ptr; | |||||
| dialog->GetResult (ptr.resetAndGetPointerAddress()); | |||||
| return ptr; | |||||
| }(); | |||||
| if (item == nullptr) | |||||
| return {}; | |||||
| return { URL (String (getUrl (*item).get())) }; | |||||
| } | |||||
| const auto dialog = [&] | |||||
| { | |||||
| ComSmartPtr<IFileOpenDialog> ptr; | |||||
| ptr.CoCreateInstance (CLSID_FileOpenDialog, CLSCTX_INPROC_SERVER); | |||||
| return ptr; | |||||
| }(); | |||||
| if (dialog == nullptr) | |||||
| return {}; | |||||
| showDialog (*dialog, async); | |||||
| const auto items = [&] | |||||
| { | |||||
| ComSmartPtr<IShellItemArray> ptr; | |||||
| dialog->GetResults (ptr.resetAndGetPointerAddress()); | |||||
| return ptr; | |||||
| }(); | |||||
| if (items == nullptr) | |||||
| return {}; | |||||
| Array<URL> result; | |||||
| DWORD numItems = 0; | |||||
| items->GetCount (&numItems); | |||||
| for (DWORD i = 0; i < numItems; ++i) | |||||
| { | |||||
| ComSmartPtr<IShellItem> scope; | |||||
| items->GetItemAt (i, scope.resetAndGetPointerAddress()); | |||||
| if (scope != nullptr) | |||||
| result.add (String (getUrl (*scope).get())); | |||||
| } | |||||
| return result; | |||||
| } | |||||
| Array<URL> openDialogPreVista (bool async) | |||||
| { | { | ||||
| Array<URL> selections; | Array<URL> selections; | ||||
| @@ -213,7 +349,7 @@ private: | |||||
| { | { | ||||
| OPENFILENAMEW of = {}; | OPENFILENAMEW of = {}; | ||||
| #ifdef OPENFILENAME_SIZE_VERSION_400W | |||||
| #ifdef OPENFILENAME_SIZE_VERSION_400W | |||||
| of.lStructSize = OPENFILENAME_SIZE_VERSION_400W; | of.lStructSize = OPENFILENAME_SIZE_VERSION_400W; | ||||
| #else | #else | ||||
| of.lStructSize = sizeof (of); | of.lStructSize = sizeof (of); | ||||
| @@ -231,16 +367,10 @@ private: | |||||
| if (isSave) | if (isSave) | ||||
| { | { | ||||
| StringArray tokens; | |||||
| tokens.addTokens (filtersString, ";,", "\"'"); | |||||
| tokens.trim(); | |||||
| tokens.removeEmptyStrings(); | |||||
| auto extension = getDefaultFileExtension (files.getData()); | |||||
| if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty()) | |||||
| { | |||||
| defaultExtension = tokens[0].fromFirstOccurrenceOf (".", false, false); | |||||
| of.lpstrDefExt = defaultExtension.toWideCharPointer(); | |||||
| } | |||||
| if (extension.isNotEmpty()) | |||||
| of.lpstrDefExt = extension.toWideCharPointer(); | |||||
| if (! GetSaveFileName (&of)) | if (! GetSaveFileName (&of)) | ||||
| return {}; | return {}; | ||||
| @@ -251,7 +381,7 @@ private: | |||||
| return {}; | return {}; | ||||
| } | } | ||||
| if (selectMultiple && of.nFileOffset > 0 && files [of.nFileOffset - 1] == 0) | |||||
| if (selectMultiple && of.nFileOffset > 0 && files[of.nFileOffset - 1] == 0) | |||||
| { | { | ||||
| const WCHAR* filename = files + of.nFileOffset; | const WCHAR* filename = files + of.nFileOffset; | ||||
| @@ -267,18 +397,34 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| getNativeDialogList().removeValue (this); | |||||
| return selections; | return selections; | ||||
| } | } | ||||
| Array<URL> openDialog (bool async) | |||||
| { | |||||
| struct Remover | |||||
| { | |||||
| explicit Remover (Win32NativeFileChooser& chooser) : item (chooser) {} | |||||
| ~Remover() { getNativeDialogList().removeValue (&item); } | |||||
| Win32NativeFileChooser& item; | |||||
| }; | |||||
| const Remover remover (*this); | |||||
| if (SystemStats::getOperatingSystemType() >= SystemStats::WinVista) | |||||
| return openDialogVistaAndUp (async); | |||||
| return openDialogPreVista (async); | |||||
| } | |||||
| void run() override | void run() override | ||||
| { | { | ||||
| // as long as the thread is running, don't delete this class | // as long as the thread is running, don't delete this class | ||||
| Ptr safeThis (this); | Ptr safeThis (this); | ||||
| threadHasReference.signal(); | threadHasReference.signal(); | ||||
| Array<URL> r = openDialog (true); | |||||
| auto r = openDialog (true); | |||||
| MessageManager::callAsync ([safeThis, r] | MessageManager::callAsync ([safeThis, r] | ||||
| { | { | ||||
| safeThis->results = r; | safeThis->results = r; | ||||
| @@ -330,6 +476,23 @@ private: | |||||
| return ofFlags; | return ofFlags; | ||||
| } | } | ||||
| String getDefaultFileExtension (const String& filename) const | |||||
| { | |||||
| auto extension = filename.fromLastOccurrenceOf (".", false, false); | |||||
| if (extension.isEmpty()) | |||||
| { | |||||
| auto tokens = StringArray::fromTokens (filtersString, ";,", "\"'"); | |||||
| tokens.trim(); | |||||
| tokens.removeEmptyStrings(); | |||||
| if (tokens.size() == 1 && tokens[0].removeCharacters ("*.").isNotEmpty()) | |||||
| extension = tokens[0].fromFirstOccurrenceOf (".", false, false); | |||||
| } | |||||
| return extension; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void initialised (HWND hWnd) | void initialised (HWND hWnd) | ||||
| { | { | ||||
| @@ -414,7 +577,7 @@ private: | |||||
| if (customComponent != nullptr && shouldCancel.get() == 0) | if (customComponent != nullptr && shouldCancel.get() == 0) | ||||
| { | { | ||||
| if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent(0))) | |||||
| if (FilePreviewComponent* comp = dynamic_cast<FilePreviewComponent*> (customComponent->getChildComponent (0))) | |||||
| { | { | ||||
| WCHAR path [MAX_PATH * 2] = { 0 }; | WCHAR path [MAX_PATH * 2] = { 0 }; | ||||
| CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH); | CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH); | ||||
| @@ -514,7 +677,6 @@ class FileChooser::Native : public Component, | |||||
| public FileChooser::Pimpl | public FileChooser::Pimpl | ||||
| { | { | ||||
| public: | public: | ||||
| Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp) | Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp) | ||||
| : owner (fileChooser), | : owner (fileChooser), | ||||
| nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, | nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, | ||||
| @@ -531,7 +693,7 @@ public: | |||||
| addToDesktop (0); | addToDesktop (0); | ||||
| } | } | ||||
| ~Native() | |||||
| ~Native() override | |||||
| { | { | ||||
| exitModalState (0); | exitModalState (0); | ||||
| nativeFileChooser->cancel(); | nativeFileChooser->cancel(); | ||||
| @@ -3176,10 +3176,7 @@ private: | |||||
| updateKeyModifiers(); | updateKeyModifiers(); | ||||
| if (hwnd == GetActiveWindow()) | if (hwnd == GetActiveWindow()) | ||||
| { | |||||
| handleKeyPress (key, 0); | |||||
| return true; | |||||
| } | |||||
| return handleKeyPress (key, 0); | |||||
| } | } | ||||
| return false; | return false; | ||||
| @@ -198,7 +198,7 @@ public: | |||||
| srcMimeTypeAtomList.clear(); | srcMimeTypeAtomList.clear(); | ||||
| dragAndDropCurrentMimeType = 0; | dragAndDropCurrentMimeType = 0; | ||||
| auto dndCurrentVersion = static_cast<unsigned long> (clientMsg.data.l[1] & 0xff000000) >> 24; | |||||
| auto dndCurrentVersion = (static_cast<unsigned long> (clientMsg.data.l[1]) & 0xff000000) >> 24; | |||||
| if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion) | if (dndCurrentVersion < 3 || dndCurrentVersion > XWindowSystemUtilities::Atoms::DndVersion) | ||||
| { | { | ||||
| @@ -80,6 +80,7 @@ bool X11Symbols::loadAllSymbols() | |||||
| using namespace X11SymbolHelpers; | using namespace X11SymbolHelpers; | ||||
| if (! loadSymbols (xLib, xextLib, | if (! loadSymbols (xLib, xextLib, | ||||
| makeSymbolBinding (xAllocClassHint, "XAllocClassHint"), | |||||
| makeSymbolBinding (xAllocSizeHints, "XAllocSizeHints"), | makeSymbolBinding (xAllocSizeHints, "XAllocSizeHints"), | ||||
| makeSymbolBinding (xAllocWMHints, "XAllocWMHints"), | makeSymbolBinding (xAllocWMHints, "XAllocWMHints"), | ||||
| makeSymbolBinding (xBitmapBitOrder, "XBitmapBitOrder"), | makeSymbolBinding (xBitmapBitOrder, "XBitmapBitOrder"), | ||||
| @@ -169,6 +170,7 @@ bool X11Symbols::loadAllSymbols() | |||||
| makeSymbolBinding (xScreenNumberOfScreen, "XScreenNumberOfScreen"), | makeSymbolBinding (xScreenNumberOfScreen, "XScreenNumberOfScreen"), | ||||
| makeSymbolBinding (xSelectInput, "XSelectInput"), | makeSymbolBinding (xSelectInput, "XSelectInput"), | ||||
| makeSymbolBinding (xSendEvent, "XSendEvent"), | makeSymbolBinding (xSendEvent, "XSendEvent"), | ||||
| makeSymbolBinding (xSetClassHint, "XSetClassHint"), | |||||
| makeSymbolBinding (xSetErrorHandler, "XSetErrorHandler"), | makeSymbolBinding (xSetErrorHandler, "XSetErrorHandler"), | ||||
| makeSymbolBinding (xSetIOErrorHandler, "XSetIOErrorHandler"), | makeSymbolBinding (xSetIOErrorHandler, "XSetIOErrorHandler"), | ||||
| makeSymbolBinding (xSetInputFocus, "XSetInputFocus"), | makeSymbolBinding (xSetInputFocus, "XSetInputFocus"), | ||||
| @@ -49,6 +49,10 @@ public: | |||||
| bool loadAllSymbols(); | bool loadAllSymbols(); | ||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocClassHint, xAllocClassHint, | |||||
| (), | |||||
| XClassHint*) | |||||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocSizeHints, xAllocSizeHints, | JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocSizeHints, xAllocSizeHints, | ||||
| (), | (), | ||||
| XSizeHints*) | XSizeHints*) | ||||
| @@ -405,6 +409,10 @@ public: | |||||
| (::Display*, ::Window, Bool, long, XEvent*), | (::Display*, ::Window, Bool, long, XEvent*), | ||||
| Status) | Status) | ||||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetClassHint, xSetClassHint, | |||||
| (::Display*, ::Window, XClassHint*), | |||||
| void) | |||||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetErrorHandler, xSetErrorHandler, | JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetErrorHandler, xSetErrorHandler, | ||||
| (XErrorHandler), | (XErrorHandler), | ||||
| XErrorHandler) | XErrorHandler) | ||||
| @@ -643,16 +643,13 @@ namespace Visuals | |||||
| } | } | ||||
| //================================= X11 - Bitmap =============================== | //================================= X11 - Bitmap =============================== | ||||
| static std::unordered_map<::Window, int> shmPaintsPendingMap; | |||||
| class XBitmapImage : public ImagePixelData | class XBitmapImage : public ImagePixelData | ||||
| { | { | ||||
| public: | public: | ||||
| XBitmapImage (::Display* d, Image::PixelFormat format, int w, int h, | |||||
| XBitmapImage (Image::PixelFormat format, int w, int h, | |||||
| bool clearImage, unsigned int imageDepth_, Visual* visual) | bool clearImage, unsigned int imageDepth_, Visual* visual) | ||||
| : ImagePixelData (format, w, h), | : ImagePixelData (format, w, h), | ||||
| imageDepth (imageDepth_), | |||||
| display (d) | |||||
| imageDepth (imageDepth_) | |||||
| { | { | ||||
| jassert (format == Image::RGB || format == Image::ARGB); | jassert (format == Image::RGB || format == Image::ARGB); | ||||
| @@ -807,6 +804,11 @@ public: | |||||
| { | { | ||||
| XWindowSystemUtilities::ScopedXLock xLock; | XWindowSystemUtilities::ScopedXLock xLock; | ||||
| #if JUCE_USE_XSHM | |||||
| if (isUsingXShm()) | |||||
| XWindowSystem::getInstance()->addPendingPaintForWindow (window); | |||||
| #endif | |||||
| if (gc == None) | if (gc == None) | ||||
| { | { | ||||
| XGCValues gcvalues; | XGCValues gcvalues; | ||||
| @@ -856,10 +858,7 @@ public: | |||||
| // blit results to screen. | // blit results to screen. | ||||
| #if JUCE_USE_XSHM | #if JUCE_USE_XSHM | ||||
| if (isUsingXShm()) | if (isUsingXShm()) | ||||
| { | |||||
| X11Symbols::getInstance()->xShmPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh, True); | X11Symbols::getInstance()->xShmPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh, True); | ||||
| ++shmPaintsPendingMap[window]; | |||||
| } | |||||
| else | else | ||||
| #endif | #endif | ||||
| X11Symbols::getInstance()->xPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh); | X11Symbols::getInstance()->xPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh); | ||||
| @@ -878,7 +877,7 @@ private: | |||||
| int pixelStride, lineStride; | int pixelStride, lineStride; | ||||
| uint8* imageData = nullptr; | uint8* imageData = nullptr; | ||||
| GC gc = None; | GC gc = None; | ||||
| ::Display* display = nullptr; | |||||
| ::Display* display = XWindowSystem::getInstance()->getDisplay(); | |||||
| #if JUCE_USE_XSHM | #if JUCE_USE_XSHM | ||||
| XShmSegmentInfo segmentInfo; | XShmSegmentInfo segmentInfo; | ||||
| @@ -1155,7 +1154,7 @@ namespace ClipboardHelpers | |||||
| // translate to utf8 | // translate to utf8 | ||||
| numDataItems = localContent.getNumBytesAsUTF8() + 1; | numDataItems = localContent.getNumBytesAsUTF8() + 1; | ||||
| data.calloc (numDataItems + 1); | |||||
| data.calloc (numDataItems); | |||||
| localContent.copyToUTF8 (data, numDataItems); | localContent.copyToUTF8 (data, numDataItems); | ||||
| propertyFormat = 8; // bits/item | propertyFormat = 8; // bits/item | ||||
| } | } | ||||
| @@ -1165,7 +1164,7 @@ namespace ClipboardHelpers | |||||
| numDataItems = 2; | numDataItems = 2; | ||||
| propertyFormat = 32; // atoms are 32-bit | propertyFormat = 32; // atoms are 32-bit | ||||
| data.calloc (numDataItems * 4); | data.calloc (numDataItems * 4); | ||||
| Atom* atoms = reinterpret_cast<Atom*> (data.getData()); | |||||
| Atom* atoms = unalignedPointerCast<Atom*> (data.getData()); | |||||
| atoms[0] = XWindowSystem::getInstance()->getAtoms().utf8String; | atoms[0] = XWindowSystem::getInstance()->getAtoms().utf8String; | ||||
| atoms[1] = XA_STRING; | atoms[1] = XA_STRING; | ||||
| @@ -1224,7 +1223,7 @@ ComponentPeer* getPeerFor (::Window windowH) | |||||
| X11Symbols::getInstance()->xFindContext (display, (XID) windowH, windowHandleXContext, &peer); | X11Symbols::getInstance()->xFindContext (display, (XID) windowH, windowHandleXContext, &peer); | ||||
| } | } | ||||
| return reinterpret_cast<ComponentPeer*> (peer); | |||||
| return unalignedPointerCast<ComponentPeer*> (peer); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1305,6 +1304,13 @@ static int getAllEventsMask (bool ignoresMouseClicks) | |||||
| XWindowSystemUtilities::ScopedXLock xLock; | XWindowSystemUtilities::ScopedXLock xLock; | ||||
| auto root = X11Symbols::getInstance()->xRootWindow (display, X11Symbols::getInstance()->xDefaultScreen (display)); | |||||
| auto visualAndDepth = displayVisuals->getBestVisualForWindow ((styleFlags & ComponentPeer::windowIsSemiTransparent) != 0); | |||||
| auto colormap = X11Symbols::getInstance()->xCreateColormap (display, root, visualAndDepth.visual, AllocNone); | |||||
| X11Symbols::getInstance()->xInstallColormap (display, colormap); | |||||
| // Set up the window attributes | // Set up the window attributes | ||||
| XSetWindowAttributes swa; | XSetWindowAttributes swa; | ||||
| swa.border_pixel = 0; | swa.border_pixel = 0; | ||||
| @@ -1313,11 +1319,9 @@ static int getAllEventsMask (bool ignoresMouseClicks) | |||||
| swa.override_redirect = ((styleFlags & ComponentPeer::windowIsTemporary) != 0) ? True : False; | swa.override_redirect = ((styleFlags & ComponentPeer::windowIsTemporary) != 0) ? True : False; | ||||
| swa.event_mask = getAllEventsMask (styleFlags & ComponentPeer::windowIgnoresMouseClicks); | swa.event_mask = getAllEventsMask (styleFlags & ComponentPeer::windowIgnoresMouseClicks); | ||||
| auto root = X11Symbols::getInstance()->xRootWindow (display, X11Symbols::getInstance()->xDefaultScreen (display)); | |||||
| auto windowH = X11Symbols::getInstance()->xCreateWindow (display, parentToAddTo != 0 ? parentToAddTo : root, | auto windowH = X11Symbols::getInstance()->xCreateWindow (display, parentToAddTo != 0 ? parentToAddTo : root, | ||||
| 0, 0, 1, 1, | 0, 0, 1, 1, | ||||
| 0, depth, InputOutput, visual, | |||||
| 0, visualAndDepth.depth, InputOutput, visualAndDepth.visual, | |||||
| CWBorderPixel | CWColormap | CWBackPixmap | CWEventMask | CWOverrideRedirect, | CWBorderPixel | CWColormap | CWBackPixmap | CWEventMask | CWOverrideRedirect, | ||||
| &swa); | &swa); | ||||
| @@ -1334,12 +1338,28 @@ static int getAllEventsMask (bool ignoresMouseClicks) | |||||
| } | } | ||||
| // Set window manager hints | // Set window manager hints | ||||
| auto* wmHints = X11Symbols::getInstance()->xAllocWMHints(); | |||||
| wmHints->flags = InputHint | StateHint; | |||||
| wmHints->input = True; | |||||
| wmHints->initial_state = NormalState; | |||||
| X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); | |||||
| X11Symbols::getInstance()->xFree (wmHints); | |||||
| if (auto* wmHints = X11Symbols::getInstance()->xAllocWMHints()) | |||||
| { | |||||
| wmHints->flags = InputHint | StateHint; | |||||
| wmHints->input = True; | |||||
| wmHints->initial_state = NormalState; | |||||
| X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); | |||||
| X11Symbols::getInstance()->xFree (wmHints); | |||||
| } | |||||
| // Set class hint | |||||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||||
| { | |||||
| if (auto* classHint = X11Symbols::getInstance()->xAllocClassHint()) | |||||
| { | |||||
| auto appName = app->getApplicationName(); | |||||
| classHint->res_name = (char*) appName.getCharPointer().getAddress(); | |||||
| classHint->res_class = (char*) appName.getCharPointer().getAddress(); | |||||
| X11Symbols::getInstance()->xSetClassHint (display, windowH, classHint); | |||||
| X11Symbols::getInstance()->xFree (classHint); | |||||
| } | |||||
| } | |||||
| // Set the window type | // Set the window type | ||||
| setWindowType (windowH, styleFlags); | setWindowType (windowH, styleFlags); | ||||
| @@ -1407,7 +1427,10 @@ void XWindowSystem::destroyWindow (::Window windowH) | |||||
| &event) == True) | &event) == True) | ||||
| {} | {} | ||||
| shmPaintsPendingMap.erase (windowH); | |||||
| #if JUCE_USE_XSHM | |||||
| if (XSHMHelpers::isShmAvailable (display)) | |||||
| shmPaintsPendingMap.erase (windowH); | |||||
| #endif | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1456,12 +1479,15 @@ void XWindowSystem::setIcon (::Window windowH, const Image& newIcon) const | |||||
| if (wmHints == nullptr) | if (wmHints == nullptr) | ||||
| wmHints = X11Symbols::getInstance()->xAllocWMHints(); | wmHints = X11Symbols::getInstance()->xAllocWMHints(); | ||||
| wmHints->flags |= IconPixmapHint | IconMaskHint; | |||||
| wmHints->icon_pixmap = PixmapHelpers::createColourPixmapFromImage (display, newIcon); | |||||
| wmHints->icon_mask = PixmapHelpers::createMaskPixmapFromImage (display, newIcon); | |||||
| if (wmHints != nullptr) | |||||
| { | |||||
| wmHints->flags |= IconPixmapHint | IconMaskHint; | |||||
| wmHints->icon_pixmap = PixmapHelpers::createColourPixmapFromImage (display, newIcon); | |||||
| wmHints->icon_mask = PixmapHelpers::createMaskPixmapFromImage (display, newIcon); | |||||
| X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); | |||||
| X11Symbols::getInstance()->xFree (wmHints); | |||||
| X11Symbols::getInstance()->xSetWMHints (display, windowH, wmHints); | |||||
| X11Symbols::getInstance()->xFree (wmHints); | |||||
| } | |||||
| X11Symbols::getInstance()->xSync (display, False); | X11Symbols::getInstance()->xSync (display, False); | ||||
| } | } | ||||
| @@ -1514,22 +1540,24 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle<int> newBounds, bool | |||||
| XWindowSystemUtilities::ScopedXLock xLock; | XWindowSystemUtilities::ScopedXLock xLock; | ||||
| auto* hints = X11Symbols::getInstance()->xAllocSizeHints(); | |||||
| hints->flags = USSize | USPosition; | |||||
| hints->x = newBounds.getX(); | |||||
| hints->y = newBounds.getY(); | |||||
| hints->width = newBounds.getWidth(); | |||||
| hints->height = newBounds.getHeight(); | |||||
| if ((peer->getStyleFlags() & ComponentPeer::windowIsResizable) == 0) | |||||
| if (auto* hints = X11Symbols::getInstance()->xAllocSizeHints()) | |||||
| { | { | ||||
| hints->min_width = hints->max_width = hints->width; | |||||
| hints->min_height = hints->max_height = hints->height; | |||||
| hints->flags |= PMinSize | PMaxSize; | |||||
| } | |||||
| hints->flags = USSize | USPosition; | |||||
| hints->x = newBounds.getX(); | |||||
| hints->y = newBounds.getY(); | |||||
| hints->width = newBounds.getWidth(); | |||||
| hints->height = newBounds.getHeight(); | |||||
| X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints); | |||||
| X11Symbols::getInstance()->xFree (hints); | |||||
| if ((peer->getStyleFlags() & ComponentPeer::windowIsResizable) == 0) | |||||
| { | |||||
| hints->min_width = hints->max_width = hints->width; | |||||
| hints->min_height = hints->max_height = hints->height; | |||||
| hints->flags |= PMinSize | PMaxSize; | |||||
| } | |||||
| X11Symbols::getInstance()->xSetWMNormalHints (display, windowH, hints); | |||||
| X11Symbols::getInstance()->xFree (hints); | |||||
| } | |||||
| auto windowBorder = peer->getFrameSize(); | auto windowBorder = peer->getFrameSize(); | ||||
| @@ -1787,16 +1815,18 @@ bool XWindowSystem::canUseARGBImages() const | |||||
| return canUseARGB; | return canUseARGB; | ||||
| } | } | ||||
| Image XWindowSystem::createImage (int width, int height, bool argb) const | |||||
| Image XWindowSystem::createImage (bool isSemiTransparent, int width, int height, bool argb) const | |||||
| { | { | ||||
| auto visualAndDepth = displayVisuals->getBestVisualForWindow (isSemiTransparent); | |||||
| #if JUCE_USE_XSHM | #if JUCE_USE_XSHM | ||||
| return Image (new XBitmapImage (display, argb ? Image::ARGB : Image::RGB, | |||||
| return Image (new XBitmapImage (argb ? Image::ARGB : Image::RGB, | |||||
| #else | #else | ||||
| return Image (new XBitmapImage (display, Image::RGB, | |||||
| return Image (new XBitmapImage (Image::RGB, | |||||
| #endif | #endif | ||||
| (width + 31) & ~31, | (width + 31) & ~31, | ||||
| (height + 31) & ~31, | (height + 31) & ~31, | ||||
| false, (unsigned int) depth, visual)); | |||||
| false, (unsigned int) visualAndDepth.depth, visualAndDepth.visual)); | |||||
| } | } | ||||
| void XWindowSystem::blitToWindow (::Window windowH, Image image, Rectangle<int> destinationRect, Rectangle<int> totalRect) const | void XWindowSystem::blitToWindow (::Window windowH, Image image, Rectangle<int> destinationRect, Rectangle<int> totalRect) const | ||||
| @@ -1812,20 +1842,47 @@ void XWindowSystem::blitToWindow (::Window windowH, Image image, Rectangle<int> | |||||
| destinationRect.getX() - totalRect.getX(), destinationRect.getY() - totalRect.getY()); | destinationRect.getX() - totalRect.getX(), destinationRect.getY() - totalRect.getY()); | ||||
| } | } | ||||
| int XWindowSystem::getNumPaintsPending (::Window windowH) const | |||||
| void XWindowSystem::processPendingPaintsForWindow (::Window windowH) | |||||
| { | { | ||||
| #if JUCE_USE_XSHM | #if JUCE_USE_XSHM | ||||
| if (shmPaintsPendingMap[windowH] != 0) | |||||
| if (! XSHMHelpers::isShmAvailable (display)) | |||||
| return; | |||||
| if (getNumPaintsPendingForWindow (windowH) > 0) | |||||
| { | { | ||||
| XWindowSystemUtilities::ScopedXLock xLock; | XWindowSystemUtilities::ScopedXLock xLock; | ||||
| XEvent evt; | XEvent evt; | ||||
| while (X11Symbols::getInstance()->xCheckTypedWindowEvent (display, windowH, shmCompletionEvent, &evt)) | while (X11Symbols::getInstance()->xCheckTypedWindowEvent (display, windowH, shmCompletionEvent, &evt)) | ||||
| --shmPaintsPendingMap[windowH]; | |||||
| removePendingPaintForWindow (windowH); | |||||
| } | } | ||||
| #endif | #endif | ||||
| } | |||||
| return shmPaintsPendingMap[windowH]; | |||||
| int XWindowSystem::getNumPaintsPendingForWindow (::Window windowH) | |||||
| { | |||||
| #if JUCE_USE_XSHM | |||||
| if (XSHMHelpers::isShmAvailable (display)) | |||||
| return shmPaintsPendingMap[windowH]; | |||||
| #endif | |||||
| return 0; | |||||
| } | |||||
| void XWindowSystem::addPendingPaintForWindow (::Window windowH) | |||||
| { | |||||
| #if JUCE_USE_XSHM | |||||
| if (XSHMHelpers::isShmAvailable (display)) | |||||
| ++shmPaintsPendingMap[windowH]; | |||||
| #endif | |||||
| } | |||||
| void XWindowSystem::removePendingPaintForWindow (::Window windowH) | |||||
| { | |||||
| #if JUCE_USE_XSHM | |||||
| if (XSHMHelpers::isShmAvailable (display)) | |||||
| --shmPaintsPendingMap[windowH]; | |||||
| #endif | |||||
| } | } | ||||
| void XWindowSystem::setScreenSaverEnabled (bool enabled) const | void XWindowSystem::setScreenSaverEnabled (bool enabled) const | ||||
| @@ -2107,24 +2164,39 @@ ModifierKeys XWindowSystem::getNativeRealtimeModifiers() const | |||||
| return ModifierKeys::currentModifiers; | return ModifierKeys::currentModifiers; | ||||
| } | } | ||||
| Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||||
| static bool hasWorkAreaData (const XWindowSystemUtilities::GetXProperty& prop) | |||||
| { | { | ||||
| Array<Displays::Display> displays; | |||||
| Atom hints = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WORKAREA"); | |||||
| return prop.success | |||||
| && prop.actualType == XA_CARDINAL | |||||
| && prop.actualFormat == 32 | |||||
| && prop.numItems == 4 | |||||
| && prop.data != nullptr; | |||||
| } | |||||
| auto getWorkAreaPropertyData = [&] (int screenNum) -> unsigned char* | |||||
| static Rectangle<int> getWorkArea (const XWindowSystemUtilities::GetXProperty& prop) | |||||
| { | |||||
| if (hasWorkAreaData (prop)) | |||||
| { | { | ||||
| if (hints != None) | |||||
| { | |||||
| XWindowSystemUtilities::GetXProperty prop (X11Symbols::getInstance()->xRootWindow (display, screenNum), hints, 0, 4, false, XA_CARDINAL); | |||||
| auto* positionData = prop.data; | |||||
| std::array<long, 4> position; | |||||
| if (prop.success && prop.actualType == XA_CARDINAL && prop.actualFormat == 32 && prop.numItems == 4) | |||||
| return prop.data; | |||||
| for (auto& p : position) | |||||
| { | |||||
| memcpy (&p, positionData, sizeof (long)); | |||||
| positionData += sizeof (long); | |||||
| } | } | ||||
| return nullptr; | |||||
| }; | |||||
| return { (int) position[0], (int) position[1], | |||||
| (int) position[2], (int) position[3] }; | |||||
| } | |||||
| return {}; | |||||
| } | |||||
| Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||||
| { | |||||
| Array<Displays::Display> displays; | |||||
| auto workAreaHints = XWindowSystemUtilities::Atoms::getIfExists (display, "_NET_WORKAREA"); | |||||
| #if JUCE_USE_XRANDR | #if JUCE_USE_XRANDR | ||||
| { | { | ||||
| @@ -2137,11 +2209,13 @@ Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||||
| for (int i = 0; i < numMonitors; ++i) | for (int i = 0; i < numMonitors; ++i) | ||||
| { | { | ||||
| if (getWorkAreaPropertyData (i) == nullptr) | |||||
| auto rootWindow = X11Symbols::getInstance()->xRootWindow (display, i); | |||||
| XWindowSystemUtilities::GetXProperty prop (rootWindow, workAreaHints, 0, 4, false, XA_CARDINAL); | |||||
| if (! hasWorkAreaData (prop)) | |||||
| continue; | continue; | ||||
| if (auto* screens = X11Symbols::getInstance()->xRRGetScreenResources (display, | |||||
| X11Symbols::getInstance()->xRootWindow (display, i))) | |||||
| if (auto* screens = X11Symbols::getInstance()->xRRGetScreenResources (display, rootWindow)) | |||||
| { | { | ||||
| for (int j = 0; j < screens->noutput; ++j) | for (int j = 0; j < screens->noutput; ++j) | ||||
| { | { | ||||
| @@ -2225,25 +2299,22 @@ Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||||
| if (displays.isEmpty()) | if (displays.isEmpty()) | ||||
| #endif | #endif | ||||
| { | { | ||||
| if (hints != None) | |||||
| if (workAreaHints != None) | |||||
| { | { | ||||
| auto numMonitors = X11Symbols::getInstance()->xScreenCount (display); | auto numMonitors = X11Symbols::getInstance()->xScreenCount (display); | ||||
| for (int i = 0; i < numMonitors; ++i) | for (int i = 0; i < numMonitors; ++i) | ||||
| { | { | ||||
| if (auto* positionData = getWorkAreaPropertyData (i)) | |||||
| { | |||||
| std::array<long, 4> position; | |||||
| XWindowSystemUtilities::GetXProperty prop (X11Symbols::getInstance()->xRootWindow (display, i), | |||||
| workAreaHints, 0, 4, false, XA_CARDINAL); | |||||
| for (auto& p : position) | |||||
| { | |||||
| memcpy (&p, positionData, sizeof (long)); | |||||
| positionData += sizeof (long); | |||||
| } | |||||
| auto workArea = getWorkArea (prop); | |||||
| if (! workArea.isEmpty()) | |||||
| { | |||||
| Displays::Display d; | Displays::Display d; | ||||
| d.totalArea = { (int) position[0], (int) position[1], | |||||
| (int) position[2], (int) position[3] }; | |||||
| d.totalArea = workArea; | |||||
| d.isMain = displays.isEmpty(); | d.isMain = displays.isEmpty(); | ||||
| d.scale = masterScale; | d.scale = masterScale; | ||||
| d.dpi = DisplayHelpers::getDisplayDPI (display, i); | d.dpi = DisplayHelpers::getDisplayDPI (display, i); | ||||
| @@ -2675,6 +2746,40 @@ long XWindowSystem::getUserTime (::Window windowH) const | |||||
| return result; | return result; | ||||
| } | } | ||||
| XWindowSystem::DisplayVisuals::DisplayVisuals (::Display* xDisplay) | |||||
| { | |||||
| auto findVisualWithDepthOrNull = [&] (int desiredDepth) -> Visual* | |||||
| { | |||||
| int matchedDepth = 0; | |||||
| auto* visual = Visuals::findVisualFormat (xDisplay, desiredDepth, matchedDepth); | |||||
| if (desiredDepth == matchedDepth) | |||||
| return visual; | |||||
| return nullptr; | |||||
| }; | |||||
| visual16Bit = findVisualWithDepthOrNull (16); | |||||
| visual24Bit = findVisualWithDepthOrNull (24); | |||||
| visual32Bit = findVisualWithDepthOrNull (32); | |||||
| } | |||||
| XWindowSystem::VisualAndDepth XWindowSystem::DisplayVisuals::getBestVisualForWindow (bool isSemiTransparent) const | |||||
| { | |||||
| if (isSemiTransparent && visual32Bit != nullptr) | |||||
| return { visual32Bit, 32 }; | |||||
| if (visual24Bit != nullptr) | |||||
| return { visual24Bit, 24 }; | |||||
| return { visual16Bit, 16 }; | |||||
| } | |||||
| bool XWindowSystem::DisplayVisuals::isValid() const noexcept | |||||
| { | |||||
| return (visual32Bit != nullptr || visual24Bit != nullptr || visual16Bit != nullptr); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| bool XWindowSystem::initialiseXDisplay() | bool XWindowSystem::initialiseXDisplay() | ||||
| { | { | ||||
| @@ -2708,7 +2813,8 @@ bool XWindowSystem::initialiseXDisplay() | |||||
| // Create our message window (this will never be mapped) | // Create our message window (this will never be mapped) | ||||
| auto screen = X11Symbols::getInstance()->xDefaultScreen (display); | auto screen = X11Symbols::getInstance()->xDefaultScreen (display); | ||||
| juce_messageWindowHandle = X11Symbols::getInstance()->xCreateWindow (display, X11Symbols::getInstance()->xRootWindow (display, screen), | |||||
| auto root = X11Symbols::getInstance()->xRootWindow (display, screen); | |||||
| juce_messageWindowHandle = X11Symbols::getInstance()->xCreateWindow (display, root, | |||||
| 0, 0, 1, 1, 0, 0, InputOnly, | 0, 0, 1, 1, 0, 0, InputOnly, | ||||
| X11Symbols::getInstance()->xDefaultVisual (display, screen), | X11Symbols::getInstance()->xDefaultVisual (display, screen), | ||||
| CWEventMask, &swa); | CWEventMask, &swa); | ||||
| @@ -2717,22 +2823,6 @@ bool XWindowSystem::initialiseXDisplay() | |||||
| atoms = XWindowSystemUtilities::Atoms (display); | atoms = XWindowSystemUtilities::Atoms (display); | ||||
| // Get defaults for various properties | |||||
| auto root = X11Symbols::getInstance()->xRootWindow (display, screen); | |||||
| // Try to obtain a 32-bit visual or fallback to 24 or 16 | |||||
| visual = Visuals::findVisualFormat (display, 32, depth); | |||||
| if (visual == nullptr) | |||||
| { | |||||
| Logger::outputDebugString ("ERROR: System doesn't support 32, 24 or 16 bit RGB display.\n"); | |||||
| Process::terminate(); | |||||
| } | |||||
| // Create and install a colormap suitable for our visual | |||||
| colormap = X11Symbols::getInstance()->xCreateColormap (display, root, visual, AllocNone); | |||||
| X11Symbols::getInstance()->xInstallColormap (display, colormap); | |||||
| initialisePointerMap(); | initialisePointerMap(); | ||||
| updateModifierMappings(); | updateModifierMappings(); | ||||
| @@ -2741,6 +2831,14 @@ bool XWindowSystem::initialiseXDisplay() | |||||
| shmCompletionEvent = X11Symbols::getInstance()->xShmGetEventBase (display) + ShmCompletion; | shmCompletionEvent = X11Symbols::getInstance()->xShmGetEventBase (display) + ShmCompletion; | ||||
| #endif | #endif | ||||
| displayVisuals = std::make_unique<DisplayVisuals> (display); | |||||
| if (! displayVisuals->isValid()) | |||||
| { | |||||
| Logger::outputDebugString ("ERROR: System doesn't support 32, 24 or 16 bit RGB display.\n"); | |||||
| Process::terminate(); | |||||
| } | |||||
| // Setup input event handler | // Setup input event handler | ||||
| LinuxEventLoop::registerFdCallback (X11Symbols::getInstance()->xConnectionNumber (display), | LinuxEventLoop::registerFdCallback (X11Symbols::getInstance()->xConnectionNumber (display), | ||||
| [this] (int) | [this] (int) | ||||
| @@ -2788,10 +2886,10 @@ void XWindowSystem::destroyXDisplay() | |||||
| X11Symbols::getInstance()->xSync (display, True); | X11Symbols::getInstance()->xSync (display, True); | ||||
| LinuxEventLoop::unregisterFdCallback (X11Symbols::getInstance()->xConnectionNumber (display)); | LinuxEventLoop::unregisterFdCallback (X11Symbols::getInstance()->xConnectionNumber (display)); | ||||
| visual = nullptr; | |||||
| X11Symbols::getInstance()->xCloseDisplay (display); | X11Symbols::getInstance()->xCloseDisplay (display); | ||||
| display = nullptr; | display = nullptr; | ||||
| displayVisuals = nullptr; | |||||
| } | } | ||||
| } | } | ||||
| @@ -2870,7 +2968,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEv | |||||
| XWindowSystemUtilities::ScopedXLock xLock; | XWindowSystemUtilities::ScopedXLock xLock; | ||||
| if (event.xany.type == shmCompletionEvent) | if (event.xany.type == shmCompletionEvent) | ||||
| --shmPaintsPendingMap[(::Window) peer->getNativeHandle()]; | |||||
| XWindowSystem::getInstance()->removePendingPaintForWindow ((::Window) peer->getNativeHandle()); | |||||
| } | } | ||||
| #endif | #endif | ||||
| break; | break; | ||||
| @@ -2978,7 +3076,7 @@ void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer<::Window>* peer, XKe | |||||
| if (sym >= XK_F1 && sym <= XK_F35) | if (sym >= XK_F1 && sym <= XK_F35) | ||||
| { | { | ||||
| keyPressed = true; | keyPressed = true; | ||||
| keyCode = (sym & 0xff) | Keys::extendedKeyModifier; | |||||
| keyCode = static_cast<int> ((sym & 0xff) | Keys::extendedKeyModifier); | |||||
| } | } | ||||
| break; | break; | ||||
| } | } | ||||
| @@ -126,9 +126,12 @@ public: | |||||
| bool canUseSemiTransparentWindows() const; | bool canUseSemiTransparentWindows() const; | ||||
| bool canUseARGBImages() const; | bool canUseARGBImages() const; | ||||
| int getNumPaintsPending (::Window windowH) const; | |||||
| int getNumPaintsPendingForWindow (::Window windowH); | |||||
| void processPendingPaintsForWindow (::Window windowH); | |||||
| void addPendingPaintForWindow (::Window windowH); | |||||
| void removePendingPaintForWindow (::Window windowH); | |||||
| Image createImage (int width, int height, bool argb) const; | |||||
| Image createImage (bool isSemiTransparentWindow, int width, int height, bool argb) const; | |||||
| void blitToWindow (::Window windowH, Image image, Rectangle<int> destinationRect, Rectangle<int> totalRect) const; | void blitToWindow (::Window windowH, Image image, Rectangle<int> destinationRect, Rectangle<int> totalRect) const; | ||||
| void setScreenSaverEnabled (bool enabled) const; | void setScreenSaverEnabled (bool enabled) const; | ||||
| @@ -171,6 +174,24 @@ private: | |||||
| ~XWindowSystem(); | ~XWindowSystem(); | ||||
| //============================================================================== | //============================================================================== | ||||
| struct VisualAndDepth | |||||
| { | |||||
| Visual* visual; | |||||
| int depth; | |||||
| }; | |||||
| struct DisplayVisuals | |||||
| { | |||||
| explicit DisplayVisuals (::Display* d); | |||||
| VisualAndDepth getBestVisualForWindow (bool isSemiTransparent) const; | |||||
| bool isValid() const noexcept; | |||||
| Visual* visual16Bit = nullptr; | |||||
| Visual* visual24Bit = nullptr; | |||||
| Visual* visual32Bit = nullptr; | |||||
| }; | |||||
| bool initialiseXDisplay(); | bool initialiseXDisplay(); | ||||
| void destroyXDisplay(); | void destroyXDisplay(); | ||||
| @@ -217,10 +238,13 @@ private: | |||||
| XWindowSystemUtilities::Atoms atoms; | XWindowSystemUtilities::Atoms atoms; | ||||
| ::Display* display = nullptr; | ::Display* display = nullptr; | ||||
| Colormap colormap = {}; | |||||
| Visual* visual = nullptr; | |||||
| std::unique_ptr<DisplayVisuals> displayVisuals; | |||||
| #if JUCE_USE_XSHM | |||||
| std::map<::Window, int> shmPaintsPendingMap; | |||||
| #endif | |||||
| int depth = 0, shmCompletionEvent = 0; | |||||
| int shmCompletionEvent = 0; | |||||
| int pointerMap[5] = {}; | int pointerMap[5] = {}; | ||||
| String localClipboardContent; | String localClipboardContent; | ||||
| @@ -42,7 +42,7 @@ public: | |||||
| } | } | ||||
| }; | }; | ||||
| void updateButtonTickColour (ToggleButton* button, bool usingDefault) | |||||
| static void updateButtonTickColour (ToggleButton* button, bool usingDefault) | |||||
| { | { | ||||
| button->setColour (ToggleButton::tickColourId, button->getLookAndFeel().findColour (ToggleButton::tickColourId) | button->setColour (ToggleButton::tickColourId, button->getLookAndFeel().findColour (ToggleButton::tickColourId) | ||||
| .withAlpha (usingDefault ? 0.4f : 1.0f)); | .withAlpha (usingDefault ? 0.4f : 1.0f)); | ||||
| @@ -278,10 +278,11 @@ struct TextEditor::Iterator | |||||
| Iterator (const TextEditor& ed) | Iterator (const TextEditor& ed) | ||||
| : sections (ed.sections), | : sections (ed.sections), | ||||
| justification (ed.justification), | justification (ed.justification), | ||||
| justificationWidth (ed.getJustificationWidth()), | |||||
| bottomRight (ed.getMaximumWidth(), ed.getMaximumHeight()), | |||||
| wordWrapWidth (ed.getWordWrapWidth()), | wordWrapWidth (ed.getWordWrapWidth()), | ||||
| passwordCharacter (ed.passwordCharacter), | passwordCharacter (ed.passwordCharacter), | ||||
| lineSpacing (ed.lineSpacing) | |||||
| lineSpacing (ed.lineSpacing), | |||||
| underlineWhitespace (ed.underlineWhitespace) | |||||
| { | { | ||||
| jassert (wordWrapWidth > 0); | jassert (wordWrapWidth > 0); | ||||
| @@ -292,6 +293,8 @@ struct TextEditor::Iterator | |||||
| if (currentSection != nullptr) | if (currentSection != nullptr) | ||||
| beginNewLine(); | beginNewLine(); | ||||
| } | } | ||||
| lineHeight = ed.currentFont.getHeight(); | |||||
| } | } | ||||
| Iterator (const Iterator&) = default; | Iterator (const Iterator&) = default; | ||||
| @@ -325,7 +328,7 @@ struct TextEditor::Iterator | |||||
| { | { | ||||
| tempAtom.numChars = (uint16) split; | tempAtom.numChars = (uint16) split; | ||||
| tempAtom.width = g.getGlyph (split - 1).getRight(); | tempAtom.width = g.getGlyph (split - 1).getRight(); | ||||
| atomX = getJustificationOffset (tempAtom.width); | |||||
| atomX = getJustificationOffsetX (tempAtom.width); | |||||
| atomRight = atomX + tempAtom.width; | atomRight = atomX + tempAtom.width; | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -427,14 +430,14 @@ struct TextEditor::Iterator | |||||
| tempAtom.numChars = 0; | tempAtom.numChars = 0; | ||||
| atom = &tempAtom; | atom = &tempAtom; | ||||
| if (atomX > justificationOffset) | |||||
| if (atomX > justificationOffsetX) | |||||
| beginNewLine(); | beginNewLine(); | ||||
| return next(); | return next(); | ||||
| } | } | ||||
| beginNewLine(); | beginNewLine(); | ||||
| atomX = justificationOffset; | |||||
| atomX = justificationOffsetX; | |||||
| atomRight = atomX + atom->width; | atomRight = atomX + atom->width; | ||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -494,25 +497,22 @@ struct TextEditor::Iterator | |||||
| ++tempAtomIndex; | ++tempAtomIndex; | ||||
| } | } | ||||
| justificationOffset = getJustificationOffset (lineWidth); | |||||
| atomX = justificationOffset; | |||||
| justificationOffsetX = getJustificationOffsetX (lineWidth); | |||||
| atomX = justificationOffsetX; | |||||
| } | } | ||||
| float getJustificationOffset (float lineWidth) const | |||||
| float getJustificationOffsetX (float lineWidth) const | |||||
| { | { | ||||
| if (justification.getOnlyHorizontalFlags() == Justification::horizontallyCentred) | |||||
| return jmax (0.0f, (justificationWidth - lineWidth) * 0.5f); | |||||
| if (justification.getOnlyHorizontalFlags() == Justification::right) | |||||
| return jmax (0.0f, justificationWidth - lineWidth); | |||||
| if (justification.testFlags (Justification::horizontallyCentred)) return jmax (0.0f, (bottomRight.x - lineWidth) * 0.5f); | |||||
| if (justification.testFlags (Justification::right)) return jmax (0.0f, bottomRight.x - lineWidth); | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void draw (Graphics& g, const UniformTextSection*& lastSection) const | |||||
| void draw (Graphics& g, const UniformTextSection*& lastSection, AffineTransform transform) const | |||||
| { | { | ||||
| if (passwordCharacter != 0 || ! atom->isWhitespace()) | |||||
| if (passwordCharacter != 0 || (underlineWhitespace || ! atom->isWhitespace())) | |||||
| { | { | ||||
| if (lastSection != currentSection) | if (lastSection != currentSection) | ||||
| { | { | ||||
| @@ -527,7 +527,7 @@ struct TextEditor::Iterator | |||||
| ga.addLineOfText (currentSection->font, | ga.addLineOfText (currentSection->font, | ||||
| atom->getTrimmedText (passwordCharacter), | atom->getTrimmedText (passwordCharacter), | ||||
| atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); | atomX, (float) roundToInt (lineY + lineHeight - maxDescent)); | ||||
| ga.draw (g); | |||||
| ga.draw (g, transform); | |||||
| } | } | ||||
| } | } | ||||
| @@ -539,18 +539,19 @@ struct TextEditor::Iterator | |||||
| area.add (startX, lineY, endX - startX, lineHeight * lineSpacing); | area.add (startX, lineY, endX - startX, lineHeight * lineSpacing); | ||||
| } | } | ||||
| void drawUnderline (Graphics& g, Range<int> underline, Colour colour) const | |||||
| void drawUnderline (Graphics& g, Range<int> underline, Colour colour, AffineTransform transform) const | |||||
| { | { | ||||
| auto startX = roundToInt (indexToX (underline.getStart())); | auto startX = roundToInt (indexToX (underline.getStart())); | ||||
| auto endX = roundToInt (indexToX (underline.getEnd())); | auto endX = roundToInt (indexToX (underline.getEnd())); | ||||
| auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f); | auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f); | ||||
| Graphics::ScopedSaveState state (g); | Graphics::ScopedSaveState state (g); | ||||
| g.addTransform (transform); | |||||
| g.reduceClipRegion ({ startX, baselineY, endX - startX, 1 }); | g.reduceClipRegion ({ startX, baselineY, endX - startX, 1 }); | ||||
| g.fillCheckerBoard ({ (float) endX, (float) baselineY + 1.0f }, 3.0f, 1.0f, colour, Colours::transparentBlack); | g.fillCheckerBoard ({ (float) endX, (float) baselineY + 1.0f }, 3.0f, 1.0f, colour, Colours::transparentBlack); | ||||
| } | } | ||||
| void drawSelectedText (Graphics& g, Range<int> selected, Colour selectedTextColour) const | |||||
| void drawSelectedText (Graphics& g, Range<int> selected, Colour selectedTextColour, AffineTransform transform) const | |||||
| { | { | ||||
| if (passwordCharacter != 0 || ! atom->isWhitespace()) | if (passwordCharacter != 0 || ! atom->isWhitespace()) | ||||
| { | { | ||||
| @@ -566,7 +567,7 @@ struct TextEditor::Iterator | |||||
| ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1); | ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1); | ||||
| g.setColour (currentSection->colour); | g.setColour (currentSection->colour); | ||||
| ga2.draw (g); | |||||
| ga2.draw (g, transform); | |||||
| } | } | ||||
| if (selected.getStart() > indexInText) | if (selected.getStart() > indexInText) | ||||
| @@ -576,11 +577,11 @@ struct TextEditor::Iterator | |||||
| ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText); | ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText); | ||||
| g.setColour (currentSection->colour); | g.setColour (currentSection->colour); | ||||
| ga2.draw (g); | |||||
| ga2.draw (g, transform); | |||||
| } | } | ||||
| g.setColour (selectedTextColour); | g.setColour (selectedTextColour); | ||||
| ga.draw (g); | |||||
| ga.draw (g, transform); | |||||
| } | } | ||||
| } | } | ||||
| @@ -649,20 +650,42 @@ struct TextEditor::Iterator | |||||
| return false; | return false; | ||||
| } | } | ||||
| float getYOffset() | |||||
| { | |||||
| if (justification.testFlags (Justification::top) || lineY >= bottomRight.y) | |||||
| return 0; | |||||
| while (next()) | |||||
| { | |||||
| if (lineY >= bottomRight.y) | |||||
| return 0; | |||||
| } | |||||
| auto bottom = jmax (0.0f, bottomRight.y - lineY - lineHeight); | |||||
| if (justification.testFlags (Justification::bottom)) | |||||
| return bottom; | |||||
| return bottom * 0.5f; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| int indexInText = 0; | int indexInText = 0; | ||||
| float lineY = 0, justificationOffset = 0, lineHeight = 0, maxDescent = 0; | |||||
| float lineY = 0, lineHeight = 0, maxDescent = 0; | |||||
| float atomX = 0, atomRight = 0; | float atomX = 0, atomRight = 0; | ||||
| const TextAtom* atom = nullptr; | const TextAtom* atom = nullptr; | ||||
| const UniformTextSection* currentSection = nullptr; | |||||
| private: | private: | ||||
| const OwnedArray<UniformTextSection>& sections; | const OwnedArray<UniformTextSection>& sections; | ||||
| const UniformTextSection* currentSection = nullptr; | |||||
| int sectionIndex = 0, atomIndex = 0; | int sectionIndex = 0, atomIndex = 0; | ||||
| Justification justification; | Justification justification; | ||||
| const float justificationWidth, wordWrapWidth; | |||||
| float justificationOffsetX = 0; | |||||
| const Point<float> bottomRight; | |||||
| const float wordWrapWidth; | |||||
| const juce_wchar passwordCharacter; | const juce_wchar passwordCharacter; | ||||
| const float lineSpacing; | const float lineSpacing; | ||||
| const bool underlineWhitespace; | |||||
| TextAtom tempAtom; | TextAtom tempAtom; | ||||
| void moveToEndOfLastAtom() | void moveToEndOfLastAtom() | ||||
| @@ -673,7 +696,7 @@ private: | |||||
| if (atom->isNewLine()) | if (atom->isNewLine()) | ||||
| { | { | ||||
| atomX = 0.0f; | |||||
| atomX = getJustificationOffsetX (0); | |||||
| lineY += lineHeight * lineSpacing; | lineY += lineHeight * lineSpacing; | ||||
| } | } | ||||
| } | } | ||||
| @@ -826,8 +849,8 @@ struct TextEditor::TextEditorViewport : public Viewport | |||||
| void visibleAreaChanged (const Rectangle<int>&) override | void visibleAreaChanged (const Rectangle<int>&) override | ||||
| { | { | ||||
| if (! rentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars | |||||
| // appear and disappear, causing the wrap width to change. | |||||
| if (! reentrant) // it's rare, but possible to get into a feedback loop as the viewport's scrollbars | |||||
| // appear and disappear, causing the wrap width to change. | |||||
| { | { | ||||
| auto wordWrapWidth = owner.getWordWrapWidth(); | auto wordWrapWidth = owner.getWordWrapWidth(); | ||||
| @@ -835,9 +858,8 @@ struct TextEditor::TextEditorViewport : public Viewport | |||||
| { | { | ||||
| lastWordWrapWidth = wordWrapWidth; | lastWordWrapWidth = wordWrapWidth; | ||||
| rentrant = true; | |||||
| owner.updateTextHolderSize(); | |||||
| rentrant = false; | |||||
| ScopedValueSetter<bool> svs (reentrant, true); | |||||
| owner.checkLayout(); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -845,7 +867,7 @@ struct TextEditor::TextEditorViewport : public Viewport | |||||
| private: | private: | ||||
| TextEditor& owner; | TextEditor& owner; | ||||
| float lastWordWrapWidth = 0; | float lastWordWrapWidth = 0; | ||||
| bool rentrant = false; | |||||
| bool reentrant = false; | |||||
| JUCE_DECLARE_NON_COPYABLE (TextEditorViewport) | JUCE_DECLARE_NON_COPYABLE (TextEditorViewport) | ||||
| }; | }; | ||||
| @@ -936,8 +958,8 @@ void TextEditor::setMultiLine (const bool shouldBeMultiLine, | |||||
| multiline = shouldBeMultiLine; | multiline = shouldBeMultiLine; | ||||
| wordWrap = shouldWordWrap && shouldBeMultiLine; | wordWrap = shouldWordWrap && shouldBeMultiLine; | ||||
| viewport->setScrollBarsShown (scrollbarVisible && multiline, | |||||
| scrollbarVisible && multiline); | |||||
| checkLayout(); | |||||
| viewport->setViewPosition (0, 0); | viewport->setViewPosition (0, 0); | ||||
| resized(); | resized(); | ||||
| scrollToMakeSureCursorIsVisible(); | scrollToMakeSureCursorIsVisible(); | ||||
| @@ -954,8 +976,7 @@ void TextEditor::setScrollbarsShown (bool shown) | |||||
| if (scrollbarVisible != shown) | if (scrollbarVisible != shown) | ||||
| { | { | ||||
| scrollbarVisible = shown; | scrollbarVisible = shown; | ||||
| shown = shown && isMultiLine(); | |||||
| viewport->setScrollBarsShown (shown, shown); | |||||
| checkLayout(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1003,7 +1024,9 @@ void TextEditor::setJustification (Justification j) | |||||
| if (justification != j) | if (justification != j) | ||||
| { | { | ||||
| justification = j; | justification = j; | ||||
| resized(); | resized(); | ||||
| repaint(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1028,7 +1051,7 @@ void TextEditor::applyFontToAllText (const Font& newFont, bool changeCurrentFont | |||||
| } | } | ||||
| coalesceSimilarSections(); | coalesceSimilarSections(); | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| scrollToMakeSureCursorIsVisible(); | scrollToMakeSureCursorIsVisible(); | ||||
| repaint(); | repaint(); | ||||
| } | } | ||||
| @@ -1090,8 +1113,13 @@ void TextEditor::recreateCaret() | |||||
| void TextEditor::updateCaretPosition() | void TextEditor::updateCaretPosition() | ||||
| { | { | ||||
| if (caret != nullptr) | |||||
| caret->setCaretPosition (getCaretRectangle().translated (leftIndent, topIndent)); | |||||
| if (caret != nullptr | |||||
| && getWidth() > 0 && getHeight() > 0) | |||||
| { | |||||
| Iterator i (*this); | |||||
| caret->setCaretPosition (getCaretRectangle().translated (leftIndent, | |||||
| topIndent + roundToInt (i.getYOffset()))); | |||||
| } | |||||
| } | } | ||||
| TextEditor::LengthAndCharacterRestriction::LengthAndCharacterRestriction (int maxLen, const String& chars) | TextEditor::LengthAndCharacterRestriction::LengthAndCharacterRestriction (int maxLen, const String& chars) | ||||
| @@ -1146,7 +1174,7 @@ void TextEditor::setScrollBarThickness (int newThicknessPixels) | |||||
| void TextEditor::clear() | void TextEditor::clear() | ||||
| { | { | ||||
| clearInternal (nullptr); | clearInternal (nullptr); | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| undoManager.clearUndoHistory(); | undoManager.clearUndoHistory(); | ||||
| } | } | ||||
| @@ -1181,7 +1209,7 @@ void TextEditor::setText (const String& newText, bool sendTextChangeMessage) | |||||
| else | else | ||||
| textValue.addListener (textHolder); | textValue.addListener (textHolder); | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| scrollToMakeSureCursorIsVisible(); | scrollToMakeSureCursorIsVisible(); | ||||
| undoManager.clearUndoHistory(); | undoManager.clearUndoHistory(); | ||||
| @@ -1214,7 +1242,7 @@ void TextEditor::textWasChangedByValue() | |||||
| //============================================================================== | //============================================================================== | ||||
| void TextEditor::textChanged() | void TextEditor::textChanged() | ||||
| { | { | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| if (listeners.size() != 0 || onTextChange != nullptr) | if (listeners.size() != 0 || onTextChange != nullptr) | ||||
| postCommandMessage (TextEditorDefs::textChangeMessageId); | postCommandMessage (TextEditorDefs::textChangeMessageId); | ||||
| @@ -1259,30 +1287,33 @@ void TextEditor::repaintText (Range<int> range) | |||||
| { | { | ||||
| if (! range.isEmpty()) | if (! range.isEmpty()) | ||||
| { | { | ||||
| auto lh = currentFont.getHeight(); | |||||
| auto wordWrapWidth = getWordWrapWidth(); | |||||
| if (wordWrapWidth > 0) | |||||
| if (range.getEnd() >= getTotalNumChars()) | |||||
| { | { | ||||
| Point<float> anchor; | |||||
| Iterator i (*this); | |||||
| i.getCharPosition (range.getStart(), anchor, lh); | |||||
| textHolder->repaint(); | |||||
| return; | |||||
| } | |||||
| auto y1 = (int) anchor.y; | |||||
| int y2; | |||||
| Iterator i (*this); | |||||
| if (range.getEnd() >= getTotalNumChars()) | |||||
| { | |||||
| y2 = textHolder->getHeight(); | |||||
| } | |||||
| else | |||||
| { | |||||
| i.getCharPosition (range.getEnd(), anchor, lh); | |||||
| y2 = (int) (anchor.y + lh * 2.0f); | |||||
| } | |||||
| Point<float> anchor; | |||||
| auto lh = currentFont.getHeight(); | |||||
| i.getCharPosition (range.getStart(), anchor, lh); | |||||
| textHolder->repaint (0, y1, textHolder->getWidth(), y2 - y1); | |||||
| auto y1 = std::trunc (anchor.y); | |||||
| int y2 = 0; | |||||
| if (range.getEnd() >= getTotalNumChars()) | |||||
| { | |||||
| y2 = textHolder->getHeight(); | |||||
| } | } | ||||
| else | |||||
| { | |||||
| i.getCharPosition (range.getEnd(), anchor, lh); | |||||
| y2 = (int) (anchor.y + lh * 2.0f); | |||||
| } | |||||
| auto offset = i.getYOffset(); | |||||
| textHolder->repaint (0, roundToInt (y1 + offset), textHolder->getWidth(), roundToInt ((float) y2 - y1 + offset)); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1367,33 +1398,66 @@ Rectangle<float> TextEditor::getCaretRectangleFloat() const | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| enum { rightEdgeSpace = 2 }; | |||||
| // Extra space for the cursor at the right-hand-edge | |||||
| constexpr int rightEdgeSpace = 2; | |||||
| float TextEditor::getWordWrapWidth() const | float TextEditor::getWordWrapWidth() const | ||||
| { | { | ||||
| return wordWrap ? getJustificationWidth() | |||||
| return wordWrap ? getMaximumWidth() | |||||
| : std::numeric_limits<float>::max(); | : std::numeric_limits<float>::max(); | ||||
| } | } | ||||
| float TextEditor::getJustificationWidth() const | |||||
| float TextEditor::getMaximumWidth() const | |||||
| { | { | ||||
| return (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1)); | return (float) (viewport->getMaximumVisibleWidth() - (leftIndent + rightEdgeSpace + 1)); | ||||
| } | } | ||||
| void TextEditor::updateTextHolderSize() | |||||
| float TextEditor::getMaximumHeight() const | |||||
| { | |||||
| return (float) (viewport->getMaximumVisibleHeight() - topIndent); | |||||
| } | |||||
| void TextEditor::checkLayout() | |||||
| { | { | ||||
| if (getWordWrapWidth() > 0) | if (getWordWrapWidth() > 0) | ||||
| { | { | ||||
| float maxWidth = getJustificationWidth(); | |||||
| auto maxWidth = getMaximumWidth(); | |||||
| Iterator i (*this); | Iterator i (*this); | ||||
| while (i.next()) | while (i.next()) | ||||
| maxWidth = jmax (maxWidth, i.atomRight); | maxWidth = jmax (maxWidth, i.atomRight); | ||||
| auto w = leftIndent + roundToInt (maxWidth); | |||||
| auto h = topIndent + roundToInt (jmax (i.lineY + i.lineHeight, currentFont.getHeight())); | |||||
| auto textRight = roundToInt (maxWidth); | |||||
| auto textBottom = roundToInt (i.lineY + i.lineHeight + i.getYOffset()); | |||||
| textHolder->setSize (w + rightEdgeSpace, h + 1); // (allows a bit of space for the cursor to be at the right-hand-edge) | |||||
| if (i.atom != nullptr && i.atom->isNewLine()) | |||||
| textBottom += (int) i.lineHeight; | |||||
| updateTextHolderSize (textRight, textBottom); | |||||
| updateScrollbarVisibility (textRight, textBottom); | |||||
| } | |||||
| } | |||||
| void TextEditor::updateTextHolderSize (int textRight, int textBottom) | |||||
| { | |||||
| auto w = leftIndent + jmax (roundToInt (getMaximumWidth()), textRight); | |||||
| auto h = topIndent + textBottom; | |||||
| textHolder->setSize (w + rightEdgeSpace, h + 1); | |||||
| } | |||||
| void TextEditor::updateScrollbarVisibility (int textRight, int textBottom) | |||||
| { | |||||
| if (scrollbarVisible && multiline) | |||||
| { | |||||
| auto horizontalVisible = (leftIndent + textRight) > (viewport->getMaximumVisibleWidth() - viewport->getScrollBarThickness()); | |||||
| auto verticalVisible = (topIndent + textBottom) > (viewport->getMaximumVisibleHeight() + 1); | |||||
| viewport->setScrollBarsShown (verticalVisible, horizontalVisible); | |||||
| } | |||||
| else | |||||
| { | |||||
| viewport->setScrollBarsShown (false, false); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1402,8 +1466,14 @@ int TextEditor::getTextHeight() const { return textHolder->getHeight(); } | |||||
| void TextEditor::setIndents (int newLeftIndent, int newTopIndent) | void TextEditor::setIndents (int newLeftIndent, int newTopIndent) | ||||
| { | { | ||||
| leftIndent = newLeftIndent; | |||||
| topIndent = newTopIndent; | |||||
| if (leftIndent != newLeftIndent || topIndent != newTopIndent) | |||||
| { | |||||
| leftIndent = newLeftIndent; | |||||
| topIndent = newTopIndent; | |||||
| resized(); | |||||
| repaint(); | |||||
| } | |||||
| } | } | ||||
| void TextEditor::setBorder (BorderSize<int> border) | void TextEditor::setBorder (BorderSize<int> border) | ||||
| @@ -1506,8 +1576,10 @@ void TextEditor::moveCaretTo (const int newPosition, const bool isSelecting) | |||||
| int TextEditor::getTextIndexAt (const int x, const int y) | int TextEditor::getTextIndexAt (const int x, const int y) | ||||
| { | { | ||||
| Iterator i (*this); | |||||
| return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent - borderSize.getLeft()), | return indexAtPosition ((float) (x + viewport->getViewPositionX() - leftIndent - borderSize.getLeft()), | ||||
| (float) (y + viewport->getViewPositionY() - topIndent - borderSize.getTop())); | |||||
| (float) (y + viewport->getViewPositionY() - topIndent - borderSize.getTop()) - i.getYOffset()); | |||||
| } | } | ||||
| void TextEditor::insertTextAtCaret (const String& t) | void TextEditor::insertTextAtCaret (const String& t) | ||||
| @@ -1576,8 +1648,19 @@ void TextEditor::drawContent (Graphics& g) | |||||
| { | { | ||||
| g.setOrigin (leftIndent, topIndent); | g.setOrigin (leftIndent, topIndent); | ||||
| auto clip = g.getClipBounds(); | auto clip = g.getClipBounds(); | ||||
| Colour selectedTextColour; | |||||
| auto yOffset = Iterator (*this).getYOffset(); | |||||
| AffineTransform transform; | |||||
| if (yOffset > 0) | |||||
| { | |||||
| transform = AffineTransform::translation (0.0f, yOffset); | |||||
| clip.setY (roundToInt ((float) clip.getY() - yOffset)); | |||||
| } | |||||
| Iterator i (*this); | Iterator i (*this); | ||||
| Colour selectedTextColour; | |||||
| if (! selection.isEmpty()) | if (! selection.isEmpty()) | ||||
| { | { | ||||
| @@ -1593,10 +1676,10 @@ void TextEditor::drawContent (Graphics& g) | |||||
| } | } | ||||
| } | } | ||||
| g.setColour (findColour (highlightColourId).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); | |||||
| g.fillRectList (selectionArea); | |||||
| selectedTextColour = findColour (highlightedTextColourId); | selectedTextColour = findColour (highlightedTextColourId); | ||||
| g.setColour (findColour (highlightColourId).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); | |||||
| g.fillPath (selectionArea.toPath(), transform); | |||||
| } | } | ||||
| const UniformTextSection* lastSection = nullptr; | const UniformTextSection* lastSection = nullptr; | ||||
| @@ -1607,12 +1690,12 @@ void TextEditor::drawContent (Graphics& g) | |||||
| { | { | ||||
| if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars })) | if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars })) | ||||
| { | { | ||||
| i.drawSelectedText (g, selection, selectedTextColour); | |||||
| i.drawSelectedText (g, selection, selectedTextColour, transform); | |||||
| lastSection = nullptr; | lastSection = nullptr; | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| i.draw (g, lastSection); | |||||
| i.draw (g, lastSection, transform); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1626,7 +1709,7 @@ void TextEditor::drawContent (Graphics& g) | |||||
| if (i2.lineY + i2.lineHeight >= (float) clip.getY() | if (i2.lineY + i2.lineHeight >= (float) clip.getY() | ||||
| && underlinedSection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars })) | && underlinedSection.intersects ({ i2.indexInText, i2.indexInText + i2.atom->numChars })) | ||||
| { | { | ||||
| i2.drawUnderline (g, underlinedSection, findColour (textColourId)); | |||||
| i2.drawUnderline (g, underlinedSection, findColour (textColourId), transform); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1647,13 +1730,10 @@ void TextEditor::paintOverChildren (Graphics& g) | |||||
| g.setColour (colourForTextWhenEmpty); | g.setColour (colourForTextWhenEmpty); | ||||
| g.setFont (getFont()); | g.setFont (getFont()); | ||||
| if (isMultiLine()) | |||||
| g.drawText (textToShowWhenEmpty, getLocalBounds(), | |||||
| Justification::centred, true); | |||||
| else | |||||
| g.drawText (textToShowWhenEmpty, | |||||
| leftIndent, 0, viewport->getWidth() - leftIndent, getHeight(), | |||||
| Justification::centredLeft, true); | |||||
| g.drawText (textToShowWhenEmpty, | |||||
| leftIndent, topIndent, | |||||
| viewport->getWidth() - leftIndent, getHeight() - topIndent, | |||||
| justification, true); | |||||
| } | } | ||||
| getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); | getLookAndFeel().drawTextEditorOutline (g, getWidth(), getHeight(), *this); | ||||
| @@ -2055,7 +2135,7 @@ bool TextEditor::keyStateChanged (const bool isKeyDown) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void TextEditor::focusGained (FocusChangeType) | |||||
| void TextEditor::focusGained (FocusChangeType cause) | |||||
| { | { | ||||
| newTransaction(); | newTransaction(); | ||||
| @@ -2067,6 +2147,9 @@ void TextEditor::focusGained (FocusChangeType) | |||||
| checkFocus(); | checkFocus(); | ||||
| if (cause == FocusChangeType::focusChangedByMouseClick && selectAllTextWhenFocused) | |||||
| wasFocused = false; | |||||
| repaint(); | repaint(); | ||||
| updateCaretPosition(); | updateCaretPosition(); | ||||
| } | } | ||||
| @@ -2095,7 +2178,7 @@ void TextEditor::resized() | |||||
| viewport->setBoundsInset (borderSize); | viewport->setBoundsInset (borderSize); | ||||
| viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight())); | viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight())); | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| if (isMultiLine()) | if (isMultiLine()) | ||||
| updateCaretPosition(); | updateCaretPosition(); | ||||
| @@ -2213,7 +2296,7 @@ void TextEditor::insert (const String& text, int insertIndex, const Font& font, | |||||
| totalNumChars = -1; | totalNumChars = -1; | ||||
| valueTextNeedsUpdating = true; | valueTextNeedsUpdating = true; | ||||
| updateTextHolderSize(); | |||||
| checkLayout(); | |||||
| moveCaretTo (caretPositionToMoveTo, false); | moveCaretTo (caretPositionToMoveTo, false); | ||||
| repaintText ({ insertIndex, getTotalNumChars() }); | repaintText ({ insertIndex, getTotalNumChars() }); | ||||
| @@ -2426,7 +2509,7 @@ void TextEditor::getCharPosition (int index, Point<float>& anchor, float& lineHe | |||||
| if (sections.isEmpty()) | if (sections.isEmpty()) | ||||
| { | { | ||||
| anchor = { i.getJustificationOffset (0), 0 }; | |||||
| anchor = { i.getJustificationOffsetX (0), 0 }; | |||||
| lineHeight = currentFont.getHeight(); | lineHeight = currentFont.getHeight(); | ||||
| } | } | ||||
| else | else | ||||
| @@ -155,7 +155,6 @@ public: | |||||
| */ | */ | ||||
| bool areScrollbarsShown() const noexcept { return scrollbarVisible; } | bool areScrollbarsShown() const noexcept { return scrollbarVisible; } | ||||
| /** Changes the password character used to disguise the text. | /** Changes the password character used to disguise the text. | ||||
| @param passwordCharacter if this is not zero, this character will be used as a replacement | @param passwordCharacter if this is not zero, this character will be used as a replacement | ||||
| @@ -172,7 +171,6 @@ public: | |||||
| */ | */ | ||||
| juce_wchar getPasswordCharacter() const noexcept { return passwordCharacter; } | juce_wchar getPasswordCharacter() const noexcept { return passwordCharacter; } | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Allows a right-click menu to appear for the editor. | /** Allows a right-click menu to appear for the editor. | ||||
| @@ -251,7 +249,7 @@ public: | |||||
| @see setFont | @see setFont | ||||
| */ | */ | ||||
| const Font& getFont() const noexcept { return currentFont; } | |||||
| const Font& getFont() const noexcept { return currentFont; } | |||||
| /** Applies a colour to all the text in the editor. | /** Applies a colour to all the text in the editor. | ||||
| @@ -260,6 +258,18 @@ public: | |||||
| */ | */ | ||||
| void applyColourToAllText (const Colour& newColour, bool changeCurrentTextColour = true); | void applyColourToAllText (const Colour& newColour, bool changeCurrentTextColour = true); | ||||
| /** Sets whether whitespace should be underlined when the editor font is underlined. | |||||
| @see isWhitespaceUnderlined | |||||
| */ | |||||
| void setWhitespaceUnderlined (bool shouldUnderlineWhitespace) noexcept { underlineWhitespace = shouldUnderlineWhitespace; } | |||||
| /** Returns true if whitespace is underlined for underlined fonts. | |||||
| @see setWhitespaceIsUnderlined | |||||
| */ | |||||
| bool isWhitespaceUnderlined() const noexcept { return underlineWhitespace; } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** If set to true, focusing on the editor will highlight all its text. | /** If set to true, focusing on the editor will highlight all its text. | ||||
| @@ -495,9 +505,12 @@ public: | |||||
| */ | */ | ||||
| void setScrollToShowCursor (bool shouldScrollToShowCaret); | void setScrollToShowCursor (bool shouldScrollToShowCaret); | ||||
| /** Modifies the horizontal justification of the text within the editor window. */ | |||||
| /** Modifies the justification of the text within the editor window. */ | |||||
| void setJustification (Justification newJustification); | void setJustification (Justification newJustification); | ||||
| /** Returns the type of justification, as set in setJustification(). */ | |||||
| Justification getJustificationType() const noexcept { return justification; } | |||||
| /** Sets the line spacing of the TextEditor. | /** Sets the line spacing of the TextEditor. | ||||
| The default (and minimum) value is 1.0 and values > 1.0 will increase the line spacing as a | The default (and minimum) value is 1.0 and values > 1.0 will increase the line spacing as a | ||||
| multiple of the line height e.g. for double-spacing call this method with an argument of 2.0. | multiple of the line height e.g. for double-spacing call this method with an argument of 2.0. | ||||
| @@ -712,7 +725,7 @@ private: | |||||
| std::unique_ptr<Viewport> viewport; | std::unique_ptr<Viewport> viewport; | ||||
| TextHolderComponent* textHolder; | TextHolderComponent* textHolder; | ||||
| BorderSize<int> borderSize { 1, 1, 1, 3 }; | BorderSize<int> borderSize { 1, 1, 1, 3 }; | ||||
| Justification justification { Justification::left }; | |||||
| Justification justification { Justification::topLeft }; | |||||
| bool readOnly = false; | bool readOnly = false; | ||||
| bool caretVisible = true; | bool caretVisible = true; | ||||
| @@ -728,6 +741,7 @@ private: | |||||
| bool menuActive = false; | bool menuActive = false; | ||||
| bool valueTextNeedsUpdating = false; | bool valueTextNeedsUpdating = false; | ||||
| bool consumeEscAndReturnKeys = true; | bool consumeEscAndReturnKeys = true; | ||||
| bool underlineWhitespace = true; | |||||
| UndoManager undoManager; | UndoManager undoManager; | ||||
| std::unique_ptr<CaretComponent> caret; | std::unique_ptr<CaretComponent> caret; | ||||
| @@ -778,9 +792,12 @@ private: | |||||
| int findWordBreakBefore (int position) const; | int findWordBreakBefore (int position) const; | ||||
| bool moveCaretWithTransaction (int newPos, bool selecting); | bool moveCaretWithTransaction (int newPos, bool selecting); | ||||
| void drawContent (Graphics&); | void drawContent (Graphics&); | ||||
| void updateTextHolderSize(); | |||||
| void checkLayout(); | |||||
| void updateTextHolderSize (int, int); | |||||
| void updateScrollbarVisibility (int, int); | |||||
| float getWordWrapWidth() const; | float getWordWrapWidth() const; | ||||
| float getJustificationWidth() const; | |||||
| float getMaximumWidth() const; | |||||
| float getMaximumHeight() const; | |||||
| void timerCallbackInt(); | void timerCallbackInt(); | ||||
| void checkFocus(); | void checkFocus(); | ||||
| void repaintText (Range<int>); | void repaintText (Range<int>); | ||||
| @@ -40,9 +40,7 @@ CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const pare | |||||
| else | else | ||||
| { | { | ||||
| setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); | setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); | ||||
| updatePosition (area, Desktop::getInstance().getDisplays().findDisplayForRect (area).userArea); | updatePosition (area, Desktop::getInstance().getDisplays().findDisplayForRect (area).userArea); | ||||
| addToDesktop (ComponentPeer::windowIsTemporary); | addToDesktop (ComponentPeer::windowIsTemporary); | ||||
| startTimer (100); | startTimer (100); | ||||
| @@ -51,15 +49,14 @@ CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const pare | |||||
| creationTime = Time::getCurrentTime(); | creationTime = Time::getCurrentTime(); | ||||
| } | } | ||||
| CallOutBox::~CallOutBox() = default; | |||||
| //============================================================================== | //============================================================================== | ||||
| class CallOutBoxCallback : public ModalComponentManager::Callback, | class CallOutBoxCallback : public ModalComponentManager::Callback, | ||||
| private Timer | private Timer | ||||
| { | { | ||||
| public: | public: | ||||
| CallOutBoxCallback (Component* c, const Rectangle<int>& area, Component* parent) | |||||
| : content (c), callout (*c, area, parent) | |||||
| CallOutBoxCallback (std::unique_ptr<Component> c, const Rectangle<int>& area, Component* parent) | |||||
| : content (std::move (c)), | |||||
| callout (*content, area, parent) | |||||
| { | { | ||||
| callout.setVisible (true); | callout.setVisible (true); | ||||
| callout.enterModalState (true, this); | callout.enterModalState (true, this); | ||||
| @@ -80,11 +77,11 @@ public: | |||||
| JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback) | JUCE_DECLARE_NON_COPYABLE (CallOutBoxCallback) | ||||
| }; | }; | ||||
| CallOutBox& CallOutBox::launchAsynchronously (Component* content, Rectangle<int> area, Component* parent) | |||||
| CallOutBox& CallOutBox::launchAsynchronously (std::unique_ptr<Component> content, Rectangle<int> area, Component* parent) | |||||
| { | { | ||||
| jassert (content != nullptr); // must be a valid content component! | jassert (content != nullptr); // must be a valid content component! | ||||
| return (new CallOutBoxCallback (content, area, parent))->callout; | |||||
| return (new CallOutBoxCallback (std::move (content), area, parent))->callout; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -99,7 +96,11 @@ int CallOutBox::getBorderSize() const noexcept | |||||
| return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize); | return jmax (getLookAndFeel().getCallOutBoxBorderSize (*this), (int) arrowSize); | ||||
| } | } | ||||
| void CallOutBox::lookAndFeelChanged() { resized(); repaint(); } | |||||
| void CallOutBox::lookAndFeelChanged() | |||||
| { | |||||
| resized(); | |||||
| repaint(); | |||||
| } | |||||
| void CallOutBox::paint (Graphics& g) | void CallOutBox::paint (Graphics& g) | ||||
| { | { | ||||
| @@ -158,7 +159,7 @@ void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept | |||||
| dismissalMouseClicksAreAlwaysConsumed = b; | dismissalMouseClicksAreAlwaysConsumed = b; | ||||
| } | } | ||||
| enum { callOutBoxDismissCommandId = 0x4f83a04b }; | |||||
| static constexpr int callOutBoxDismissCommandId = 0x4f83a04b; | |||||
| void CallOutBox::handleCommandMessage (int commandId) | void CallOutBox::handleCommandMessage (int commandId) | ||||
| { | { | ||||
| @@ -193,9 +194,8 @@ void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const R | |||||
| availableArea = newAreaToFitIn; | availableArea = newAreaToFitIn; | ||||
| auto borderSpace = getBorderSize(); | auto borderSpace = getBorderSize(); | ||||
| Rectangle<int> newBounds (content.getWidth() + borderSpace * 2, | |||||
| content.getHeight() + borderSpace * 2); | |||||
| auto newBounds = getLocalArea (&content, Rectangle<int> (content.getWidth() + borderSpace * 2, | |||||
| content.getHeight() + borderSpace * 2)); | |||||
| auto hw = newBounds.getWidth() / 2; | auto hw = newBounds.getWidth() / 2; | ||||
| auto hh = newBounds.getHeight() / 2; | auto hh = newBounds.getHeight() / 2; | ||||
| @@ -250,7 +250,7 @@ void CallOutBox::refreshPath() | |||||
| const float gap = 4.5f; | const float gap = 4.5f; | ||||
| outline.addBubble (content.getBounds().toFloat().expanded (gap, gap), | |||||
| outline.addBubble (getLocalArea (&content, content.getLocalBounds().toFloat()).expanded (gap, gap), | |||||
| getLocalBounds().toFloat(), | getLocalBounds().toFloat(), | ||||
| targetPoint - getPosition().toFloat(), | targetPoint - getPosition().toFloat(), | ||||
| getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f); | getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f); | ||||
| @@ -43,11 +43,12 @@ namespace juce | |||||
| @code | @code | ||||
| void mouseUp (const MouseEvent&) | void mouseUp (const MouseEvent&) | ||||
| { | { | ||||
| FoobarContentComp* content = new FoobarContentComp(); | |||||
| auto content = std::make_unique<FoobarContentComp>(); | |||||
| content->setSize (300, 300); | content->setSize (300, 300); | ||||
| CallOutBox& myBox | |||||
| = CallOutBox::launchAsynchronously (content, getScreenBounds(), nullptr); | |||||
| auto& myBox = CallOutBox::launchAsynchronously (std::move (content), | |||||
| getScreenBounds(), | |||||
| nullptr); | |||||
| } | } | ||||
| @endcode | @endcode | ||||
| @@ -77,9 +78,6 @@ public: | |||||
| Rectangle<int> areaToPointTo, | Rectangle<int> areaToPointTo, | ||||
| Component* parentComponent); | Component* parentComponent); | ||||
| /** Destructor. */ | |||||
| ~CallOutBox() override; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Changes the base width of the arrow. */ | /** Changes the base width of the arrow. */ | ||||
| void setArrowSize (float newSize); | void setArrowSize (float newSize); | ||||
| @@ -109,15 +107,13 @@ public: | |||||
| @param contentComponent the component to display inside the call-out. This should | @param contentComponent the component to display inside the call-out. This should | ||||
| already have a size set (although the call-out will also | already have a size set (although the call-out will also | ||||
| update itself when the component's size is changed later). | update itself when the component's size is changed later). | ||||
| This component will be owned by the callout box and deleted | |||||
| later when the box is dismissed. | |||||
| @param areaToPointTo the area that the call-out's arrow should point towards. If | @param areaToPointTo the area that the call-out's arrow should point towards. If | ||||
| a parentComponent is supplied, then this is relative to that | a parentComponent is supplied, then this is relative to that | ||||
| parent; otherwise, it's a global screen coord. | parent; otherwise, it's a global screen coord. | ||||
| @param parentComponent if not a nullptr, this is the component to add the call-out to. | @param parentComponent if not a nullptr, this is the component to add the call-out to. | ||||
| If this is a nullptr, the call-out will be added to the desktop. | If this is a nullptr, the call-out will be added to the desktop. | ||||
| */ | */ | ||||
| static CallOutBox& launchAsynchronously (Component* contentComponent, | |||||
| static CallOutBox& launchAsynchronously (std::unique_ptr<Component> contentComponent, | |||||
| Rectangle<int> areaToPointTo, | Rectangle<int> areaToPointTo, | ||||
| Component* parentComponent); | Component* parentComponent); | ||||
| @@ -402,6 +402,16 @@ Rectangle<int> ComponentPeer::globalToLocal (const Rectangle<int>& screenPositio | |||||
| return screenPosition.withPosition (globalToLocal (screenPosition.getPosition())); | return screenPosition.withPosition (globalToLocal (screenPosition.getPosition())); | ||||
| } | } | ||||
| Rectangle<float> ComponentPeer::localToGlobal (const Rectangle<float>& relativePosition) | |||||
| { | |||||
| return relativePosition.withPosition (localToGlobal (relativePosition.getPosition())); | |||||
| } | |||||
| Rectangle<float> ComponentPeer::globalToLocal (const Rectangle<float>& screenPosition) | |||||
| { | |||||
| return screenPosition.withPosition (globalToLocal (screenPosition.getPosition())); | |||||
| } | |||||
| Rectangle<int> ComponentPeer::getAreaCoveredBy (Component& subComponent) const | Rectangle<int> ComponentPeer::getAreaCoveredBy (Component& subComponent) const | ||||
| { | { | ||||
| return ScalingHelpers::scaledScreenPosToUnscaled | return ScalingHelpers::scaledScreenPosToUnscaled | ||||