| @@ -122,10 +122,35 @@ public: | |||
| 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) | |||
| { | |||
| *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; | |||
| } | |||
| } | |||
| @@ -43,7 +43,7 @@ void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| *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; | |||
| } | |||
| } | |||
| @@ -68,7 +68,7 @@ void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| *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; | |||
| } | |||
| } | |||
| @@ -143,7 +143,7 @@ void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| *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; | |||
| } | |||
| } | |||
| @@ -168,7 +168,7 @@ void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| *reinterpret_cast<float*> (d) = source[i]; | |||
| *unalignedPointerCast<float*> (d) = source[i]; | |||
| #if JUCE_BIG_ENDIAN | |||
| *reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||
| *unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||
| #endif | |||
| d += destBytesPerSample; | |||
| @@ -199,10 +199,10 @@ void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* de | |||
| for (int i = 0; i < numSamples; ++i) | |||
| { | |||
| *reinterpret_cast<float*> (d) = source[i]; | |||
| *unalignedPointerCast<float*> (d) = source[i]; | |||
| #if JUCE_LITTLE_ENDIAN | |||
| *reinterpret_cast<uint32*> (d) = ByteOrder::swap (*reinterpret_cast<uint32*> (d)); | |||
| *unalignedPointerCast<uint32*> (d) = ByteOrder::swap (*unalignedPointerCast<uint32*> (d)); | |||
| #endif | |||
| d += destBytesPerSample; | |||
| @@ -219,7 +219,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest | |||
| { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -230,7 +230,7 @@ void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint16*> (intData)); | |||
| dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint16*> (intData)); | |||
| intData += srcBytesPerSample; | |||
| } | |||
| } | |||
| @@ -255,7 +255,7 @@ void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*reinterpret_cast<const uint32*> (intData)); | |||
| dest[i] = scale * (float) ByteOrder::swapIfBigEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
| intData += srcBytesPerSample; | |||
| } | |||
| } | |||
| @@ -330,7 +330,7 @@ void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*reinterpret_cast<const uint32*> (intData)); | |||
| dest[i] = scale * (float) ByteOrder::swapIfLittleEndian (*unalignedPointerCast<const uint32*> (intData)); | |||
| intData += srcBytesPerSample; | |||
| } | |||
| } | |||
| @@ -355,7 +355,7 @@ void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest | |||
| for (int i = numSamples; --i >= 0;) | |||
| { | |||
| 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) | |||
| { | |||
| dest[i] = *reinterpret_cast<const float*> (s); | |||
| dest[i] = *unalignedPointerCast<const float*> (s); | |||
| #if JUCE_BIG_ENDIAN | |||
| auto d = reinterpret_cast<uint32*> (dest + i); | |||
| auto d = unalignedPointerCast<uint32*> (dest + i); | |||
| *d = ByteOrder::swap (*d); | |||
| #endif | |||
| @@ -383,10 +383,10 @@ void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* de | |||
| for (int i = 0; i < numSamples; ++i) | |||
| { | |||
| dest[i] = *reinterpret_cast<const float*> (s); | |||
| dest[i] = *unalignedPointerCast<const float*> (s); | |||
| #if JUCE_LITTLE_ENDIAN | |||
| auto d = reinterpret_cast<uint32*> (dest + i); | |||
| auto d = unalignedPointerCast<uint32*> (dest + i); | |||
| *d = ByteOrder::swap (*d); | |||
| #endif | |||
| @@ -409,8 +409,8 @@ public: | |||
| 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) | |||
| { | |||
| @@ -442,10 +442,10 @@ public: | |||
| { | |||
| allocatedBytes = newTotalBytes; | |||
| 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) | |||
| { | |||
| @@ -1127,7 +1127,7 @@ private: | |||
| 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, | |||
| "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); | |||
| #endif | |||
| @@ -1142,8 +1142,8 @@ private: | |||
| allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; | |||
| 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) | |||
| { | |||
| @@ -1167,7 +1167,7 @@ private: | |||
| else | |||
| { | |||
| allocatedData.malloc (numChannels + 1, sizeof (Type*)); | |||
| channels = reinterpret_cast<Type**> (allocatedData.get()); | |||
| channels = unalignedPointerCast<Type**> (allocatedData.get()); | |||
| } | |||
| for (int i = 0; i < numChannels; ++i) | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_audio_basics | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE audio and MIDI data classes | |||
| description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -136,7 +136,7 @@ public: | |||
| //============================================================================== | |||
| /** Receives events from a MidiKeyboardState object. */ | |||
| class Listener | |||
| class JUCE_API Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| @@ -401,7 +401,7 @@ public: | |||
| /** Returns true if the message is an aftertouch event. | |||
| 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. | |||
| @see getAftertouchValue, getNoteNumber | |||
| @@ -177,8 +177,9 @@ static void addIfNotNull (OwnedArray<AudioIODeviceType>& list, AudioIODeviceType | |||
| 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_ASIO()); | |||
| addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_CoreAudio()); | |||
| @@ -544,6 +545,8 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup | |||
| else if (currentAudioDevice != nullptr) | |||
| return {}; | |||
| stopDevice(); | |||
| if (getCurrentDeviceTypeObject() == nullptr | |||
| || (newSetup.inputDeviceName.isEmpty() && newSetup.outputDeviceName.isEmpty())) | |||
| { | |||
| @@ -555,8 +558,6 @@ String AudioDeviceManager::setAudioDeviceSetup (const AudioDeviceSetup& newSetup | |||
| return {}; | |||
| } | |||
| stopDevice(); | |||
| String error; | |||
| 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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| #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 | |||
| } // namespace juce | |||
| @@ -149,8 +149,8 @@ public: | |||
| static AudioIODeviceType* createAudioIODeviceType_CoreAudio(); | |||
| /** Creates an iOS device type if it's available on this platform, or returns null. */ | |||
| 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. */ | |||
| static AudioIODeviceType* createAudioIODeviceType_DirectSound(); | |||
| /** 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. */ | |||
| 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: | |||
| explicit AudioIODeviceType (const String& typeName); | |||
| @@ -45,6 +45,8 @@ | |||
| #include "juce_audio_devices.h" | |||
| #include "native/juce_MidiDataConcatenator.h" | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| #define Point CarbonDummyPointName | |||
| @@ -55,6 +57,9 @@ | |||
| #undef Point | |||
| #undef Component | |||
| #include "native/juce_mac_CoreAudio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| #elif JUCE_IOS | |||
| #import <AudioToolbox/AudioToolbox.h> | |||
| #import <AVFoundation/AVFoundation.h> | |||
| @@ -64,13 +69,21 @@ | |||
| #import <CoreMIDI/MIDINetworkSession.h> | |||
| #endif | |||
| #include "native/juce_ios_Audio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #if JUCE_WASAPI | |||
| #include <mmreg.h> | |||
| #include "native/juce_win32_WASAPI.cpp" | |||
| #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 | |||
| 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 | |||
| @@ -93,6 +106,8 @@ | |||
| JUCE_END_IGNORE_WARNINGS_MSVC | |||
| #endif | |||
| #include "native/juce_win32_Midi.cpp" | |||
| #if JUCE_ASIO | |||
| /* 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 | |||
| @@ -114,6 +129,7 @@ | |||
| needed - so to simplify things, you could just copy these into your JUCE directory). | |||
| */ | |||
| #include <iasiodrv.h> | |||
| #include "native/juce_win32_ASIO.cpp" | |||
| #endif | |||
| //============================================================================== | |||
| @@ -128,6 +144,7 @@ | |||
| just set the JUCE_ALSA flag to 0. | |||
| */ | |||
| #include <alsa/asoundlib.h> | |||
| #include "native/juce_linux_ALSA.cpp" | |||
| #endif | |||
| #if JUCE_JACK | |||
| @@ -140,6 +157,7 @@ | |||
| JUCE with low latency audio support, just set the JUCE_JACK flag to 0. | |||
| */ | |||
| #include <jack/jack.h> | |||
| #include "native/juce_linux_JackAudio.cpp" | |||
| #endif | |||
| #if JUCE_BELA | |||
| @@ -149,89 +167,18 @@ | |||
| */ | |||
| #include <Bela.h> | |||
| #include <Midi.h> | |||
| #include "native/juce_linux_Bela.cpp" | |||
| #endif | |||
| #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" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #include "native/juce_android_Audio.cpp" | |||
| #include "native/juce_android_Midi.cpp" | |||
| @@ -239,10 +186,25 @@ | |||
| #include "native/juce_android_HighPerformanceAudioHelpers.h" | |||
| #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" | |||
| #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 | |||
| #include "native/juce_android_Oboe.cpp" | |||
| #endif | |||
| #endif | |||
| @@ -259,3 +221,11 @@ namespace juce | |||
| bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; } | |||
| } | |||
| #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 | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE audio and MIDI I/O device classes | |||
| description: Classes to play and record from audio and MIDI I/O devices | |||
| website: http://www.juce.com/juce | |||
| @@ -88,21 +88,12 @@ | |||
| #endif | |||
| /** 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 | |||
| #define JUCE_WASAPI 1 | |||
| #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 | |||
| Enables DirectSound audio (MS Windows only). | |||
| */ | |||
| @@ -174,6 +165,22 @@ | |||
| //============================================================================== | |||
| #include "midi_io/juce_MidiDevices.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_AudioIODeviceType.h" | |||
| #include "audio_io/juce_SystemAudioVolume.h" | |||
| @@ -164,12 +164,16 @@ public: | |||
| /** Deprecated. */ | |||
| static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*); | |||
| /** @internal */ | |||
| class Pimpl; | |||
| private: | |||
| //============================================================================== | |||
| explicit MidiInput (const String&, const String&); | |||
| MidiDeviceInfo deviceInfo; | |||
| void* internal = nullptr; | |||
| std::unique_ptr<Pimpl> internal; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput) | |||
| }; | |||
| @@ -350,6 +354,9 @@ public: | |||
| /** Deprecated. */ | |||
| static std::unique_ptr<MidiOutput> openDevice (int); | |||
| /** @internal */ | |||
| class Pimpl; | |||
| private: | |||
| //============================================================================== | |||
| struct PendingMessage | |||
| @@ -368,7 +375,9 @@ private: | |||
| void run() override; | |||
| MidiDeviceInfo deviceInfo; | |||
| void* internal = nullptr; | |||
| std::unique_ptr<Pimpl> internal; | |||
| CriticalSection lock; | |||
| PendingMessage* firstMessage = nullptr; | |||
| @@ -478,19 +478,4 @@ private: | |||
| extern bool isOboeAvailable(); | |||
| 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 | |||
| @@ -350,17 +350,17 @@ DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$Juc | |||
| #undef JNI_CLASS_MEMBERS | |||
| //============================================================================== | |||
| class AndroidMidiInput | |||
| class MidiInput::Pimpl | |||
| { | |||
| 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), | |||
| javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID, | |||
| (jint) deviceID, (jlong) this))) | |||
| { | |||
| } | |||
| ~AndroidMidiInput() | |||
| ~Pimpl() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| { | |||
| @@ -416,7 +416,7 @@ public: | |||
| static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray, | |||
| jint offset, jint len, jlong timestamp) | |||
| { | |||
| auto* myself = reinterpret_cast<AndroidMidiInput*> (host); | |||
| auto* myself = reinterpret_cast<Pimpl*> (host); | |||
| myself->handleMidi (byteArray, offset, len, timestamp); | |||
| } | |||
| @@ -429,15 +429,15 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| class AndroidMidiOutput | |||
| class MidiOutput::Pimpl | |||
| { | |||
| public: | |||
| AndroidMidiOutput (const LocalRef<jobject>& midiDevice) | |||
| Pimpl (const LocalRef<jobject>& midiDevice) | |||
| : javaMidiDevice (midiDevice) | |||
| { | |||
| } | |||
| ~AndroidMidiOutput() | |||
| ~Pimpl() | |||
| { | |||
| if (jobject d = javaMidiDevice.get()) | |||
| { | |||
| @@ -468,7 +468,7 @@ private: | |||
| //============================================================================== | |||
| #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) | |||
| #undef JNI_CLASS_MEMBERS | |||
| @@ -509,11 +509,11 @@ public: | |||
| 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()) | |||
| { | |||
| 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()) | |||
| return androidMidiInput.release(); | |||
| @@ -522,11 +522,11 @@ public: | |||
| return nullptr; | |||
| } | |||
| AndroidMidiOutput* openMidiOutputPortWithID (int deviceID) | |||
| MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID) | |||
| { | |||
| if (auto dm = deviceManager.get()) | |||
| 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; | |||
| } | |||
| @@ -564,7 +564,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback)) | |||
| { | |||
| midiInput->internal = port; | |||
| midiInput->internal.reset (port); | |||
| midiInput->setName (port->getName()); | |||
| 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() | |||
| { | |||
| if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||
| if (auto* mi = internal.get()) | |||
| mi->start(); | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| if (auto* mi = reinterpret_cast<AndroidMidiInput*> (internal)) | |||
| if (auto* mi = internal.get()) | |||
| mi->stop(); | |||
| } | |||
| @@ -646,7 +643,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||
| if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue())) | |||
| { | |||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier)); | |||
| midiOutput->internal = port; | |||
| midiOutput->internal.reset (port); | |||
| midiOutput->setName (port->getName()); | |||
| return midiOutput; | |||
| @@ -681,13 +678,11 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| delete reinterpret_cast<AndroidMidiOutput*> (internal); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| if (auto* androidMidi = reinterpret_cast<AndroidMidiOutput*>(internal)) | |||
| if (auto* androidMidi = internal.get()) | |||
| { | |||
| auto* env = getEnv(); | |||
| auto messageSize = message.getRawDataSize(); | |||
| @@ -1211,9 +1211,13 @@ public: | |||
| jmethodID getChannelCountsMethod = env->GetMethodID (deviceClass, "getChannelCounts", "()[I"); | |||
| 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 sampleRates = jintArrayToJuceArray (jSampleRates); | |||
| @@ -1222,42 +1226,42 @@ public: | |||
| auto channelCounts = jintArrayToJuceArray (jChannelCounts); | |||
| int numChannels = channelCounts.isEmpty() ? -1 : channelCounts.getLast(); | |||
| bool isInput = env->CallBooleanMethod (device, isSourceMethod); | |||
| auto isInput = env->CallBooleanMethod (device, isSourceMethod); | |||
| auto& devices = isInput ? inputDevices : outputDevices; | |||
| devices.add ({ name, id, sampleRates, numChannels }); | |||
| } | |||
| static const char* deviceTypeToString (int type) | |||
| static String deviceTypeToString (int 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"; | |||
| //============================================================================== | |||
| bool isOboeAvailable() { return OboeAudioIODeviceType::isOboeAvailable(); } | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() | |||
| { | |||
| return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr; | |||
| } | |||
| //============================================================================== | |||
| class OboeRealtimeThread : private oboe::AudioStreamCallback | |||
| { | |||
| @@ -71,12 +71,11 @@ static void destroyObject (SLObjectType 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 (SLObjectItf o) : ptr (o) {} | |||
| std::unique_ptr<const SLObjectItf_* const> ptr; | |||
| std::unique_ptr<const SLObjectItf_* const, SLObjectItfFree> ptr; | |||
| }; | |||
| ReferenceCountedObjectPtr<ControlBlock> cb; | |||
| @@ -1121,11 +1120,6 @@ const char* const OpenSLAudioIODevice::openSLTypeName = "Android OpenSL"; | |||
| //============================================================================== | |||
| bool isOpenSLAvailable() { return OpenSLAudioDeviceType::isOpenSLAvailable(); } | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() | |||
| { | |||
| return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr; | |||
| } | |||
| //============================================================================== | |||
| class SLRealtimeThread | |||
| { | |||
| @@ -1431,12 +1431,6 @@ void iOSAudioIODeviceType::handleAsyncUpdate() | |||
| callDeviceChangeListeners(); | |||
| } | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() | |||
| { | |||
| return new iOSAudioIODeviceType(); | |||
| } | |||
| //============================================================================== | |||
| AudioSessionHolder::AudioSessionHolder() { nativeSession = [[iOSAudioSessionNative alloc] init: this]; } | |||
| AudioSessionHolder::~AudioSessionHolder() { [nativeSession release]; } | |||
| @@ -1299,9 +1299,4 @@ AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices() | |||
| return new ALSAAudioIODeviceType (false, "ALSA"); | |||
| } | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() | |||
| { | |||
| return createAudioIODeviceType_ALSA_PCMDevices(); | |||
| } | |||
| } // namespace juce | |||
| @@ -24,12 +24,12 @@ namespace juce | |||
| { | |||
| //============================================================================== | |||
| class BelaMidiInput | |||
| class MidiInput::Pimpl | |||
| { | |||
| 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) | |||
| { | |||
| jassert (midiCallback != nullptr); | |||
| @@ -38,7 +38,7 @@ public: | |||
| buffer.resize (32); | |||
| } | |||
| ~BelaMidiInput() | |||
| ~Pimpl() | |||
| { | |||
| stop(); | |||
| midiInputs.removeAllInstancesOf (this); | |||
| @@ -76,7 +76,7 @@ public: | |||
| } | |||
| if (receivedBytes > 0) | |||
| pushMidiData (receivedBytes); | |||
| pushMidiData ((int) receivedBytes); | |||
| } | |||
| static Array<MidiDeviceInfo> getDevices (bool input) | |||
| @@ -141,7 +141,7 @@ private: | |||
| snd_rawmidi_info_t* 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_STREAM_OUTPUT); | |||
| @@ -173,10 +173,10 @@ private: | |||
| Midi midi; | |||
| 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; } | |||
| //============================================================================== | |||
| int getCurrentBufferSizeSamples() override { return actualBufferSize; } | |||
| int getCurrentBufferSizeSamples() override { return (int) actualBufferSize; } | |||
| double getCurrentSampleRate() override { return 44100.0; } | |||
| int getCurrentBitDepth() override { return 16; } | |||
| BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; } | |||
| @@ -384,8 +384,8 @@ private: | |||
| bool setup (BelaContext& context) | |||
| { | |||
| 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; | |||
| firstCallback = true; | |||
| @@ -405,7 +405,7 @@ private: | |||
| ScopedLock lock (callbackLock); | |||
| // Check for and process and midi | |||
| for (auto midiInput : BelaMidiInput::midiInputs) | |||
| for (auto midiInput : MidiInput::Pimpl::midiInputs) | |||
| midiInput->poll(); | |||
| if (callback != nullptr) | |||
| @@ -413,27 +413,29 @@ private: | |||
| jassert (context.audioFrames <= actualBufferSize); | |||
| jassert ((context.flags & BELA_FLAG_INTERLEAVED) == 0); | |||
| using Frames = decltype (context.audioFrames); | |||
| // Setup channelInBuffers | |||
| for (int ch = 0; ch < actualNumberOfInputs; ++ch) | |||
| { | |||
| if (ch < analogChannelStart) | |||
| channelInBuffer[ch] = &context.audioIn[ch * context.audioFrames]; | |||
| channelInBuffer[ch] = &context.audioIn[(Frames) ch * context.audioFrames]; | |||
| else | |||
| channelInBuffer[ch] = &context.analogIn[(ch - analogChannelStart) * context.analogFrames]; | |||
| channelInBuffer[ch] = &context.analogIn[(Frames) (ch - analogChannelStart) * context.analogFrames]; | |||
| } | |||
| // Setup channelOutBuffers | |||
| for (int ch = 0; ch < actualNumberOfOutputs; ++ch) | |||
| { | |||
| if (ch < analogChannelStart) | |||
| channelOutBuffer[ch] = &context.audioOut[ch * context.audioFrames]; | |||
| channelOutBuffer[ch] = &context.audioOut[(Frames) ch * context.audioFrames]; | |||
| else | |||
| channelOutBuffer[ch] = &context.analogOut[(ch - analogChannelStart) * context.audioFrames]; | |||
| channelOutBuffer[ch] = &context.analogOut[(Frames) (ch - analogChannelStart) * context.audioFrames]; | |||
| } | |||
| callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs, | |||
| channelOutBuffer.getData(), actualNumberOfOutputs, | |||
| context.audioFrames); | |||
| (int) context.audioFrames); | |||
| } | |||
| } | |||
| @@ -521,25 +523,19 @@ struct BelaAudioIODeviceType : public AudioIODeviceType | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType) | |||
| }; | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() | |||
| { | |||
| return new BelaAudioIODeviceType(); | |||
| } | |||
| //============================================================================== | |||
| MidiInput::MidiInput (const String& deviceName, const String& 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() | |||
| { | |||
| return BelaMidiInput::getDevices (true); | |||
| return Pimpl::getDevices (true); | |||
| } | |||
| MidiDeviceInfo MidiInput::getDefaultDevice() | |||
| @@ -553,7 +549,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| return {}; | |||
| 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; | |||
| } | |||
| @@ -587,7 +583,8 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* | |||
| //============================================================================== | |||
| // TODO: Add Bela MidiOutput support | |||
| MidiOutput::~MidiOutput() {} | |||
| class MidiOutput::Pimpl {}; | |||
| MidiOutput::~MidiOutput() = default; | |||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { 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) \ | |||
| return_type fn_name argument_types \ | |||
| { \ | |||
| using ReturnType = return_type; \ | |||
| typedef return_type (*fn_type) argument_types; \ | |||
| 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) \ | |||
| @@ -46,30 +48,35 @@ static void* juce_loadJackFunction (const char* const name) | |||
| { \ | |||
| typedef void (*fn_type) argument_types; \ | |||
| static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \ | |||
| jassert (fn != nullptr); \ | |||
| 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 | |||
| #define JACK_LOGGING_ENABLED 1 | |||
| @@ -115,56 +122,56 @@ namespace | |||
| struct JackPortIterator | |||
| { | |||
| JackPortIterator (jack_client_t* const client, const bool forInput) | |||
| : ports (nullptr), index (-1) | |||
| { | |||
| 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() | |||
| { | |||
| if (ports == nullptr || ports [index + 1] == nullptr) | |||
| if (ports == nullptr || ports.get()[index + 1] == nullptr) | |||
| return false; | |||
| name = CharPointer_UTF8 (ports[++index]); | |||
| clientName = name.upToFirstOccurrenceOf (":", false, false); | |||
| name = CharPointer_UTF8 (ports.get()[++index]); | |||
| 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 clientName; | |||
| }; | |||
| class JackAudioIODeviceType; | |||
| static Array<JackAudioIODeviceType*> activeDeviceTypes; | |||
| //============================================================================== | |||
| class JackAudioIODevice : public AudioIODevice | |||
| { | |||
| 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); | |||
| if (client == nullptr) | |||
| @@ -179,10 +186,10 @@ public: | |||
| const StringArray inputChannels (getInputChannelNames()); | |||
| 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)); | |||
| } | |||
| @@ -190,10 +197,10 @@ public: | |||
| const StringArray outputChannels (getOutputChannelNames()); | |||
| 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)); | |||
| } | |||
| @@ -202,7 +209,7 @@ public: | |||
| } | |||
| } | |||
| ~JackAudioIODevice() | |||
| ~JackAudioIODevice() override | |||
| { | |||
| close(); | |||
| if (client != nullptr) | |||
| @@ -212,19 +219,19 @@ public: | |||
| } | |||
| } | |||
| StringArray getChannelNames (bool forInput) const | |||
| StringArray getChannelNames (const String& clientName, bool forInput) const | |||
| { | |||
| StringArray names; | |||
| 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; | |||
| } | |||
| 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 | |||
| { | |||
| @@ -241,15 +248,29 @@ public: | |||
| Array<int> sizes; | |||
| if (client != nullptr) | |||
| sizes.add (juce::jack_get_buffer_size (client)); | |||
| sizes.add (static_cast<int> (juce::jack_get_buffer_size (client))); | |||
| return sizes; | |||
| } | |||
| 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, | |||
| double /* sampleRate */, int /* bufferSizeSamples */) override | |||
| @@ -263,38 +284,55 @@ public: | |||
| lastError.clear(); | |||
| close(); | |||
| xruns = 0; | |||
| xruns.store (0, std::memory_order_relaxed); | |||
| juce::jack_set_process_callback (client, processCallback, this); | |||
| juce::jack_set_port_connect_callback (client, portConnectCallback, 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_activate (client); | |||
| deviceIsOpen = true; | |||
| 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()) | |||
| { | |||
| 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(); | |||
| @@ -308,12 +346,15 @@ public: | |||
| 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_process_callback (client, processCallback, nullptr); | |||
| juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr); | |||
| juce::jack_on_shutdown (client, shutdownCallback, nullptr); | |||
| juce::jack_on_info_shutdown (client, infoShutdownCallback, nullptr); | |||
| } | |||
| deviceIsOpen = false; | |||
| @@ -347,7 +388,7 @@ public: | |||
| bool isPlaying() override { return callback != nullptr; } | |||
| int getCurrentBitDepth() override { return 32; } | |||
| 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 getActiveInputChannels() const override { return activeInputChannels; } | |||
| @@ -357,7 +398,7 @@ public: | |||
| int latency = 0; | |||
| 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; | |||
| } | |||
| @@ -367,14 +408,36 @@ public: | |||
| int latency = 0; | |||
| 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; | |||
| } | |||
| String inputId, outputId; | |||
| String inputName, outputName; | |||
| 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) | |||
| { | |||
| int numActiveInChans = 0, numActiveOutChans = 0; | |||
| @@ -382,17 +445,17 @@ private: | |||
| for (int i = 0; i < totalNumberOfInputChannels; ++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) | |||
| { | |||
| 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); | |||
| @@ -406,14 +469,14 @@ private: | |||
| else | |||
| { | |||
| 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) | |||
| { | |||
| if (callbackArgument != nullptr) | |||
| ((JackAudioIODevice*) callbackArgument)->process (nframes); | |||
| ((JackAudioIODevice*) callbackArgument)->process (static_cast<int> (nframes)); | |||
| return 0; | |||
| } | |||
| @@ -431,11 +494,11 @@ private: | |||
| BigInteger newOutputChannels, newInputChannels; | |||
| 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); | |||
| 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); | |||
| if (newOutputChannels != activeOutputChannels | |||
| @@ -451,14 +514,15 @@ private: | |||
| if (oldCallback != nullptr) | |||
| start (oldCallback); | |||
| sendDeviceChangedCallback(); | |||
| if (notifyChannelsChanged != nullptr) | |||
| notifyChannelsChanged(); | |||
| } | |||
| } | |||
| static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | |||
| { | |||
| if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg)) | |||
| device->updateActivePorts(); | |||
| device->mainThreadDispatcher.updateActivePorts(); | |||
| } | |||
| 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) | |||
| { | |||
| 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; | |||
| AudioIODeviceCallback* callback; | |||
| AudioIODeviceCallback* callback = nullptr; | |||
| CriticalSection callbackLock; | |||
| 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; | |||
| int xruns; | |||
| }; | |||
| std::atomic<int> xruns { 0 }; | |||
| std::function<void()> notifyChannelsChanged; | |||
| MainThreadDispatcher mainThreadDispatcher { *this }; | |||
| }; | |||
| //============================================================================== | |||
| class JackAudioIODeviceType; | |||
| class JackAudioIODeviceType : public AudioIODeviceType | |||
| { | |||
| public: | |||
| JackAudioIODeviceType() | |||
| : AudioIODeviceType ("JACK"), | |||
| hasScanned (false) | |||
| { | |||
| activeDeviceTypes.add (this); | |||
| } | |||
| ~JackAudioIODeviceType() | |||
| { | |||
| activeDeviceTypes.removeFirstMatchingValue (this); | |||
| } | |||
| : AudioIODeviceType ("JACK") | |||
| {} | |||
| void scanForDevices() | |||
| { | |||
| hasScanned = true; | |||
| inputNames.clear(); | |||
| inputIds.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", RTLD_LAZY); | |||
| if (juce_libjackHandle == nullptr) return; | |||
| jack_status_t status; | |||
| jack_status_t status = {}; | |||
| // 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 | |||
| 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 | |||
| 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); | |||
| } | |||
| @@ -580,8 +639,8 @@ public: | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| 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; | |||
| } | |||
| @@ -595,34 +654,17 @@ public: | |||
| const int outputIndex = outputNames.indexOf (outputDeviceName); | |||
| 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; | |||
| } | |||
| void portConnectionChange() { callDeviceChangeListeners(); } | |||
| private: | |||
| StringArray inputNames, outputNames, inputIds, outputIds; | |||
| bool hasScanned; | |||
| StringArray inputNames, outputNames; | |||
| bool hasScanned = false; | |||
| 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 | |||
| @@ -25,10 +25,6 @@ namespace juce | |||
| #if JUCE_ALSA | |||
| //============================================================================== | |||
| namespace | |||
| { | |||
| //============================================================================== | |||
| class AlsaClient : public ReferenceCountedObject | |||
| { | |||
| @@ -453,9 +449,23 @@ static AlsaClient::Port* iterateMidiDevices (bool forInput, | |||
| 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> devices; | |||
| @@ -485,7 +495,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (port->portName, deviceIdentifier)); | |||
| port->setupInput (midiInput.get(), callback); | |||
| midiInput->internal = port; | |||
| midiInput->internal = std::make_unique<Pimpl> (port); | |||
| 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))); | |||
| port->setupInput (midiInput.get(), callback); | |||
| midiInput->internal = port; | |||
| midiInput->internal = std::make_unique<Pimpl> (port); | |||
| return midiInput; | |||
| } | |||
| @@ -536,20 +546,25 @@ MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier) | |||
| MidiInput::~MidiInput() | |||
| { | |||
| stop(); | |||
| AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||
| } | |||
| void MidiInput::start() | |||
| { | |||
| static_cast<AlsaClient::Port*> (internal)->enableCallback (true); | |||
| internal->ptr->enableCallback (true); | |||
| } | |||
| 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> devices; | |||
| @@ -577,7 +592,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||
| std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->portName, deviceIdentifier)); | |||
| port->setupOutput(); | |||
| midiOutput->internal = port; | |||
| midiOutput->internal = std::make_unique<Pimpl> (port); | |||
| 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))); | |||
| port->setupOutput(); | |||
| midiOutput->internal = port; | |||
| midiOutput->internal = std::make_unique<Pimpl> (port); | |||
| return midiOutput; | |||
| } | |||
| @@ -623,17 +638,18 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| AlsaClient::getInstance()->deletePort (static_cast<AlsaClient::Port*> (internal)); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| static_cast<AlsaClient::Port*> (internal)->sendMessageNow (message); | |||
| internal->ptr->sendMessageNow (message); | |||
| } | |||
| //============================================================================== | |||
| #else | |||
| class MidiInput::Pimpl {}; | |||
| // (These are just stub functions if ALSA is unavailable...) | |||
| MidiInput::MidiInput (const String& deviceName, const String& deviceID) | |||
| : deviceInfo (deviceName, deviceID) | |||
| @@ -651,6 +667,8 @@ StringArray MidiInput::getDevices() | |||
| int MidiInput::getDefaultDeviceIndex() { return 0;} | |||
| std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; } | |||
| class MidiOutput::Pimpl {}; | |||
| MidiOutput::~MidiOutput() {} | |||
| void MidiOutput::sendMessageNow (const MidiMessage&) {} | |||
| Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; } | |||
| @@ -2234,12 +2234,6 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() | |||
| { | |||
| return new CoreAudioClasses::CoreAudioIODeviceType(); | |||
| } | |||
| #undef JUCE_COREAUDIOLOG | |||
| } // namespace juce | |||
| @@ -393,6 +393,12 @@ namespace CoreMidiHelpers | |||
| } | |||
| } | |||
| class MidiInput::Pimpl : public CoreMidiHelpers::MidiPortAndCallback | |||
| { | |||
| public: | |||
| using MidiPortAndCallback::MidiPortAndCallback; | |||
| }; | |||
| //============================================================================== | |||
| 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))) | |||
| { | |||
| 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))) | |||
| { | |||
| @@ -435,10 +441,11 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| std::unique_ptr<MidiInput> midiInput (new MidiInput (endpointInfo.name, endpointInfo.identifier)); | |||
| mpc->input = midiInput.get(); | |||
| midiInput->internal = mpc.get(); | |||
| auto* ptr = mpc.get(); | |||
| midiInput->internal = std::move (mpc); | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (mpc.release()); | |||
| activeCallbacks.add (ptr); | |||
| return midiInput; | |||
| } | |||
| @@ -462,7 +469,7 @@ std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, | |||
| if (auto client = getGlobalMidiClient()) | |||
| { | |||
| auto mpc = std::make_unique<MidiPortAndCallback> (*callback); | |||
| auto mpc = std::make_unique<Pimpl> (*callback); | |||
| mpc->active = false; | |||
| 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))); | |||
| mpc->input = midiInput.get(); | |||
| midiInput->internal = mpc.get(); | |||
| auto* ptr = mpc.get(); | |||
| midiInput->internal = std::move (mpc); | |||
| const ScopedLock sl (callbackLock); | |||
| activeCallbacks.add (mpc.release()); | |||
| activeCallbacks.add (ptr); | |||
| 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() | |||
| { | |||
| const ScopedLock sl (CoreMidiHelpers::callbackLock); | |||
| static_cast<CoreMidiHelpers::MidiPortAndCallback*> (internal)->active = true; | |||
| internal->active = true; | |||
| } | |||
| void MidiInput::stop() | |||
| { | |||
| 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() | |||
| { | |||
| 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))) | |||
| { | |||
| 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; | |||
| } | |||
| @@ -622,7 +633,7 @@ std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceNam | |||
| if (CHECK_ERROR (MIDIObjectSetIntegerProperty (endpoint, kMIDIPropertyUniqueID, (SInt32) 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; | |||
| } | |||
| @@ -655,8 +666,6 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| delete static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| @@ -715,7 +724,7 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| return; | |||
| } | |||
| static_cast<CoreMidiHelpers::MidiPortAndEndpoint*> (internal)->send (packetToSend); | |||
| internal->send (packetToSend); | |||
| } | |||
| #undef CHECK_ERROR | |||
| @@ -504,7 +504,7 @@ public: | |||
| { | |||
| inBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * n); | |||
| ASIOChannelInfo channelInfo = { 0 }; | |||
| ASIOChannelInfo channelInfo = {}; | |||
| channelInfo.channel = i; | |||
| channelInfo.isInput = 1; | |||
| asioObject->getChannelInfo (&channelInfo); | |||
| @@ -526,7 +526,7 @@ public: | |||
| { | |||
| outBuffers[n] = ioBufferSpace + (currentBlockSizeSamples * (numActiveInputChans + n)); | |||
| ASIOChannelInfo channelInfo = { 0 }; | |||
| ASIOChannelInfo channelInfo = {}; | |||
| channelInfo.channel = i; | |||
| channelInfo.isInput = 0; | |||
| asioObject->getChannelInfo (&channelInfo); | |||
| @@ -673,10 +673,10 @@ public: | |||
| 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"); | |||
| @@ -767,7 +767,7 @@ private: | |||
| bool deviceIsOpen = false, isStarted = false, buffersCreated = false; | |||
| std::atomic<bool> calledback { false }; | |||
| bool littleEndian = false, postOutput = true, needToReset = false; | |||
| bool postOutput = true, needToReset = false; | |||
| bool insideControlPanelModalLoop = false; | |||
| bool shouldUsePreferredSize = false; | |||
| int xruns = 0; | |||
| @@ -785,7 +785,7 @@ private: | |||
| String getChannelName (int index, bool isInput) const | |||
| { | |||
| ASIOChannelInfo channelInfo = { 0 }; | |||
| ASIOChannelInfo channelInfo = {}; | |||
| channelInfo.channel = index; | |||
| channelInfo.isInput = isInput ? 1 : 0; | |||
| asioObject->getChannelInfo (&channelInfo); | |||
| @@ -1065,7 +1065,7 @@ private: | |||
| for (int i = 0; i < totalNumOutputChans; ++i) | |||
| { | |||
| ASIOChannelInfo channelInfo = { 0 }; | |||
| ASIOChannelInfo channelInfo = {}; | |||
| channelInfo.channel = i; | |||
| channelInfo.isInput = 0; | |||
| asioObject->getChannelInfo (&channelInfo); | |||
| @@ -1640,9 +1640,4 @@ void sendASIODeviceChangeToListeners (ASIOAudioIODeviceType* type) | |||
| type->sendDeviceChangeToListeners(); | |||
| } | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() | |||
| { | |||
| return new ASIOAudioIODeviceType(); | |||
| } | |||
| } // namespace juce | |||
| @@ -1282,10 +1282,4 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DSoundAudioIODeviceType) | |||
| }; | |||
| //============================================================================== | |||
| AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() | |||
| { | |||
| return new DSoundAudioIODeviceType(); | |||
| } | |||
| } // namespace juce | |||
| @@ -29,37 +29,39 @@ | |||
| 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 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) | |||
| }; | |||
| @@ -82,12 +84,12 @@ struct Win32MidiService : public MidiServiceType, | |||
| : 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); | |||
| } | |||
| OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||
| MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override | |||
| { | |||
| return new Win32OutputWrapper (*this, deviceIdentifier); | |||
| } | |||
| @@ -384,7 +386,7 @@ private: | |||
| } | |||
| }; | |||
| struct Win32InputWrapper : public InputWrapper, | |||
| struct Win32InputWrapper : public MidiInput::Pimpl, | |||
| public Win32MidiDeviceQuery<Win32InputWrapper> | |||
| { | |||
| 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> | |||
| { | |||
| Win32OutputWrapper (Win32MidiService& p, const String& deviceIdentifier) | |||
| @@ -763,12 +765,12 @@ public: | |||
| : 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); | |||
| } | |||
| OutputWrapper* createOutputWrapper (const String& deviceIdentifier) override | |||
| MidiOutput::Pimpl* createOutputWrapper (const String& deviceIdentifier) override | |||
| { | |||
| 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> | |||
| { | |||
| @@ -1708,7 +1710,7 @@ private: | |||
| }; | |||
| //============================================================================== | |||
| struct WinRTOutputWrapper final : public OutputWrapper, | |||
| struct WinRTOutputWrapper final : public MidiOutput::Pimpl, | |||
| private WinRTIOWrapper <IMidiOutPortStatics, IMidiOutPort> | |||
| { | |||
| WinRTOutputWrapper (WinRTMidiService& service, const String& deviceIdentifier) | |||
| @@ -1865,7 +1867,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| return {}; | |||
| std::unique_ptr<MidiInput> in (new MidiInput ({}, deviceIdentifier)); | |||
| std::unique_ptr<MidiServiceType::InputWrapper> wrapper; | |||
| std::unique_ptr<Pimpl> wrapper; | |||
| try | |||
| { | |||
| @@ -1877,7 +1879,7 @@ std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier | |||
| } | |||
| in->setName (wrapper->getDeviceName()); | |||
| in->internal = wrapper.release(); | |||
| in->internal = std::move (wrapper); | |||
| 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() | |||
| @@ -1931,7 +1930,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||
| if (deviceIdentifier.isEmpty()) | |||
| return {}; | |||
| std::unique_ptr<MidiServiceType::OutputWrapper> wrapper; | |||
| std::unique_ptr<Pimpl> wrapper; | |||
| try | |||
| { | |||
| @@ -1945,7 +1944,7 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifi | |||
| std::unique_ptr<MidiOutput> out; | |||
| out.reset (new MidiOutput (wrapper->getDeviceName(), deviceIdentifier)); | |||
| out->internal = wrapper.release(); | |||
| out->internal = std::move (wrapper); | |||
| return out; | |||
| } | |||
| @@ -1973,12 +1972,11 @@ std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index) | |||
| MidiOutput::~MidiOutput() | |||
| { | |||
| stopBackgroundThread(); | |||
| delete static_cast<MidiServiceType::OutputWrapper*> (internal); | |||
| } | |||
| void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
| { | |||
| static_cast<MidiServiceType::OutputWrapper*> (internal)->sendMessageNow (message); | |||
| internal->sendMessageNow (message); | |||
| } | |||
| } // namespace juce | |||
| @@ -208,6 +208,29 @@ enum AUDCLNT_SHAREMODE | |||
| 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_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_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_COMCALL GetBuffer (BYTE**, UINT32*, DWORD*, UINT64*, UINT64*) = 0; | |||
| @@ -322,87 +359,76 @@ String getDeviceID (IMMDevice* device) | |||
| return s; | |||
| } | |||
| EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device) | |||
| static EDataFlow getDataFlow (const ComSmartPtr<IMMDevice>& device) | |||
| { | |||
| 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; | |||
| } | |||
| 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); | |||
| } | |||
| 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); | |||
| } | |||
| 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) | |||
| : 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 | |||
| { | |||
| 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); | |||
| ComSmartPtr<IAudioClient> tempClient (createClient()); | |||
| if (tempClient == nullptr) | |||
| 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; | |||
| WAVEFORMATEXTENSIBLE format; | |||
| copyWavFormat (format, mixFormat); | |||
| CoTaskMemFree (mixFormat); | |||
| actualNumChannels = numChannels = format.Format.nChannels; | |||
| defaultSampleRate = format.Format.nSamplesPerSec; | |||
| minBufferSize = refTimeToSamples (minPeriod, defaultSampleRate); | |||
| defaultBufferSize = refTimeToSamples (defaultPeriod, defaultSampleRate); | |||
| mixFormatChannelMask = format.dwChannelMask; | |||
| 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() | |||
| @@ -487,11 +513,14 @@ public: | |||
| //============================================================================== | |||
| ComSmartPtr<IMMDevice> device; | |||
| ComSmartPtr<IAudioClient> client; | |||
| WASAPIDeviceMode deviceMode; | |||
| double sampleRate = 0, defaultSampleRate = 0; | |||
| int numChannels = 0, actualNumChannels = 0; | |||
| int minBufferSize = 0, defaultBufferSize = 0, latencySamples = 0; | |||
| int lowLatencyBufferSizeMultiple = 0, lowLatencyMaxBufferSize = 0; | |||
| DWORD mixFormatChannelMask = 0; | |||
| const bool useExclusiveMode; | |||
| Array<double> rates; | |||
| HANDLE clientEvent = {}; | |||
| BigInteger channels; | |||
| @@ -582,6 +611,84 @@ private: | |||
| 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 | |||
| { | |||
| bool useFloat; | |||
| @@ -613,22 +720,35 @@ private: | |||
| format.SubFormat = sampleFormat.useFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM; | |||
| 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, | |||
| useExclusiveMode ? nullptr : (WAVEFORMATEX**) &nearestFormat); | |||
| isExclusiveMode (deviceMode) ? nullptr | |||
| : &nearestFormat); | |||
| 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; | |||
| } | |||
| CoTaskMemFree (nearestFormat); | |||
| return check (hr); | |||
| return hr == S_OK; | |||
| } | |||
| bool findSupportedFormat (IAudioClient* clientToUse, double newSampleRate, | |||
| @@ -652,50 +772,88 @@ private: | |||
| 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 | |||
| { | |||
| 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 | |||
| { | |||
| 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) | |||
| return 0; | |||
| if (! useExclusiveMode) | |||
| if (! isExclusiveMode (deviceMode)) | |||
| { | |||
| UINT32 padding = 0; | |||
| @@ -953,7 +1111,7 @@ public: | |||
| while (bufferSize > 0) | |||
| { | |||
| // 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) | |||
| inputDevice->handleDeviceBuffer(); | |||
| @@ -968,7 +1126,7 @@ public: | |||
| break; | |||
| } | |||
| if (useExclusiveMode && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) | |||
| if (isExclusiveMode (deviceMode) && WaitForSingleObject (clientEvent, 1000) == WAIT_TIMEOUT) | |||
| break; | |||
| uint8* outputData = nullptr; | |||
| @@ -1002,12 +1160,12 @@ public: | |||
| const String& typeName, | |||
| const String& outputDeviceID, | |||
| const String& inputDeviceID, | |||
| bool exclusiveMode) | |||
| WASAPIDeviceMode mode) | |||
| : AudioIODevice (deviceName, typeName), | |||
| Thread ("JUCE WASAPI"), | |||
| outputDeviceId (outputDeviceID), | |||
| inputDeviceId (inputDeviceID), | |||
| useExclusiveMode (exclusiveMode) | |||
| deviceMode (mode) | |||
| { | |||
| } | |||
| @@ -1026,21 +1184,48 @@ public: | |||
| { | |||
| jassert (inputDevice != nullptr || outputDevice != nullptr); | |||
| sampleRates.clear(); | |||
| if (inputDevice != nullptr && outputDevice != nullptr) | |||
| { | |||
| defaultSampleRate = jmin (inputDevice->defaultSampleRate, outputDevice->defaultSampleRate); | |||
| minBufferSize = jmin (inputDevice->minBufferSize, outputDevice->minBufferSize); | |||
| minBufferSize = jmax (inputDevice->minBufferSize, outputDevice->minBufferSize); | |||
| 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 | |||
| { | |||
| 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; | |||
| minBufferSize = d->minBufferSize; | |||
| defaultBufferSize = d->defaultBufferSize; | |||
| if (isLowLatencyMode (deviceMode)) | |||
| { | |||
| lowLatencyMaxBufferSize = d->lowLatencyMaxBufferSize; | |||
| lowLatencyBufferSizeMultiple = d->lowLatencyBufferSizeMultiple; | |||
| } | |||
| sampleRates = d->rates; | |||
| } | |||
| @@ -1050,13 +1235,28 @@ public: | |||
| if (minBufferSize != defaultBufferSize) | |||
| 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; | |||
| @@ -1131,7 +1331,7 @@ public: | |||
| return lastError; | |||
| } | |||
| if (useExclusiveMode) | |||
| if (isExclusiveMode (deviceMode)) | |||
| { | |||
| // This is to make sure that the callback uses actualBufferSize in case of exclusive mode | |||
| if (inputDevice != nullptr && outputDevice != nullptr && inputDevice->actualBufferSize != outputDevice->actualBufferSize) | |||
| @@ -1297,7 +1497,7 @@ public: | |||
| } | |||
| else | |||
| { | |||
| if (useExclusiveMode && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | |||
| if (isExclusiveMode (deviceMode) && WaitForSingleObject (inputDevice->clientEvent, 0) == WAIT_OBJECT_0) | |||
| inputDevice->handleDeviceBuffer(); | |||
| } | |||
| @@ -1347,9 +1547,10 @@ private: | |||
| // Device stats... | |||
| std::unique_ptr<WASAPIInputDevice> inputDevice; | |||
| std::unique_ptr<WASAPIOutputDevice> outputDevice; | |||
| const bool useExclusiveMode; | |||
| WASAPIDeviceMode deviceMode; | |||
| double defaultSampleRate = 0; | |||
| int minBufferSize = 0, defaultBufferSize = 0; | |||
| int lowLatencyMaxBufferSize = 0, lowLatencyBufferSizeMultiple = 0; | |||
| int latencyIn = 0, latencyOut = 0; | |||
| Array<double> sampleRates; | |||
| Array<int> bufferSizes; | |||
| @@ -1399,9 +1600,9 @@ private: | |||
| auto flow = getDataFlow (device); | |||
| if (deviceId == inputDeviceId && flow == eCapture) | |||
| inputDevice.reset (new WASAPIInputDevice (device, useExclusiveMode)); | |||
| inputDevice.reset (new WASAPIInputDevice (device, deviceMode)); | |||
| 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())) | |||
| @@ -1458,10 +1659,10 @@ class WASAPIAudioIODeviceType : public AudioIODeviceType, | |||
| private DeviceChangeDetector | |||
| { | |||
| public: | |||
| WASAPIAudioIODeviceType (bool exclusive) | |||
| : AudioIODeviceType (exclusive ? "Windows Audio (Exclusive Mode)" : "Windows Audio"), | |||
| WASAPIAudioIODeviceType (WASAPIDeviceMode mode) | |||
| : AudioIODeviceType (getDeviceTypename (mode)), | |||
| DeviceChangeDetector (L"Windows Audio"), | |||
| exclusiveMode (exclusive) | |||
| deviceMode (mode) | |||
| { | |||
| } | |||
| @@ -1529,7 +1730,7 @@ public: | |||
| getTypeName(), | |||
| outputDeviceIds [outputIndex], | |||
| inputDeviceIds [inputIndex], | |||
| exclusiveMode)); | |||
| deviceMode)); | |||
| if (! device->initialise()) | |||
| device = nullptr; | |||
| @@ -1543,7 +1744,7 @@ public: | |||
| StringArray inputDeviceNames, inputDeviceIds; | |||
| private: | |||
| const bool exclusiveMode; | |||
| WASAPIDeviceMode deviceMode; | |||
| bool hasScanned = false; | |||
| ComSmartPtr<IMMDeviceEnumerator> enumerator; | |||
| @@ -1698,6 +1899,17 @@ private: | |||
| 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_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 | |||
| float JUCE_CALLTYPE SystemAudioVolume::getGain() { return WasapiClasses::MMDeviceMasterVolume().getGain(); } | |||
| @@ -39,8 +39,7 @@ | |||
| #ifndef JUCE_SUPPORTS_AUv3 | |||
| #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 | |||
| #else | |||
| #define JUCE_SUPPORTS_AUv3 0 | |||
| @@ -79,7 +78,7 @@ namespace AudioUnitFormatHelpers | |||
| static ThreadLocalValue<int> insideCallback; | |||
| #endif | |||
| String osTypeToString (OSType type) noexcept | |||
| static String osTypeToString (OSType type) noexcept | |||
| { | |||
| const juce_wchar s[4] = { (juce_wchar) ((type >> 24) & 0xff), | |||
| (juce_wchar) ((type >> 16) & 0xff), | |||
| @@ -88,7 +87,7 @@ namespace AudioUnitFormatHelpers | |||
| return String (s, 4); | |||
| } | |||
| OSType stringToOSType (String s) | |||
| static OSType stringToOSType (String s) | |||
| { | |||
| if (s.trim().length() >= 4) // (to avoid trimming leading spaces) | |||
| s = s.trim(); | |||
| @@ -103,7 +102,7 @@ namespace AudioUnitFormatHelpers | |||
| static const char* auIdentifierPrefix = "AudioUnit:"; | |||
| String createPluginIdentifier (const AudioComponentDescription& desc) | |||
| static String createPluginIdentifier (const AudioComponentDescription& desc) | |||
| { | |||
| String s (auIdentifierPrefix); | |||
| @@ -128,7 +127,7 @@ namespace AudioUnitFormatHelpers | |||
| return s; | |||
| } | |||
| void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) | |||
| static void getNameAndManufacturer (AudioComponent comp, String& name, String& manufacturer) | |||
| { | |||
| CFStringRef cfName; | |||
| if (AudioComponentCopyName (comp, &cfName) == noErr) | |||
| @@ -147,8 +146,8 @@ namespace AudioUnitFormatHelpers | |||
| 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)) | |||
| { | |||
| @@ -193,8 +192,8 @@ namespace AudioUnitFormatHelpers | |||
| 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); | |||
| @@ -296,7 +295,7 @@ namespace AudioUnitFormatHelpers | |||
| #endif | |||
| } | |||
| const char* getCategory (OSType type) noexcept | |||
| static const char* getCategory (OSType type) noexcept | |||
| { | |||
| switch (type) | |||
| { | |||
| @@ -401,8 +401,8 @@ private: | |||
| class MidiEventList : public Steinberg::Vst::IEventList | |||
| { | |||
| public: | |||
| MidiEventList() {} | |||
| virtual ~MidiEventList() {} | |||
| MidiEventList() = default; | |||
| virtual ~MidiEventList() = default; | |||
| JUCE_DECLARE_VST3_COM_REF_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, | |||
| 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 | |||
| int numEvents = 0; | |||
| @@ -491,7 +525,7 @@ public: | |||
| } | |||
| } | |||
| auto maybeEvent = createVstEvent (msg, metadata.data); | |||
| auto maybeEvent = createVstEvent (msg, metadata.data, kind); | |||
| if (! maybeEvent.isValid) | |||
| continue; | |||
| @@ -503,7 +537,6 @@ public: | |||
| } | |||
| } | |||
| private: | |||
| Array<Steinberg::Vst::Event, CriticalSection> events; | |||
| Atomic<int> refCount; | |||
| @@ -555,10 +588,21 @@ private: | |||
| { | |||
| Steinberg::Vst::Event e{}; | |||
| 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.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; | |||
| } | |||
| @@ -610,14 +654,15 @@ private: | |||
| struct BasicOptional final | |||
| { | |||
| BasicOptional() noexcept = default; | |||
| BasicOptional (const Item& i) noexcept : item ( i ), isValid ( true ) {} | |||
| BasicOptional (const Item& i) noexcept : item { i }, isValid { true } {} | |||
| Item item; | |||
| bool isValid{}; | |||
| }; | |||
| static BasicOptional<Steinberg::Vst::Event> createVstEvent (const MidiMessage& msg, | |||
| const uint8* midiEventData) noexcept | |||
| const uint8* midiEventData, | |||
| EventConversionKind kind) noexcept | |||
| { | |||
| if (msg.isNoteOn()) | |||
| return createNoteOnEvent (msg); | |||
| @@ -643,11 +688,20 @@ private: | |||
| if (msg.isQuarterFrame()) | |||
| 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()) | |||
| return createCtrlPolyPressureEvent (msg); | |||
| { | |||
| switch (kind) | |||
| { | |||
| case EventConversionKind::hostToPlugin: | |||
| return createPolyPressureEvent (msg); | |||
| case EventConversionKind::pluginToHost: | |||
| return createCtrlPolyPressureEvent (msg); | |||
| } | |||
| jassertfalse; | |||
| return {}; | |||
| } | |||
| return {}; | |||
| } | |||
| @@ -738,13 +792,26 @@ private: | |||
| 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) | |||
| @@ -55,7 +55,9 @@ JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wnon-virtual-dtor", | |||
| "-Wformat", | |||
| "-Wpedantic", | |||
| "-Wextra", | |||
| "-Wclass-memaccess") | |||
| "-Wclass-memaccess", | |||
| "-Wmissing-prototypes", | |||
| "-Wtype-limits") | |||
| #undef DEVELOPMENT | |||
| #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | |||
| @@ -818,65 +818,43 @@ private: | |||
| //============================================================================== | |||
| struct DLLHandle | |||
| { | |||
| DLLHandle (const String& modulePath) | |||
| DLLHandle (const File& fileToOpen) | |||
| : dllFile (fileToOpen) | |||
| { | |||
| if (modulePath.trim().isNotEmpty()) | |||
| open (modulePath); | |||
| open(); | |||
| } | |||
| ~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) | |||
| #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(); | |||
| CFBundleUnloadExecutable (bundleRef); | |||
| #if JUCE_WINDOWS || JUCE_LINUX | |||
| library.close(); | |||
| #elif JUCE_MAC | |||
| CFRelease (bundleRef); | |||
| 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() | |||
| { | |||
| if (factory == nullptr) | |||
| if (auto proc = (GetFactoryProc) getFunction ("GetPluginFactory")) | |||
| if (auto* proc = (GetFactoryProc) getFunction (factoryFnName)) | |||
| factory = proc(); | |||
| // The plugin NEEDS to provide a factory to be able to be called a VST3! | |||
| @@ -894,38 +872,56 @@ struct DLLHandle | |||
| if (bundleRef == 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 | |||
| } | |||
| File getFile() const noexcept { return dllFile; } | |||
| private: | |||
| File dllFile; | |||
| IPluginFactory* factory = nullptr; | |||
| void releaseFactory() | |||
| { | |||
| if (factory != nullptr) | |||
| factory->release(); | |||
| } | |||
| static constexpr const char* factoryFnName = "GetPluginFactory"; | |||
| #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; | |||
| 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()) | |||
| #else | |||
| if (proc (library.getNativeHandle())) | |||
| #endif | |||
| return true; | |||
| } | |||
| else | |||
| { | |||
| // this is required for some plug-ins which don't export the dll entry point function | |||
| return true; | |||
| } | |||
| @@ -937,12 +933,14 @@ private: | |||
| #elif JUCE_MAC | |||
| 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); | |||
| CFRelease (url); | |||
| @@ -953,17 +951,11 @@ private: | |||
| if (CFBundleLoadExecutableAndReturnError (bundleRef, &error)) | |||
| { | |||
| using BundleEntryProc = bool (*)(CFBundleRef); | |||
| if (auto proc = (BundleEntryProc) getFunction ("bundleEntry")) | |||
| if (auto* proc = (EntryProc) getFunction (entryFnName)) | |||
| { | |||
| if (proc (bundleRef)) | |||
| return true; | |||
| } | |||
| else | |||
| { | |||
| return true; | |||
| } | |||
| } | |||
| if (error != nullptr) | |||
| @@ -984,127 +976,122 @@ private: | |||
| 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 | |||
| 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 | |||
| { | |||
| 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() | |||
| { | |||
| 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>; | |||
| static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, const PluginDescription& description) | |||
| static VST3ModuleHandle::Ptr findOrCreateModule (const File& file, | |||
| const PluginDescription& description) | |||
| { | |||
| for (auto* module : getActiveModules()) | |||
| { | |||
| // VST3s are basically shells, you must therefore check their name along with their file: | |||
| if (module->file == file && module->name == description.name) | |||
| 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: | |||
| std::unique_ptr<DLLHandle> dllHandle; | |||
| //============================================================================== | |||
| 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) | |||
| { | |||
| @@ -1132,7 +1118,7 @@ private: | |||
| continue; | |||
| if (toString (info.name).trim() == description.name | |||
| && getHashForTUID (info.cid) == description.uid) | |||
| && getHashForTUID (info.cid) == description.uid) | |||
| { | |||
| name = description.name; | |||
| return true; | |||
| @@ -1143,6 +1129,11 @@ private: | |||
| return false; | |||
| } | |||
| File file; | |||
| String name; | |||
| bool isOpen = false; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3ModuleHandle) | |||
| }; | |||
| @@ -1618,7 +1609,7 @@ struct VST3ComponentHolder | |||
| PFactoryInfo factoryInfo; | |||
| factory->getFactoryInfo (&factoryInfo); | |||
| auto classIdx = getClassIndex (module->name); | |||
| auto classIdx = getClassIndex (module->getName()); | |||
| if (classIdx >= 0) | |||
| { | |||
| @@ -1667,8 +1658,8 @@ struct VST3ComponentHolder | |||
| if (component->getBusInfo (Vst::kAudio, Vst::kOutput, i, bus) == kResultOk) | |||
| 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(), | |||
| totalNumInputChannels, | |||
| totalNumOutputChannels); | |||
| @@ -1695,7 +1686,7 @@ struct VST3ComponentHolder | |||
| factory = ComSmartPtr<IPluginFactory> (module->getPluginFactory()); | |||
| int classIdx; | |||
| if ((classIdx = getClassIndex (module->name)) < 0) | |||
| if ((classIdx = getClassIndex (module->getName())) < 0) | |||
| return false; | |||
| PClassInfo info; | |||
| @@ -1991,7 +1982,7 @@ public: | |||
| const String getName() const override | |||
| { | |||
| 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 | |||
| @@ -2979,9 +2970,10 @@ private: | |||
| midiOutputs->clear(); | |||
| if (acceptsMidi()) | |||
| MidiEventList::toEventList (*midiInputs, midiBuffer, | |||
| destination.inputParameterChanges, | |||
| midiMapping); | |||
| MidiEventList::hostToPluginEventList (*midiInputs, | |||
| midiBuffer, | |||
| destination.inputParameterChanges, | |||
| midiMapping); | |||
| destination.inputEvents = midiInputs; | |||
| destination.outputEvents = midiOutputs; | |||
| @@ -3315,7 +3307,29 @@ bool VST3PluginFormat::setStateFromVSTPresetFile (AudioPluginInstance* api, cons | |||
| void VST3PluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& 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, | |||
| @@ -26,13 +26,6 @@ | |||
| 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 | |||
| @@ -24,6 +24,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||
| #define JUCE_VSTINTERFACE_H_INCLUDED | |||
| using namespace juce; | |||
| @@ -505,7 +506,7 @@ enum PresonusExtensionConstants | |||
| @tags{Audio} | |||
| */ | |||
| struct vst2FxBank | |||
| struct fxBank | |||
| { | |||
| int32 magic1; | |||
| int32 size; | |||
| @@ -527,3 +528,5 @@ struct vst2FxBank | |||
| #else | |||
| #pragma pack(pop) | |||
| #endif | |||
| #endif // JUCE_VSTINTERFACE_H_INCLUDED | |||
| @@ -173,7 +173,7 @@ namespace | |||
| #elif JUCE_LINUX || JUCE_IOS || JUCE_ANDROID | |||
| timeval micro; | |||
| gettimeofday (µ, nullptr); | |||
| return micro.tv_usec * 1000.0; | |||
| return (double) micro.tv_usec * 1000.0; | |||
| #elif JUCE_MAC | |||
| UnsignedWide micro; | |||
| Microseconds (µ); | |||
| @@ -441,8 +441,8 @@ private: | |||
| } | |||
| 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.inclusiveHigh = (curEntry == numEntries - 1); | |||
| @@ -2872,8 +2872,8 @@ public: | |||
| { | |||
| X11Symbols::getInstance()->xMoveResizeWindow (display, pluginWindow, | |||
| 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()->xFlush (display); | |||
| @@ -2939,8 +2939,8 @@ public: | |||
| if (pluginRespondsToDPIChanges) | |||
| 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); | |||
| } | |||
| #endif | |||
| @@ -3164,8 +3164,8 @@ private: | |||
| X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | |||
| #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 | |||
| w = jmax (w, 32); | |||
| @@ -3631,7 +3631,6 @@ FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() | |||
| FileSearchPath paths; | |||
| paths.add (WindowsRegistry::getValue ("HKEY_LOCAL_MACHINE\\Software\\VST\\VSTPluginsPath")); | |||
| paths.addIfNotAlreadyThere (programFiles + "\\Steinberg\\VstPlugins"); | |||
| paths.removeNonExistentPaths(); | |||
| paths.addIfNotAlreadyThere (programFiles + "\\VstPlugins"); | |||
| paths.removeRedundantPaths(); | |||
| return paths; | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_audio_processors | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE audio processor classes | |||
| description: Classes for loading and playing VST, AU, LADSPA, or internally-generated audio processors. | |||
| website: http://www.juce.com/juce | |||
| @@ -1567,35 +1567,4 @@ void AudioProcessorParameter::removeListener (AudioProcessorParameter::Listener* | |||
| 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 | |||
| @@ -191,6 +191,7 @@ ComboBoxParameterAttachment::ComboBoxParameterAttachment (RangedAudioParameter& | |||
| ComboBox& c, | |||
| UndoManager* um) | |||
| : comboBox (c), | |||
| storedParameter (param), | |||
| attachment (param, [this] (float f) { setValue (f); }, um) | |||
| { | |||
| sendInitialUpdate(); | |||
| @@ -209,7 +210,8 @@ void ComboBoxParameterAttachment::sendInitialUpdate() | |||
| 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()) | |||
| return; | |||
| @@ -223,7 +225,12 @@ void ComboBoxParameterAttachment::comboBoxChanged (ComboBox*) | |||
| if (ignoreCallbacks) | |||
| 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; | |||
| ComboBox& comboBox; | |||
| RangedAudioParameter& storedParameter; | |||
| ParameterAttachment attachment; | |||
| 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(); | |||
| } | |||
| bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, | |||
| const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) | |||
| static bool operator== (const ArrayBaseTestsHelpers::NonTriviallyCopyableType& ntct, | |||
| const ArrayBaseTestsHelpers::TriviallyCopyableType& tct) | |||
| { | |||
| return tct == ntct; | |||
| } | |||
| @@ -257,8 +257,8 @@ public: | |||
| } | |||
| 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__ | |||
| using OSType = unsigned int; | |||
| #else | |||
| @@ -23,6 +23,14 @@ | |||
| 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_MSVC (4996) | |||
| @@ -37,6 +45,7 @@ RangedDirectoryIterator::RangedDirectoryIterator (const File& directory, | |||
| wildCard, | |||
| whatToLookFor)) | |||
| { | |||
| entry.iterator = iterator; | |||
| increment(); | |||
| } | |||
| @@ -53,7 +53,13 @@ public: | |||
| /** True if the item is read-only, false otherwise. */ | |||
| 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: | |||
| std::weak_ptr<DirectoryIterator> iterator; | |||
| File file; | |||
| Time modTime; | |||
| Time creationTime; | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_core | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| 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. | |||
| 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> | |||
| #endif | |||
| @@ -39,13 +39,6 @@ inline void zerostruct (Type& structure) noexcept { memset ((v | |||
| template <typename Type> | |||
| 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. | |||
| alignmentBytes must be a power of two. */ | |||
| template <typename Type, typename IntegerType> | |||
| @@ -83,6 +76,53 @@ inline void writeUnaligned (void* dstPtr, Type value) noexcept | |||
| 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 | |||
| @@ -215,8 +215,8 @@ double Time::getMillisecondCounterHiRes() noexcept | |||
| bool Time::setSystemTimeToThisTime() const | |||
| { | |||
| 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; | |||
| } | |||
| @@ -424,13 +424,23 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||
| StringArray params; | |||
| params.addTokens (parameters, true); | |||
| NSMutableDictionary* dict = [[NSMutableDictionary new] autorelease]; | |||
| NSMutableArray* paramArray = [[NSMutableArray new] autorelease]; | |||
| for (int i = 0; i < params.size(); ++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 | |||
| forKey: nsStringLiteral ("NSWorkspaceLaunchConfigurationArguments")]; | |||
| @@ -438,6 +448,7 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||
| options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance | |||
| configuration: dict | |||
| error: nil]; | |||
| #endif | |||
| } | |||
| if (file.exists()) | |||
| @@ -137,11 +137,17 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() | |||
| StringArray parts; | |||
| 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 | |||
| } | |||
| @@ -199,10 +205,8 @@ bool SystemStats::isOperatingSystem64Bit() | |||
| { | |||
| #if JUCE_IOS | |||
| return false; | |||
| #elif JUCE_64BIT | |||
| return true; | |||
| #else | |||
| return getOperatingSystemType() >= MacOSX_10_6; | |||
| return true; | |||
| #endif | |||
| } | |||
| @@ -193,29 +193,33 @@ NSRect makeNSRect (const RectangleType& r) noexcept | |||
| static_cast<CGFloat> (r.getHeight())); | |||
| } | |||
| #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 | |||
| 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 | |||
| { | |||
| @@ -236,7 +240,10 @@ struct ObjCClass | |||
| ~ObjCClass() | |||
| { | |||
| objc_disposeClassPair (cls); | |||
| auto kvoSubclassName = String ("NSKVONotifying_") + class_getName (cls); | |||
| if (objc_getClass (kvoSubclassName.toUTF8()) == nullptr) | |||
| objc_disposeClassPair (cls); | |||
| } | |||
| void registerClass() | |||
| @@ -287,13 +294,11 @@ struct ObjCClass | |||
| 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> | |||
| static Type getIvar (id self, const char* name) | |||
| @@ -330,18 +335,14 @@ struct ObjCLifetimeManagedClass : public ObjCClass<NSObject> | |||
| addMethod (@selector (dealloc), dealloc, "v@:"); | |||
| registerClass(); | |||
| } | |||
| 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); | |||
| return self; | |||
| } | |||
| @@ -353,11 +354,9 @@ struct ObjCLifetimeManagedClass : public ObjCClass<NSObject> | |||
| object_setInstanceVariable (_self, "cppObject", nullptr); | |||
| } | |||
| objc_super s = { _self, [NSObject class] }; | |||
| ObjCMsgSendSuper<void> (&s, @selector(dealloc)); | |||
| sendSuperclassMessage<void> (_self, @selector (dealloc)); | |||
| } | |||
| static ObjCLifetimeManagedClass objCLifetimeManagedClass; | |||
| }; | |||
| @@ -71,8 +71,8 @@ namespace | |||
| { | |||
| 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) | |||
| { | |||
| @@ -83,8 +83,8 @@ namespace | |||
| } | |||
| 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; | |||
| } | |||
| } | |||
| @@ -134,6 +134,17 @@ public: | |||
| 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: | |||
| ComClass* p = nullptr; | |||
| @@ -283,9 +283,6 @@ String SystemStats::getOperatingSystemName() | |||
| case Android: 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_8: JUCE_FALLTHROUGH | |||
| case MacOSX_10_9: JUCE_FALLTHROUGH | |||
| @@ -448,7 +448,7 @@ public: | |||
| if (! isRunning()) | |||
| break; | |||
| Thread::yield(); | |||
| Thread::sleep (1); | |||
| } | |||
| else | |||
| { | |||
| @@ -66,12 +66,14 @@ namespace juce | |||
| @see jassert() | |||
| */ | |||
| #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 | |||
| #ifndef __INTEL_COMPILER | |||
| #pragma intrinsic (__debugbreak) | |||
| #endif | |||
| #define JUCE_BREAK_IN_DEBUGGER { __debugbreak(); } | |||
| #elif JUCE_GCC || JUCE_MAC | |||
| #elif JUCE_INTEL && (JUCE_GCC || JUCE_MAC) | |||
| #if JUCE_NO_INLINE_ASM | |||
| #define JUCE_BREAK_IN_DEBUGGER { } | |||
| #else | |||
| @@ -29,7 +29,7 @@ | |||
| */ | |||
| #define JUCE_MAJOR_VERSION 6 | |||
| #define JUCE_MINOR_VERSION 0 | |||
| #define JUCE_BUILDNUMBER 0 | |||
| #define JUCE_BUILDNUMBER 4 | |||
| /** Current JUCE version number. | |||
| @@ -52,9 +52,6 @@ public: | |||
| Android = 0x0800, | |||
| iOS = 0x1000, | |||
| MacOSX_10_4 = MacOSX | 4, | |||
| MacOSX_10_5 = MacOSX | 5, | |||
| MacOSX_10_6 = MacOSX | 6, | |||
| MacOSX_10_7 = MacOSX | 7, | |||
| MacOSX_10_8 = MacOSX | 8, | |||
| MacOSX_10_9 = MacOSX | 9, | |||
| @@ -63,6 +60,8 @@ public: | |||
| MacOSX_10_12 = MacOSX | 12, | |||
| MacOSX_10_13 = MacOSX | 13, | |||
| MacOSX_10_14 = MacOSX | 14, | |||
| MacOSX_10_15 = MacOSX | 15, | |||
| MacOSX_11_0 = MacOSX | 16, | |||
| Win2000 = Windows | 1, | |||
| WinXP = Windows | 2, | |||
| @@ -58,7 +58,6 @@ | |||
| //============================================================================== | |||
| #if defined (_WIN32) || defined (_WIN64) | |||
| #define JUCE_WIN32 1 | |||
| #define JUCE_WINDOWS 1 | |||
| #elif defined (JUCE_ANDROID) | |||
| #undef JUCE_ANDROID | |||
| @@ -68,7 +68,7 @@ public: | |||
| static CharPointerType createUninitialisedBytes (size_t numBytes) | |||
| { | |||
| 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->allocatedNumBytes = numBytes; | |||
| return CharPointerType (s->text); | |||
| @@ -210,7 +210,7 @@ private: | |||
| static StringHolder* bufferFromText (const CharPointerType text) noexcept | |||
| { | |||
| // (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)); | |||
| } | |||
| @@ -1991,7 +1991,7 @@ String String::createStringFromData (const void* const unknownData, int size) | |||
| 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)) | |||
| { | |||
| @@ -2061,19 +2061,19 @@ struct StringEncodingConverter | |||
| template <> | |||
| 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 <> | |||
| 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 <> | |||
| 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); } | |||
| @@ -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 | |||
| // aliased type | |||
| #if __has_attribute(objc_bridge) | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_data_structures | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE data model helper classes | |||
| description: Classes for undo/redo management, and smart data structures. | |||
| website: http://www.juce.com/juce | |||
| @@ -32,7 +32,7 @@ | |||
| ID: juce_events | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE message and event handling classes | |||
| description: Classes for running an application's main event loop and sending/receiving messages, timers, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -144,7 +144,11 @@ private: | |||
| { | |||
| 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) | |||
| didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | |||
| @@ -303,7 +303,6 @@ public: | |||
| /** Returns true if the font is underlined. */ | |||
| bool isUnderlined() const noexcept; | |||
| //============================================================================== | |||
| /** Returns the font's horizontal scale. | |||
| 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); | |||
| private: | |||
| //============================================================================== | |||
| class SharedFontInternal; | |||
| @@ -120,12 +120,22 @@ public: | |||
| Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; } | |||
| /** 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. */ | |||
| 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. */ | |||
| template <typename FloatType> | |||
| @@ -75,10 +75,6 @@ | |||
| #import <QuartzCore/QuartzCore.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 | |||
| #ifndef JUCE_USE_FREETYPE | |||
| #define JUCE_USE_FREETYPE 1 | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_graphics | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE graphics classes | |||
| description: Classes for 2D vector graphics, image loading/saving, font handling, etc. | |||
| website: http://www.juce.com/juce | |||
| @@ -79,7 +79,7 @@ | |||
| /** 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. | |||
| */ | |||
| #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); | |||
| } | |||
| //============================================================================== | |||
| struct ScopedCGContextState | |||
| { | |||
| explicit ScopedCGContextState (CGContextRef c) : context (c) { CGContextSaveGState (context); } | |||
| ~ScopedCGContextState() { CGContextRestoreGState (context); } | |||
| CGContextRef context; | |||
| }; | |||
| //============================================================================== | |||
| CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) | |||
| : context (c), | |||
| @@ -198,15 +207,18 @@ CoreGraphicsContext::CoreGraphicsContext (CGContextRef c, float h) | |||
| CGContextRetain (context.get()); | |||
| CGContextSaveGState (context.get()); | |||
| #if JUCE_MAC | |||
| 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); | |||
| CGContextSetAllowsFontSmoothing (context.get(), enableFontSmoothing); | |||
| #endif | |||
| CGContextSetShouldAntialias (context.get(), true); | |||
| CGContextSetBlendMode (context.get(), kCGBlendModeNormal); | |||
| rgbColourSpace.reset (CGColorSpaceCreateWithName (kCGColorSpaceSRGB)); | |||
| @@ -455,26 +467,23 @@ void CoreGraphicsContext::fillCGRect (const CGRect& cgRect, bool replaceExisting | |||
| { | |||
| CGContextFillRect (context.get(), cgRect); | |||
| } | |||
| else if (state->fillType.isGradient()) | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| CGContextClipToRect (context.get(), cgRect); | |||
| drawGradient(); | |||
| CGContextRestoreGState (context.get()); | |||
| } | |||
| else | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| ScopedCGContextState scopedState (context.get()); | |||
| 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) | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| ScopedCGContextState scopedState (context.get()); | |||
| if (state->fillType.isColour()) | |||
| { | |||
| @@ -501,8 +510,6 @@ void CoreGraphicsContext::fillPath (const Path& path, const AffineTransform& tra | |||
| else | |||
| drawImage (state->fillType.image, state->fillType.transform, true); | |||
| } | |||
| CGContextRestoreGState (context.get()); | |||
| } | |||
| void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTransform& transform) | |||
| @@ -519,7 +526,7 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans | |||
| : rgbColourSpace.get(); | |||
| auto image = detail::ImagePtr { CoreGraphicsPixelData::getCachedImageRef (sourceImage, colourSpace) }; | |||
| CGContextSaveGState (context.get()); | |||
| ScopedCGContextState scopedState (context.get()); | |||
| CGContextSetAlpha (context.get(), state->fillType.getOpacity()); | |||
| flip(); | |||
| @@ -563,8 +570,6 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans | |||
| { | |||
| 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); | |||
| } | |||
| else if (state->fillType.isGradient()) | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| CGContextClipToRects (context.get(), rects, num); | |||
| drawGradient(); | |||
| CGContextRestoreGState (context.get()); | |||
| } | |||
| else | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| ScopedCGContextState scopedState (context.get()); | |||
| 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 | |||
| { | |||
| CGContextSaveGState (context.get()); | |||
| ScopedCGContextState scopedState (context.get()); | |||
| flip(); | |||
| applyTransform (transform); | |||
| @@ -662,8 +664,6 @@ void CoreGraphicsContext::drawGlyph (int glyphNumber, const AffineTransform& tra | |||
| CGGlyph glyphs[1] = { (CGGlyph) glyphNumber }; | |||
| CGPoint positions[1] = { { 0.0f, 0.0f } }; | |||
| CGContextShowGlyphsAtPositions (context.get(), glyphs, positions, 1); | |||
| CGContextRestoreGState (context.get()); | |||
| } | |||
| } | |||
| else | |||
| @@ -234,6 +234,15 @@ namespace CoreTextTypeLayout | |||
| ctFontRef = getFontWithPointSize (ctFontRef, attr.font.getHeight() * getHeightToPointsFactor (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(); | |||
| if (extraKerning != 0) | |||
| @@ -463,6 +472,26 @@ namespace CoreTextTypeLayout | |||
| String::fromCFString (cfsFontStyle), | |||
| (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 (cfsFontFamily); | |||
| } | |||
| @@ -216,6 +216,22 @@ struct ScalingHelpers | |||
| 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> | |||
| static PointOrRect unscaledScreenPosToScaled (PointOrRect pos) noexcept | |||
| { | |||
| @@ -766,7 +782,7 @@ bool Component::isOpaque() const noexcept | |||
| //============================================================================== | |||
| struct StandardCachedComponentImage : public CachedComponentImage | |||
| { | |||
| StandardCachedComponentImage (Component& c) noexcept : owner (c), scale (1.0f) {} | |||
| StandardCachedComponentImage (Component& c) noexcept : owner (c) {} | |||
| void paint (Graphics& g) override | |||
| { | |||
| @@ -820,7 +836,7 @@ private: | |||
| Image image; | |||
| RectangleList<int> validArea; | |||
| Component& owner; | |||
| float scale; | |||
| float scale = 1.0f; | |||
| 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>()); } | |||
| 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) | |||
| @@ -369,6 +369,19 @@ public: | |||
| Rectangle<int> getLocalArea (const Component* sourceComponent, | |||
| 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. | |||
| @see getLocalPoint, localAreaToGlobal | |||
| */ | |||
| @@ -388,6 +401,15 @@ public: | |||
| */ | |||
| 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. | |||
| @@ -368,7 +368,7 @@ public: | |||
| if (parseNextNumber (d, num, false)) | |||
| { | |||
| auto angle = degreesToRadians (num.getFloatValue()); | |||
| auto angle = degreesToRadians (parseSafeFloat (num)); | |||
| 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) | |||
| { | |||
| @@ -618,7 +618,7 @@ private: | |||
| 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 points = pointsAtt.getCharPointer(); | |||
| @@ -683,7 +683,7 @@ private: | |||
| //============================================================================== | |||
| Drawable* parseShape (const XmlPath& xml, Path& path, | |||
| const bool shouldParseTransform = true, | |||
| bool shouldParseTransform = true, | |||
| AffineTransform* additonalTransform = nullptr) const | |||
| { | |||
| if (shouldParseTransform && xml->hasAttribute ("transform")) | |||
| @@ -835,14 +835,14 @@ private: | |||
| auto col = parseColour (fillXml.getChild (e), "stop-color", Colours::black); | |||
| 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 ('%')) | |||
| 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; | |||
| } | |||
| } | |||
| @@ -983,10 +983,10 @@ private: | |||
| float opacity = 1.0f; | |||
| if (overallOpacity.isNotEmpty()) | |||
| opacity = jlimit (0.0f, 1.0f, overallOpacity.getFloatValue()); | |||
| opacity = jlimit (0.0f, 1.0f, parseSafeFloat (overallOpacity)); | |||
| 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 urlID = parseURL (fill); | |||
| @@ -1035,11 +1035,10 @@ private: | |||
| } | |||
| //============================================================================== | |||
| 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 }; | |||
| @@ -1099,7 +1098,7 @@ private: | |||
| dt->setTransform (transform); | |||
| 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(), | |||
| font.getStringWidthFloat (text), font.getHeight()); | |||
| @@ -1138,8 +1137,8 @@ private: | |||
| //============================================================================== | |||
| 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 }; | |||
| @@ -1210,10 +1209,13 @@ private: | |||
| 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()))); | |||
| @@ -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; | |||
| @@ -1251,13 +1253,13 @@ private: | |||
| 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) | |||
| && 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)) | |||
| return true; | |||
| @@ -1268,8 +1270,8 @@ private: | |||
| 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) | |||
| { | |||
| @@ -1293,7 +1295,7 @@ private: | |||
| 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(); | |||
| float value; | |||
| @@ -1302,6 +1304,12 @@ private: | |||
| 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) | |||
| { | |||
| @@ -1452,7 +1460,7 @@ private: | |||
| 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; | |||
| @@ -1574,21 +1582,21 @@ private: | |||
| auto alpha = [&tokens, &text] | |||
| { | |||
| if ((text.startsWith ("rgba") || text.startsWith ("hsla")) && tokens.size() == 4) | |||
| return tokens[3].getFloatValue(); | |||
| return parseSafeFloat (tokens[3]); | |||
| return 1.0f; | |||
| }(); | |||
| 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); | |||
| 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); | |||
| return Colour ((uint8) tokens[0].getIntValue(), | |||
| @@ -1623,7 +1631,7 @@ private: | |||
| float numbers[6]; | |||
| for (int i = 0; i < numElementsInArray (numbers); ++i) | |||
| numbers[i] = tokens[i].getFloatValue(); | |||
| numbers[i] = parseSafeFloat (tokens[i]); | |||
| AffineTransform trans; | |||
| @@ -367,7 +367,7 @@ void FileBrowserComponent::lookAndFeelChanged() | |||
| currentPathBox.setColour (ComboBox::arrowColourId, findColour (currentPathBoxArrowColourId)); | |||
| filenameBox.setColour (TextEditor::backgroundColourId, findColour (filenameBoxBackgroundColourId)); | |||
| filenameBox.setColour (TextEditor::textColourId, findColour (filenameBoxTextColourId)); | |||
| filenameBox.applyColourToAllText (findColour (filenameBoxTextColourId)); | |||
| resized(); | |||
| repaint(); | |||
| @@ -35,7 +35,7 @@ | |||
| ID: juce_gui_basics | |||
| vendor: juce | |||
| version: 6.0.0 | |||
| version: 6.0.4 | |||
| name: JUCE GUI core classes | |||
| description: Basic user-interface components and related classes. | |||
| website: http://www.juce.com/juce | |||
| @@ -43,7 +43,7 @@ | |||
| dependencies: juce_graphics juce_data_structures | |||
| OSXFrameworks: Cocoa Carbon QuartzCore | |||
| iOSFrameworks: UIKit MobileCoreServices | |||
| iOSFrameworks: UIKit CoreServices | |||
| END_JUCE_MODULE_DECLARATION | |||
| @@ -382,11 +382,11 @@ struct MenuWindow : public Component | |||
| { | |||
| if (key.isKeyCode (KeyPress::downKey)) | |||
| { | |||
| selectNextItem (1); | |||
| selectNextItem (MenuSelectionDirection::forwards); | |||
| } | |||
| else if (key.isKeyCode (KeyPress::upKey)) | |||
| { | |||
| selectNextItem (-1); | |||
| selectNextItem (MenuSelectionDirection::backwards); | |||
| } | |||
| else if (key.isKeyCode (KeyPress::leftKey)) | |||
| { | |||
| @@ -414,14 +414,14 @@ struct MenuWindow : public Component | |||
| if (showSubMenuFor (currentChild)) | |||
| { | |||
| if (isSubMenuVisible()) | |||
| activeSubMenu->selectNextItem (0); | |||
| activeSubMenu->selectNextItem (MenuSelectionDirection::current); | |||
| } | |||
| else if (componentAttachedTo != nullptr) | |||
| { | |||
| componentAttachedTo->keyPressed (key); | |||
| } | |||
| } | |||
| else if (key.isKeyCode (KeyPress::returnKey)) | |||
| else if (key.isKeyCode (KeyPress::returnKey) || key.isKeyCode (KeyPress::spaceKey)) | |||
| { | |||
| triggerCurrentlyHighlightedItem(); | |||
| } | |||
| @@ -948,24 +948,46 @@ struct MenuWindow : public Component | |||
| } | |||
| } | |||
| void selectNextItem (int delta) | |||
| enum class MenuSelectionDirection | |||
| { | |||
| forwards, | |||
| backwards, | |||
| current | |||
| }; | |||
| void selectNextItem (MenuSelectionDirection direction) | |||
| { | |||
| 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;) | |||
| { | |||
| start += delta; | |||
| if (preIncrement) | |||
| start += (direction == MenuSelectionDirection::backwards ? -1 : 1); | |||
| if (auto* mic = items.getUnchecked ((start + items.size()) % items.size())) | |||
| { | |||
| if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item)) | |||
| { | |||
| 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 | |||
| ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | |||
| 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 | |||
| ineffective unless you have a JUCE Indie or Pro license, or are using JUCE | |||
| 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); | |||
| setAlwaysOnTop (true); | |||
| setVisible (true); | |||
| addToDesktop (0); | |||
| enterModalState (true, | |||
| @@ -26,19 +26,22 @@ | |||
| namespace juce | |||
| { | |||
| class FileChooser::Native : private Component, | |||
| public FileChooser::Pimpl | |||
| class FileChooser::Native : public FileChooser::Pimpl, | |||
| public Component, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| Native (FileChooser& fileChooser, int flags) | |||
| : 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); | |||
| static FileChooserControllerClass controllerClass; | |||
| auto* controllerClassInstance = controllerClass.createInstance(); | |||
| String firstFileExtension; | |||
| auto utTypeArray = createNSArrayFromStringArray (getUTTypesForWildcards (owner.filters, firstFileExtension)); | |||
| if ((flags & FileBrowserComponent::saveMode) != 0) | |||
| @@ -69,47 +72,51 @@ public: | |||
| } | |||
| 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]; | |||
| } | |||
| 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() setModalTransitionStyle: UIModalTransitionStyleCrossDissolve]; | |||
| 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 | |||
| // 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! | |||
| jassert (fileChooser.parent != nullptr); | |||
| jassertfalse; | |||
| return; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| auto chooserBounds = Desktop::getInstance().getDisplays().getMainDisplay().userArea; | |||
| setBounds (chooserBounds); | |||
| setAlwaysOnTop (true); | |||
| setVisible (true); | |||
| addToDesktop (0); | |||
| } | |||
| } | |||
| @@ -131,8 +138,6 @@ public: | |||
| #endif | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| void parentHierarchyChanged() override | |||
| { | |||
| auto* newPeer = dynamic_cast<UIViewComponentPeer*> (getPeer()); | |||
| @@ -141,11 +146,23 @@ private: | |||
| { | |||
| 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) | |||
| { | |||
| @@ -182,7 +199,9 @@ private: | |||
| } | |||
| } | |||
| else | |||
| { | |||
| result.add ("public.data"); | |||
| } | |||
| return result; | |||
| } | |||
| @@ -207,6 +226,8 @@ private: | |||
| //============================================================================== | |||
| void didPickDocumentAtURL (NSURL* url) | |||
| { | |||
| cancelPendingUpdate(); | |||
| bool isWriting = controller.get().documentPickerMode == UIDocumentPickerModeExportToService | |||
| | controller.get().documentPickerMode == UIDocumentPickerModeMoveToService; | |||
| @@ -267,9 +288,9 @@ private: | |||
| void pickerWasCancelled() | |||
| { | |||
| Array<URL> chooserResults; | |||
| cancelPendingUpdate(); | |||
| owner.finished (chooserResults); | |||
| owner.finished ({}); | |||
| exitModalState (0); | |||
| } | |||
| @@ -294,21 +315,40 @@ private: | |||
| //============================================================================== | |||
| static void didPickDocumentAtURL (id self, SEL, UIDocumentPickerViewController*, NSURL* url) | |||
| { | |||
| auto picker = getOwner (self); | |||
| if (picker != nullptr) | |||
| if (auto* picker = getOwner (self)) | |||
| picker->didPickDocumentAtURL (url); | |||
| } | |||
| static void documentPickerWasCancelled (id self, SEL, UIDocumentPickerViewController*) | |||
| { | |||
| auto picker = getOwner (self); | |||
| if (picker != nullptr) | |||
| if (auto* picker = getOwner (self)) | |||
| 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; | |||
| std::unique_ptr<NSObject<UIDocumentPickerDelegate>, NSObjectDeleter> delegate; | |||
| @@ -316,6 +356,7 @@ private: | |||
| UIViewComponentPeer* peer = nullptr; | |||
| static FileChooserDelegateClass fileChooserDelegateClass; | |||
| static FileChooserControllerClass fileChooserControllerClass; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Native) | |||
| @@ -28,14 +28,6 @@ namespace juce | |||
| 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() | |||
| { | |||
| UIApplication* sharedApplication = [UIApplication sharedApplication]; | |||
| @@ -79,28 +71,11 @@ namespace Orientations | |||
| 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() | |||
| { | |||
| NSUInteger allowed = 0; | |||
| Desktop& d = Desktop::getInstance(); | |||
| auto& d = Desktop::getInstance(); | |||
| if (d.isOrientationEnabled (Desktop::upright)) allowed |= UIInterfaceOrientationMaskPortrait; | |||
| if (d.isOrientationEnabled (Desktop::upsideDown)) allowed |= UIInterfaceOrientationMaskPortraitUpsideDown; | |||
| @@ -200,12 +175,10 @@ namespace juce | |||
| struct UIViewPeerControllerReceiver | |||
| { | |||
| virtual ~UIViewPeerControllerReceiver(); | |||
| virtual ~UIViewPeerControllerReceiver() = default; | |||
| virtual void setViewController (UIViewController*) = 0; | |||
| }; | |||
| UIViewPeerControllerReceiver::~UIViewPeerControllerReceiver() {} | |||
| class UIViewComponentPeer : public ComponentPeer, | |||
| public FocusChangeListener, | |||
| public UIViewPeerControllerReceiver | |||
| @@ -259,7 +232,7 @@ public: | |||
| void updateHiddenTextContent (TextInputTarget*); | |||
| void globalFocusChanged (Component*) override; | |||
| void updateTransformAndScreenBounds(); | |||
| void updateScreenBounds(); | |||
| void handleTouches (UIEvent*, bool isDown, bool isUp, bool isCancel); | |||
| @@ -268,10 +241,11 @@ public: | |||
| 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 | |||
| { | |||
| @@ -279,73 +253,10 @@ public: | |||
| + (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; | |||
| private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | |||
| //============================================================================== | |||
| class AsyncRepaintMessage : public CallbackMessage | |||
| { | |||
| public: | |||
| @@ -363,6 +274,9 @@ private: | |||
| peer->repaint (rect); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponentPeer) | |||
| }; | |||
| static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||
| @@ -370,7 +284,7 @@ static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||
| JuceUIView* juceView = (JuceUIView*) [c view]; | |||
| if (juceView != nil && juceView->owner != nullptr) | |||
| juceView->owner->updateTransformAndScreenBounds(); | |||
| juceView->owner->updateScreenBounds(); | |||
| } | |||
| void AsyncBoundsUpdater::handleAsyncUpdate() | |||
| @@ -614,14 +528,9 @@ bool KeyPress::isKeyCurrentlyDown (int) | |||
| Point<float> juce_lastMousePos; | |||
| //============================================================================== | |||
| UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyleFlags, UIView* viewToAttachTo) | |||
| UIViewComponentPeer::UIViewComponentPeer (Component& comp, int windowStyleFlags, UIView* viewToAttachTo) | |||
| : ComponentPeer (comp, windowStyleFlags), | |||
| window (nil), | |||
| view (nil), | |||
| controller (nil), | |||
| isSharedWindow (viewToAttachTo != nil), | |||
| fullScreen (false), | |||
| insideDrawRect (false), | |||
| isAppex (SystemStats::isRunningInAppExtensionSandbox()) | |||
| { | |||
| CGRect r = convertToCGRect (component.getBounds()); | |||
| @@ -632,7 +541,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||
| view.hidden = true; | |||
| view.opaque = component.isOpaque(); | |||
| view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | |||
| view.transform = CGAffineTransformIdentity; | |||
| if (isSharedWindow) | |||
| { | |||
| @@ -641,7 +549,7 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||
| } | |||
| else | |||
| { | |||
| r = convertToCGRect (rotatedScreenPosToReal (component.getBounds())); | |||
| r = convertToCGRect (component.getBounds()); | |||
| r.origin.y = [UIScreen mainScreen].bounds.size.height - (r.origin.y + r.size.height); | |||
| window = [[JuceUIWindow alloc] initWithFrame: r]; | |||
| @@ -652,7 +560,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||
| window.rootViewController = controller; | |||
| window.hidden = true; | |||
| window.transform = Orientations::getCGTransformFor (Desktop::getInstance().getCurrentOrientation()); | |||
| window.opaque = component.isOpaque(); | |||
| window.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent: 0]; | |||
| @@ -660,8 +567,6 @@ UIViewComponentPeer::UIViewComponentPeer (Component& comp, const int windowStyle | |||
| window.windowLevel = UIWindowLevelAlert; | |||
| view.frame = CGRectMake (0, 0, r.size.width, r.size.height); | |||
| [window addSubview: view]; | |||
| } | |||
| setTitle (component.getName()); | |||
| @@ -718,7 +623,7 @@ void UIViewComponentPeer::setBounds (const Rectangle<int>& newBounds, const bool | |||
| } | |||
| else | |||
| { | |||
| window.frame = convertToCGRect (rotatedScreenPosToReal (newBounds)); | |||
| window.frame = convertToCGRect (newBounds); | |||
| view.frame = CGRectMake (0, 0, (CGFloat) newBounds.getWidth(), (CGFloat) newBounds.getHeight()); | |||
| handleMovedOrResized(); | |||
| @@ -733,8 +638,6 @@ Rectangle<int> UIViewComponentPeer::getBounds (const bool global) const | |||
| { | |||
| r = [view convertRect: r toView: view.window]; | |||
| r = [view.window convertRect: r toWindow: nil]; | |||
| return realScreenPosToRotated (convertToRectInt (r)); | |||
| } | |||
| return convertToRectInt (r); | |||
| @@ -759,8 +662,8 @@ void UIViewComponentPeer::setFullScreen (bool shouldBeFullScreen) | |||
| { | |||
| 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()) | |||
| 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) | |||
| { | |||
| @@ -792,13 +693,13 @@ void UIViewComponentPeer::updateTransformAndScreenBounds() | |||
| else if (! isSharedWindow) | |||
| { | |||
| // 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)); | |||
| } | |||
| @@ -808,13 +709,8 @@ void UIViewComponentPeer::updateTransformAndScreenBounds() | |||
| 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) | |||
| withEvent: nil]; | |||
| @@ -844,16 +740,10 @@ void UIViewComponentPeer::toFront (bool makeActiveWindow) | |||
| void UIViewComponentPeer::toBehind (ComponentPeer* other) | |||
| { | |||
| if (UIViewComponentPeer* const otherPeer = dynamic_cast<UIViewComponentPeer*> (other)) | |||
| if (auto* otherPeer = dynamic_cast<UIViewComponentPeer*> (other)) | |||
| { | |||
| if (isSharedWindow) | |||
| { | |||
| [[view superview] insertSubview: view belowSubview: otherPeer->view]; | |||
| } | |||
| else | |||
| { | |||
| // don't know how to do this | |||
| } | |||
| } | |||
| else | |||
| { | |||
| @@ -869,23 +759,17 @@ void UIViewComponentPeer::setIcon (const Image& /*newIcon*/) | |||
| //============================================================================== | |||
| static float getMaximumTouchForce (UITouch* touch) noexcept | |||
| { | |||
| #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||
| if ([touch respondsToSelector: @selector (maximumPossibleForce)]) | |||
| return (float) touch.maximumPossibleForce; | |||
| #endif | |||
| ignoreUnused (touch); | |||
| return 0.0f; | |||
| } | |||
| static float getTouchForce (UITouch* touch) noexcept | |||
| { | |||
| #if defined (__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0 | |||
| if ([touch respondsToSelector: @selector (force)]) | |||
| return (float) touch.force; | |||
| #endif | |||
| ignoreUnused (touch); | |||
| 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) | |||
| { | |||
| UITouch* touch = [touches objectAtIndex: i]; | |||
| const float maximumForce = getMaximumTouchForce (touch); | |||
| auto maximumForce = getMaximumTouchForce (touch); | |||
| if ([touch phase] == UITouchPhaseStationary && maximumForce <= 0) | |||
| continue; | |||
| 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(); | |||
| 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) | |||
| { | |||
| @@ -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: | |||
| 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, | |||
| 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 | |||
| { | |||
| switch (type) | |||
| { | |||
| 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::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; | |||
| case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; | |||
| @@ -1041,9 +920,9 @@ void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target) | |||
| 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 (currentSelection.isEmpty()) | |||
| @@ -1062,15 +941,16 @@ BOOL UIViewComponentPeer::textViewReplaceCharacters (Range<int> range, const Str | |||
| 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 | |||
| { | |||
| @@ -1078,7 +958,6 @@ void UIViewComponentPeer::globalFocusChanged (Component*) | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void UIViewComponentPeer::drawRect (CGRect r) | |||
| { | |||
| @@ -1091,9 +970,6 @@ void UIViewComponentPeer::drawRect (CGRect r) | |||
| CGContextClearRect (cg, CGContextGetClipBoundingBox (cg)); | |||
| 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()); | |||
| insideDrawRect = true; | |||
| @@ -1111,9 +987,9 @@ void Desktop::setKioskComponent (Component* kioskModeComp, bool enableOrDisable, | |||
| { | |||
| 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]; | |||
| peer->setFullScreen (enableOrDisable); | |||
| @@ -1125,21 +1001,18 @@ void Desktop::allowedOrientationsChanged() | |||
| // if the current orientation isn't allowed anymore then switch orientations | |||
| 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"]; | |||
| [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 | |||
| @@ -29,7 +29,7 @@ namespace juce | |||
| struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules | |||
| { | |||
| virtual ~AppInactivityCallback() {} | |||
| virtual ~AppInactivityCallback() = default; | |||
| 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 | |||
| { | |||
| public: | |||
| @@ -466,7 +442,6 @@ public: | |||
| ModalComponentManager::Callback* cb, const bool async) | |||
| : result (0), resultReceived (false), callback (cb), isAsync (async) | |||
| { | |||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||
| if (currentlyFocusedPeer != nullptr) | |||
| { | |||
| UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title) | |||
| @@ -486,27 +461,6 @@ public: | |||
| // have at least one window on screen when you use this | |||
| 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() | |||
| @@ -515,11 +469,7 @@ public: | |||
| JUCE_AUTORELEASEPOOL | |||
| { | |||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||
| while (! resultReceived) | |||
| #else | |||
| while (! (alert.hidden || resultReceived)) | |||
| #endif | |||
| [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; | |||
| } | |||
| @@ -544,7 +494,6 @@ private: | |||
| std::unique_ptr<ModalComponentManager::Callback> callback; | |||
| const bool isAsync; | |||
| #if JUCE_USE_NEW_IOS_ALERTWINDOW | |||
| void addButton (UIAlertController* alert, NSString* text, int index) | |||
| { | |||
| if (text != nil) | |||
| @@ -552,33 +501,10 @@ private: | |||
| style: UIAlertActionStyleDefault | |||
| handler: ^(UIAlertAction*) { this->buttonClicked (index); }]]; | |||
| } | |||
| #else | |||
| UIAlertView* alert; | |||
| JuceAlertBoxDelegate* delegate; | |||
| #endif | |||
| 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 | |||
| void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType /*iconType*/, | |||
| @@ -742,13 +668,9 @@ void Displays::findDisplays (float masterScale) | |||
| UIScreen* s = [UIScreen mainScreen]; | |||
| Display d; | |||
| d.userArea = d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; | |||
| d.userArea = d.totalArea = convertToRectInt ([s bounds]) / masterScale; | |||
| 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; | |||
| displays.add (d); | |||
| @@ -27,16 +27,18 @@ namespace juce | |||
| { | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| static bool exeIsAvailable (const char* const executable) | |||
| static bool exeIsAvailable (String executable) | |||
| { | |||
| 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, | |||
| private Timer | |||
| @@ -68,7 +70,7 @@ public: | |||
| child.start (args, ChildProcess::wantStdOut); | |||
| while (child.isRunning()) | |||
| if (! MessageManager::getInstance()->runDispatchLoopUntil(20)) | |||
| if (! MessageManager::getInstance()->runDispatchLoopUntil (20)) | |||
| break; | |||
| finish (false); | |||
| @@ -187,7 +189,7 @@ private: | |||
| } | |||
| args.add (startPath.getFullPathName()); | |||
| args.add (owner.filters.replaceCharacter (';', ' ')); | |||
| args.add ("(" + owner.filters.replaceCharacter (';', ' ') + ")"); | |||
| } | |||
| void addZenityArgs() | |||
| @@ -218,8 +220,7 @@ private: | |||
| StringArray tokens; | |||
| 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()) | |||
| @@ -330,11 +330,17 @@ private: | |||
| class LinuxRepaintManager : public Timer | |||
| { | |||
| public: | |||
| LinuxRepaintManager (LinuxComponentPeer& p) : peer (p) {} | |||
| LinuxRepaintManager (LinuxComponentPeer& p) | |||
| : peer (p), | |||
| isSemiTransparentWindow ((peer.getStyleFlags() & ComponentPeer::windowIsSemiTransparent) != 0) | |||
| { | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| if (XWindowSystem::getInstance()->getNumPaintsPending (peer.windowH) > 0) | |||
| XWindowSystem::getInstance()->processPendingPaintsForWindow (peer.windowH); | |||
| if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0) | |||
| return; | |||
| if (! regionsNeedingRepaint.isEmpty()) | |||
| @@ -359,7 +365,7 @@ private: | |||
| void performAnyPendingRepaintsNow() | |||
| { | |||
| if (XWindowSystem::getInstance()->getNumPaintsPending (peer.windowH) > 0) | |||
| if (XWindowSystem::getInstance()->getNumPaintsPendingForWindow (peer.windowH) > 0) | |||
| { | |||
| startTimer (repaintTimerPeriod); | |||
| return; | |||
| @@ -374,7 +380,8 @@ private: | |||
| if (image.isNull() || image.getWidth() < totalArea.getWidth() | |||
| || image.getHeight() < totalArea.getHeight()) | |||
| { | |||
| image = XWindowSystem::getInstance()->createImage (totalArea.getWidth(), totalArea.getHeight(), | |||
| image = XWindowSystem::getInstance()->createImage (isSemiTransparentWindow, | |||
| totalArea.getWidth(), totalArea.getHeight(), | |||
| useARGBImagesForRendering); | |||
| } | |||
| @@ -407,6 +414,7 @@ private: | |||
| enum { repaintTimerPeriod = 1000 / 100 }; | |||
| LinuxComponentPeer& peer; | |||
| const bool isSemiTransparentWindow; | |||
| Image image; | |||
| uint32 lastTimeImageUsed = 0; | |||
| RectangleList<int> regionsNeedingRepaint; | |||
| @@ -283,7 +283,7 @@ private: | |||
| //============================================================================== | |||
| struct DelegateClass : ObjCClass<DelegateType> | |||
| { | |||
| DelegateClass() : ObjCClass <DelegateType> ("JUCEFileChooser_") | |||
| DelegateClass() : ObjCClass<DelegateType> ("JUCEFileChooser_") | |||
| { | |||
| addIvar<Native*> ("cppObject"); | |||
| @@ -534,30 +534,7 @@ private: | |||
| auto owner = getIvar<JuceMainMenuHandler*> (self, "owner"); | |||
| 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])); | |||
| } | |||
| } | |||
| 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) \ | |||
| && USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_DRAW_ASYNC | |||
| @@ -680,8 +685,8 @@ public: | |||
| } | |||
| 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 (...) | |||
| @@ -1770,10 +1775,7 @@ private: | |||
| owner->stringBeingComposed.clear(); | |||
| 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); | |||
| 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) | |||
| { | |||
| sendSuperclassMessage (self, @selector (becomeKeyWindow)); | |||
| sendSuperclassMessage<void> (self, @selector (becomeKeyWindow)); | |||
| if (auto* owner = getOwner (self)) | |||
| { | |||
| @@ -2057,10 +2056,15 @@ private: | |||
| 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)) | |||
| { | |||
| frameRect = sendSuperclassMessage<NSRect, NSRect, NSScreen*> (self, @selector (constrainFrameRect:toScreen:), | |||
| frameRect, screen); | |||
| frameRect = owner->constrainRect (frameRect); | |||
| } | |||
| return frameRect; | |||
| } | |||
| @@ -2104,10 +2108,10 @@ private: | |||
| { | |||
| 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(); | |||
| } | |||
| @@ -217,7 +217,7 @@ private: | |||
| delete getIvar<std::function<void()>*> (self, "callback"); | |||
| 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) | |||
| @@ -58,7 +58,7 @@ public: | |||
| // Handle nonexistent root directories in the same way as existing ones | |||
| files.calloc (static_cast<size_t> (charsAvailableForResult) + 1); | |||
| if (startingFile.isDirectory() ||startingFile.isRoot()) | |||
| if (startingFile.isDirectory() || startingFile.isRoot()) | |||
| { | |||
| initialPath = startingFile.getFullPathName(); | |||
| } | |||
| @@ -154,7 +154,7 @@ private: | |||
| Component::SafePointer<Component> owner; | |||
| String title, filtersString; | |||
| std::unique_ptr<CustomComponentHolder> customComponent; | |||
| String initialPath, returnedString, defaultExtension; | |||
| String initialPath, returnedString; | |||
| WaitableEvent threadHasReference; | |||
| CriticalSection deletingDialog; | |||
| @@ -167,8 +167,144 @@ private: | |||
| Atomic<HWND> nativeDialogRef; | |||
| 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; | |||
| @@ -213,7 +349,7 @@ private: | |||
| { | |||
| OPENFILENAMEW of = {}; | |||
| #ifdef OPENFILENAME_SIZE_VERSION_400W | |||
| #ifdef OPENFILENAME_SIZE_VERSION_400W | |||
| of.lStructSize = OPENFILENAME_SIZE_VERSION_400W; | |||
| #else | |||
| of.lStructSize = sizeof (of); | |||
| @@ -231,16 +367,10 @@ private: | |||
| 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)) | |||
| return {}; | |||
| @@ -251,7 +381,7 @@ private: | |||
| 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; | |||
| @@ -267,18 +397,34 @@ private: | |||
| } | |||
| } | |||
| getNativeDialogList().removeValue (this); | |||
| 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 | |||
| { | |||
| // as long as the thread is running, don't delete this class | |||
| Ptr safeThis (this); | |||
| threadHasReference.signal(); | |||
| Array<URL> r = openDialog (true); | |||
| auto r = openDialog (true); | |||
| MessageManager::callAsync ([safeThis, r] | |||
| { | |||
| safeThis->results = r; | |||
| @@ -330,6 +476,23 @@ private: | |||
| 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) | |||
| { | |||
| @@ -414,7 +577,7 @@ private: | |||
| 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 }; | |||
| CommDlg_OpenSave_GetFilePath (hdlg, (LPARAM) &path, MAX_PATH); | |||
| @@ -514,7 +677,6 @@ class FileChooser::Native : public Component, | |||
| public FileChooser::Pimpl | |||
| { | |||
| public: | |||
| Native (FileChooser& fileChooser, int flags, FilePreviewComponent* previewComp) | |||
| : owner (fileChooser), | |||
| nativeFileChooser (new Win32NativeFileChooser (this, flags, previewComp, fileChooser.startingFile, | |||
| @@ -531,7 +693,7 @@ public: | |||
| addToDesktop (0); | |||
| } | |||
| ~Native() | |||
| ~Native() override | |||
| { | |||
| exitModalState (0); | |||
| nativeFileChooser->cancel(); | |||
| @@ -3176,10 +3176,7 @@ private: | |||
| updateKeyModifiers(); | |||
| if (hwnd == GetActiveWindow()) | |||
| { | |||
| handleKeyPress (key, 0); | |||
| return true; | |||
| } | |||
| return handleKeyPress (key, 0); | |||
| } | |||
| return false; | |||
| @@ -198,7 +198,7 @@ public: | |||
| srcMimeTypeAtomList.clear(); | |||
| 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) | |||
| { | |||
| @@ -80,6 +80,7 @@ bool X11Symbols::loadAllSymbols() | |||
| using namespace X11SymbolHelpers; | |||
| if (! loadSymbols (xLib, xextLib, | |||
| makeSymbolBinding (xAllocClassHint, "XAllocClassHint"), | |||
| makeSymbolBinding (xAllocSizeHints, "XAllocSizeHints"), | |||
| makeSymbolBinding (xAllocWMHints, "XAllocWMHints"), | |||
| makeSymbolBinding (xBitmapBitOrder, "XBitmapBitOrder"), | |||
| @@ -169,6 +170,7 @@ bool X11Symbols::loadAllSymbols() | |||
| makeSymbolBinding (xScreenNumberOfScreen, "XScreenNumberOfScreen"), | |||
| makeSymbolBinding (xSelectInput, "XSelectInput"), | |||
| makeSymbolBinding (xSendEvent, "XSendEvent"), | |||
| makeSymbolBinding (xSetClassHint, "XSetClassHint"), | |||
| makeSymbolBinding (xSetErrorHandler, "XSetErrorHandler"), | |||
| makeSymbolBinding (xSetIOErrorHandler, "XSetIOErrorHandler"), | |||
| makeSymbolBinding (xSetInputFocus, "XSetInputFocus"), | |||
| @@ -49,6 +49,10 @@ public: | |||
| bool loadAllSymbols(); | |||
| //============================================================================== | |||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocClassHint, xAllocClassHint, | |||
| (), | |||
| XClassHint*) | |||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XAllocSizeHints, xAllocSizeHints, | |||
| (), | |||
| XSizeHints*) | |||
| @@ -405,6 +409,10 @@ public: | |||
| (::Display*, ::Window, Bool, long, XEvent*), | |||
| Status) | |||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetClassHint, xSetClassHint, | |||
| (::Display*, ::Window, XClassHint*), | |||
| void) | |||
| JUCE_GENERATE_FUNCTION_WITH_DEFAULT (XSetErrorHandler, xSetErrorHandler, | |||
| (XErrorHandler), | |||
| XErrorHandler) | |||
| @@ -643,16 +643,13 @@ namespace Visuals | |||
| } | |||
| //================================= X11 - Bitmap =============================== | |||
| static std::unordered_map<::Window, int> shmPaintsPendingMap; | |||
| class XBitmapImage : public ImagePixelData | |||
| { | |||
| 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) | |||
| : ImagePixelData (format, w, h), | |||
| imageDepth (imageDepth_), | |||
| display (d) | |||
| imageDepth (imageDepth_) | |||
| { | |||
| jassert (format == Image::RGB || format == Image::ARGB); | |||
| @@ -807,6 +804,11 @@ public: | |||
| { | |||
| XWindowSystemUtilities::ScopedXLock xLock; | |||
| #if JUCE_USE_XSHM | |||
| if (isUsingXShm()) | |||
| XWindowSystem::getInstance()->addPendingPaintForWindow (window); | |||
| #endif | |||
| if (gc == None) | |||
| { | |||
| XGCValues gcvalues; | |||
| @@ -856,10 +858,7 @@ public: | |||
| // blit results to screen. | |||
| #if JUCE_USE_XSHM | |||
| if (isUsingXShm()) | |||
| { | |||
| X11Symbols::getInstance()->xShmPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh, True); | |||
| ++shmPaintsPendingMap[window]; | |||
| } | |||
| else | |||
| #endif | |||
| X11Symbols::getInstance()->xPutImage (display, (::Drawable) window, gc, xImage, sx, sy, dx, dy, dw, dh); | |||
| @@ -878,7 +877,7 @@ private: | |||
| int pixelStride, lineStride; | |||
| uint8* imageData = nullptr; | |||
| GC gc = None; | |||
| ::Display* display = nullptr; | |||
| ::Display* display = XWindowSystem::getInstance()->getDisplay(); | |||
| #if JUCE_USE_XSHM | |||
| XShmSegmentInfo segmentInfo; | |||
| @@ -1155,7 +1154,7 @@ namespace ClipboardHelpers | |||
| // translate to utf8 | |||
| numDataItems = localContent.getNumBytesAsUTF8() + 1; | |||
| data.calloc (numDataItems + 1); | |||
| data.calloc (numDataItems); | |||
| localContent.copyToUTF8 (data, numDataItems); | |||
| propertyFormat = 8; // bits/item | |||
| } | |||
| @@ -1165,7 +1164,7 @@ namespace ClipboardHelpers | |||
| numDataItems = 2; | |||
| propertyFormat = 32; // atoms are 32-bit | |||
| data.calloc (numDataItems * 4); | |||
| Atom* atoms = reinterpret_cast<Atom*> (data.getData()); | |||
| Atom* atoms = unalignedPointerCast<Atom*> (data.getData()); | |||
| atoms[0] = XWindowSystem::getInstance()->getAtoms().utf8String; | |||
| atoms[1] = XA_STRING; | |||
| @@ -1224,7 +1223,7 @@ ComponentPeer* getPeerFor (::Window windowH) | |||
| 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; | |||
| 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 | |||
| XSetWindowAttributes swa; | |||
| swa.border_pixel = 0; | |||
| @@ -1313,11 +1319,9 @@ static int getAllEventsMask (bool ignoresMouseClicks) | |||
| swa.override_redirect = ((styleFlags & ComponentPeer::windowIsTemporary) != 0) ? True : False; | |||
| 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, | |||
| 0, 0, 1, 1, | |||
| 0, depth, InputOutput, visual, | |||
| 0, visualAndDepth.depth, InputOutput, visualAndDepth.visual, | |||
| CWBorderPixel | CWColormap | CWBackPixmap | CWEventMask | CWOverrideRedirect, | |||
| &swa); | |||
| @@ -1334,12 +1338,28 @@ static int getAllEventsMask (bool ignoresMouseClicks) | |||
| } | |||
| // 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 | |||
| setWindowType (windowH, styleFlags); | |||
| @@ -1407,7 +1427,10 @@ void XWindowSystem::destroyWindow (::Window windowH) | |||
| &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) | |||
| 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); | |||
| } | |||
| @@ -1514,22 +1540,24 @@ void XWindowSystem::setBounds (::Window windowH, Rectangle<int> newBounds, bool | |||
| 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(); | |||
| @@ -1787,16 +1815,18 @@ bool XWindowSystem::canUseARGBImages() const | |||
| 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 | |||
| return Image (new XBitmapImage (display, argb ? Image::ARGB : Image::RGB, | |||
| return Image (new XBitmapImage (argb ? Image::ARGB : Image::RGB, | |||
| #else | |||
| return Image (new XBitmapImage (display, Image::RGB, | |||
| return Image (new XBitmapImage (Image::RGB, | |||
| #endif | |||
| (width + 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 | |||
| @@ -1812,20 +1842,47 @@ void XWindowSystem::blitToWindow (::Window windowH, Image image, Rectangle<int> | |||
| destinationRect.getX() - totalRect.getX(), destinationRect.getY() - totalRect.getY()); | |||
| } | |||
| int XWindowSystem::getNumPaintsPending (::Window windowH) const | |||
| void XWindowSystem::processPendingPaintsForWindow (::Window windowH) | |||
| { | |||
| #if JUCE_USE_XSHM | |||
| if (shmPaintsPendingMap[windowH] != 0) | |||
| if (! XSHMHelpers::isShmAvailable (display)) | |||
| return; | |||
| if (getNumPaintsPendingForWindow (windowH) > 0) | |||
| { | |||
| XWindowSystemUtilities::ScopedXLock xLock; | |||
| XEvent evt; | |||
| while (X11Symbols::getInstance()->xCheckTypedWindowEvent (display, windowH, shmCompletionEvent, &evt)) | |||
| --shmPaintsPendingMap[windowH]; | |||
| removePendingPaintForWindow (windowH); | |||
| } | |||
| #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 | |||
| @@ -2107,24 +2164,39 @@ ModifierKeys XWindowSystem::getNativeRealtimeModifiers() const | |||
| 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 | |||
| { | |||
| @@ -2137,11 +2209,13 @@ Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||
| 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; | |||
| 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) | |||
| { | |||
| @@ -2225,25 +2299,22 @@ Array<Displays::Display> XWindowSystem::findDisplays (float masterScale) const | |||
| if (displays.isEmpty()) | |||
| #endif | |||
| { | |||
| if (hints != None) | |||
| if (workAreaHints != None) | |||
| { | |||
| auto numMonitors = X11Symbols::getInstance()->xScreenCount (display); | |||
| 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; | |||
| d.totalArea = { (int) position[0], (int) position[1], | |||
| (int) position[2], (int) position[3] }; | |||
| d.totalArea = workArea; | |||
| d.isMain = displays.isEmpty(); | |||
| d.scale = masterScale; | |||
| d.dpi = DisplayHelpers::getDisplayDPI (display, i); | |||
| @@ -2675,6 +2746,40 @@ long XWindowSystem::getUserTime (::Window windowH) const | |||
| 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() | |||
| { | |||
| @@ -2708,7 +2813,8 @@ bool XWindowSystem::initialiseXDisplay() | |||
| // Create our message window (this will never be mapped) | |||
| 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, | |||
| X11Symbols::getInstance()->xDefaultVisual (display, screen), | |||
| CWEventMask, &swa); | |||
| @@ -2717,22 +2823,6 @@ bool XWindowSystem::initialiseXDisplay() | |||
| 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(); | |||
| updateModifierMappings(); | |||
| @@ -2741,6 +2831,14 @@ bool XWindowSystem::initialiseXDisplay() | |||
| shmCompletionEvent = X11Symbols::getInstance()->xShmGetEventBase (display) + ShmCompletion; | |||
| #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 | |||
| LinuxEventLoop::registerFdCallback (X11Symbols::getInstance()->xConnectionNumber (display), | |||
| [this] (int) | |||
| @@ -2788,10 +2886,10 @@ void XWindowSystem::destroyXDisplay() | |||
| X11Symbols::getInstance()->xSync (display, True); | |||
| LinuxEventLoop::unregisterFdCallback (X11Symbols::getInstance()->xConnectionNumber (display)); | |||
| visual = nullptr; | |||
| X11Symbols::getInstance()->xCloseDisplay (display); | |||
| display = nullptr; | |||
| displayVisuals = nullptr; | |||
| } | |||
| } | |||
| @@ -2870,7 +2968,7 @@ void XWindowSystem::handleWindowMessage (LinuxComponentPeer<::Window>* peer, XEv | |||
| XWindowSystemUtilities::ScopedXLock xLock; | |||
| if (event.xany.type == shmCompletionEvent) | |||
| --shmPaintsPendingMap[(::Window) peer->getNativeHandle()]; | |||
| XWindowSystem::getInstance()->removePendingPaintForWindow ((::Window) peer->getNativeHandle()); | |||
| } | |||
| #endif | |||
| break; | |||
| @@ -2978,7 +3076,7 @@ void XWindowSystem::handleKeyPressEvent (LinuxComponentPeer<::Window>* peer, XKe | |||
| if (sym >= XK_F1 && sym <= XK_F35) | |||
| { | |||
| keyPressed = true; | |||
| keyCode = (sym & 0xff) | Keys::extendedKeyModifier; | |||
| keyCode = static_cast<int> ((sym & 0xff) | Keys::extendedKeyModifier); | |||
| } | |||
| break; | |||
| } | |||
| @@ -126,9 +126,12 @@ public: | |||
| bool canUseSemiTransparentWindows() 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 setScreenSaverEnabled (bool enabled) const; | |||
| @@ -171,6 +174,24 @@ private: | |||
| ~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(); | |||
| void destroyXDisplay(); | |||
| @@ -217,10 +238,13 @@ private: | |||
| XWindowSystemUtilities::Atoms atoms; | |||
| ::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] = {}; | |||
| 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) | |||
| .withAlpha (usingDefault ? 0.4f : 1.0f)); | |||
| @@ -278,10 +278,11 @@ struct TextEditor::Iterator | |||
| Iterator (const TextEditor& ed) | |||
| : sections (ed.sections), | |||
| justification (ed.justification), | |||
| justificationWidth (ed.getJustificationWidth()), | |||
| bottomRight (ed.getMaximumWidth(), ed.getMaximumHeight()), | |||
| wordWrapWidth (ed.getWordWrapWidth()), | |||
| passwordCharacter (ed.passwordCharacter), | |||
| lineSpacing (ed.lineSpacing) | |||
| lineSpacing (ed.lineSpacing), | |||
| underlineWhitespace (ed.underlineWhitespace) | |||
| { | |||
| jassert (wordWrapWidth > 0); | |||
| @@ -292,6 +293,8 @@ struct TextEditor::Iterator | |||
| if (currentSection != nullptr) | |||
| beginNewLine(); | |||
| } | |||
| lineHeight = ed.currentFont.getHeight(); | |||
| } | |||
| Iterator (const Iterator&) = default; | |||
| @@ -325,7 +328,7 @@ struct TextEditor::Iterator | |||
| { | |||
| tempAtom.numChars = (uint16) split; | |||
| tempAtom.width = g.getGlyph (split - 1).getRight(); | |||
| atomX = getJustificationOffset (tempAtom.width); | |||
| atomX = getJustificationOffsetX (tempAtom.width); | |||
| atomRight = atomX + tempAtom.width; | |||
| return true; | |||
| } | |||
| @@ -427,14 +430,14 @@ struct TextEditor::Iterator | |||
| tempAtom.numChars = 0; | |||
| atom = &tempAtom; | |||
| if (atomX > justificationOffset) | |||
| if (atomX > justificationOffsetX) | |||
| beginNewLine(); | |||
| return next(); | |||
| } | |||
| beginNewLine(); | |||
| atomX = justificationOffset; | |||
| atomX = justificationOffsetX; | |||
| atomRight = atomX + atom->width; | |||
| return true; | |||
| } | |||
| @@ -494,25 +497,22 @@ struct TextEditor::Iterator | |||
| ++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; | |||
| } | |||
| //============================================================================== | |||
| 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) | |||
| { | |||
| @@ -527,7 +527,7 @@ struct TextEditor::Iterator | |||
| ga.addLineOfText (currentSection->font, | |||
| atom->getTrimmedText (passwordCharacter), | |||
| 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); | |||
| } | |||
| 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 endX = roundToInt (indexToX (underline.getEnd())); | |||
| auto baselineY = roundToInt (lineY + currentSection->font.getAscent() + 0.5f); | |||
| Graphics::ScopedSaveState state (g); | |||
| g.addTransform (transform); | |||
| g.reduceClipRegion ({ startX, baselineY, endX - startX, 1 }); | |||
| 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()) | |||
| { | |||
| @@ -566,7 +567,7 @@ struct TextEditor::Iterator | |||
| ga.removeRangeOfGlyphs (selected.getEnd() - indexInText, -1); | |||
| g.setColour (currentSection->colour); | |||
| ga2.draw (g); | |||
| ga2.draw (g, transform); | |||
| } | |||
| if (selected.getStart() > indexInText) | |||
| @@ -576,11 +577,11 @@ struct TextEditor::Iterator | |||
| ga.removeRangeOfGlyphs (0, selected.getStart() - indexInText); | |||
| g.setColour (currentSection->colour); | |||
| ga2.draw (g); | |||
| ga2.draw (g, transform); | |||
| } | |||
| g.setColour (selectedTextColour); | |||
| ga.draw (g); | |||
| ga.draw (g, transform); | |||
| } | |||
| } | |||
| @@ -649,20 +650,42 @@ struct TextEditor::Iterator | |||
| 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; | |||
| float lineY = 0, justificationOffset = 0, lineHeight = 0, maxDescent = 0; | |||
| float lineY = 0, lineHeight = 0, maxDescent = 0; | |||
| float atomX = 0, atomRight = 0; | |||
| const TextAtom* atom = nullptr; | |||
| const UniformTextSection* currentSection = nullptr; | |||
| private: | |||
| const OwnedArray<UniformTextSection>& sections; | |||
| const UniformTextSection* currentSection = nullptr; | |||
| int sectionIndex = 0, atomIndex = 0; | |||
| Justification justification; | |||
| const float justificationWidth, wordWrapWidth; | |||
| float justificationOffsetX = 0; | |||
| const Point<float> bottomRight; | |||
| const float wordWrapWidth; | |||
| const juce_wchar passwordCharacter; | |||
| const float lineSpacing; | |||
| const bool underlineWhitespace; | |||
| TextAtom tempAtom; | |||
| void moveToEndOfLastAtom() | |||
| @@ -673,7 +696,7 @@ private: | |||
| if (atom->isNewLine()) | |||
| { | |||
| atomX = 0.0f; | |||
| atomX = getJustificationOffsetX (0); | |||
| lineY += lineHeight * lineSpacing; | |||
| } | |||
| } | |||
| @@ -826,8 +849,8 @@ struct TextEditor::TextEditorViewport : public Viewport | |||
| 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(); | |||
| @@ -835,9 +858,8 @@ struct TextEditor::TextEditorViewport : public Viewport | |||
| { | |||
| 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: | |||
| TextEditor& owner; | |||
| float lastWordWrapWidth = 0; | |||
| bool rentrant = false; | |||
| bool reentrant = false; | |||
| JUCE_DECLARE_NON_COPYABLE (TextEditorViewport) | |||
| }; | |||
| @@ -936,8 +958,8 @@ void TextEditor::setMultiLine (const bool shouldBeMultiLine, | |||
| multiline = shouldBeMultiLine; | |||
| wordWrap = shouldWordWrap && shouldBeMultiLine; | |||
| viewport->setScrollBarsShown (scrollbarVisible && multiline, | |||
| scrollbarVisible && multiline); | |||
| checkLayout(); | |||
| viewport->setViewPosition (0, 0); | |||
| resized(); | |||
| scrollToMakeSureCursorIsVisible(); | |||
| @@ -954,8 +976,7 @@ void TextEditor::setScrollbarsShown (bool shown) | |||
| if (scrollbarVisible != shown) | |||
| { | |||
| scrollbarVisible = shown; | |||
| shown = shown && isMultiLine(); | |||
| viewport->setScrollBarsShown (shown, shown); | |||
| checkLayout(); | |||
| } | |||
| } | |||
| @@ -1003,7 +1024,9 @@ void TextEditor::setJustification (Justification j) | |||
| if (justification != j) | |||
| { | |||
| justification = j; | |||
| resized(); | |||
| repaint(); | |||
| } | |||
| } | |||
| @@ -1028,7 +1051,7 @@ void TextEditor::applyFontToAllText (const Font& newFont, bool changeCurrentFont | |||
| } | |||
| coalesceSimilarSections(); | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| scrollToMakeSureCursorIsVisible(); | |||
| repaint(); | |||
| } | |||
| @@ -1090,8 +1113,13 @@ void TextEditor::recreateCaret() | |||
| 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) | |||
| @@ -1146,7 +1174,7 @@ void TextEditor::setScrollBarThickness (int newThicknessPixels) | |||
| void TextEditor::clear() | |||
| { | |||
| clearInternal (nullptr); | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| undoManager.clearUndoHistory(); | |||
| } | |||
| @@ -1181,7 +1209,7 @@ void TextEditor::setText (const String& newText, bool sendTextChangeMessage) | |||
| else | |||
| textValue.addListener (textHolder); | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| scrollToMakeSureCursorIsVisible(); | |||
| undoManager.clearUndoHistory(); | |||
| @@ -1214,7 +1242,7 @@ void TextEditor::textWasChangedByValue() | |||
| //============================================================================== | |||
| void TextEditor::textChanged() | |||
| { | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| if (listeners.size() != 0 || onTextChange != nullptr) | |||
| postCommandMessage (TextEditorDefs::textChangeMessageId); | |||
| @@ -1259,30 +1287,33 @@ void TextEditor::repaintText (Range<int> range) | |||
| { | |||
| 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 | |||
| { | |||
| return wordWrap ? getJustificationWidth() | |||
| return wordWrap ? getMaximumWidth() | |||
| : std::numeric_limits<float>::max(); | |||
| } | |||
| float TextEditor::getJustificationWidth() const | |||
| float TextEditor::getMaximumWidth() const | |||
| { | |||
| 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) | |||
| { | |||
| float maxWidth = getJustificationWidth(); | |||
| auto maxWidth = getMaximumWidth(); | |||
| Iterator i (*this); | |||
| while (i.next()) | |||
| 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) | |||
| { | |||
| leftIndent = newLeftIndent; | |||
| topIndent = newTopIndent; | |||
| if (leftIndent != newLeftIndent || topIndent != newTopIndent) | |||
| { | |||
| leftIndent = newLeftIndent; | |||
| topIndent = newTopIndent; | |||
| resized(); | |||
| repaint(); | |||
| } | |||
| } | |||
| 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) | |||
| { | |||
| Iterator i (*this); | |||
| 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) | |||
| @@ -1576,8 +1648,19 @@ void TextEditor::drawContent (Graphics& g) | |||
| { | |||
| g.setOrigin (leftIndent, topIndent); | |||
| 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); | |||
| Colour selectedTextColour; | |||
| 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); | |||
| g.setColour (findColour (highlightColourId).withMultipliedAlpha (hasKeyboardFocus (true) ? 1.0f : 0.5f)); | |||
| g.fillPath (selectionArea.toPath(), transform); | |||
| } | |||
| const UniformTextSection* lastSection = nullptr; | |||
| @@ -1607,12 +1690,12 @@ void TextEditor::drawContent (Graphics& g) | |||
| { | |||
| if (selection.intersects ({ i.indexInText, i.indexInText + i.atom->numChars })) | |||
| { | |||
| i.drawSelectedText (g, selection, selectedTextColour); | |||
| i.drawSelectedText (g, selection, selectedTextColour, transform); | |||
| lastSection = nullptr; | |||
| } | |||
| 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() | |||
| && 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.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); | |||
| @@ -2055,7 +2135,7 @@ bool TextEditor::keyStateChanged (const bool isKeyDown) | |||
| } | |||
| //============================================================================== | |||
| void TextEditor::focusGained (FocusChangeType) | |||
| void TextEditor::focusGained (FocusChangeType cause) | |||
| { | |||
| newTransaction(); | |||
| @@ -2067,6 +2147,9 @@ void TextEditor::focusGained (FocusChangeType) | |||
| checkFocus(); | |||
| if (cause == FocusChangeType::focusChangedByMouseClick && selectAllTextWhenFocused) | |||
| wasFocused = false; | |||
| repaint(); | |||
| updateCaretPosition(); | |||
| } | |||
| @@ -2095,7 +2178,7 @@ void TextEditor::resized() | |||
| viewport->setBoundsInset (borderSize); | |||
| viewport->setSingleStepSizes (16, roundToInt (currentFont.getHeight())); | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| if (isMultiLine()) | |||
| updateCaretPosition(); | |||
| @@ -2213,7 +2296,7 @@ void TextEditor::insert (const String& text, int insertIndex, const Font& font, | |||
| totalNumChars = -1; | |||
| valueTextNeedsUpdating = true; | |||
| updateTextHolderSize(); | |||
| checkLayout(); | |||
| moveCaretTo (caretPositionToMoveTo, false); | |||
| repaintText ({ insertIndex, getTotalNumChars() }); | |||
| @@ -2426,7 +2509,7 @@ void TextEditor::getCharPosition (int index, Point<float>& anchor, float& lineHe | |||
| if (sections.isEmpty()) | |||
| { | |||
| anchor = { i.getJustificationOffset (0), 0 }; | |||
| anchor = { i.getJustificationOffsetX (0), 0 }; | |||
| lineHeight = currentFont.getHeight(); | |||
| } | |||
| else | |||
| @@ -155,7 +155,6 @@ public: | |||
| */ | |||
| bool areScrollbarsShown() const noexcept { return scrollbarVisible; } | |||
| /** Changes the password character used to disguise the text. | |||
| @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; } | |||
| //============================================================================== | |||
| /** Allows a right-click menu to appear for the editor. | |||
| @@ -251,7 +249,7 @@ public: | |||
| @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. | |||
| @@ -260,6 +258,18 @@ public: | |||
| */ | |||
| 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. | |||
| @@ -495,9 +505,12 @@ public: | |||
| */ | |||
| 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); | |||
| /** Returns the type of justification, as set in setJustification(). */ | |||
| Justification getJustificationType() const noexcept { return justification; } | |||
| /** 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 | |||
| 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; | |||
| TextHolderComponent* textHolder; | |||
| BorderSize<int> borderSize { 1, 1, 1, 3 }; | |||
| Justification justification { Justification::left }; | |||
| Justification justification { Justification::topLeft }; | |||
| bool readOnly = false; | |||
| bool caretVisible = true; | |||
| @@ -728,6 +741,7 @@ private: | |||
| bool menuActive = false; | |||
| bool valueTextNeedsUpdating = false; | |||
| bool consumeEscAndReturnKeys = true; | |||
| bool underlineWhitespace = true; | |||
| UndoManager undoManager; | |||
| std::unique_ptr<CaretComponent> caret; | |||
| @@ -778,9 +792,12 @@ private: | |||
| int findWordBreakBefore (int position) const; | |||
| bool moveCaretWithTransaction (int newPos, bool selecting); | |||
| void drawContent (Graphics&); | |||
| void updateTextHolderSize(); | |||
| void checkLayout(); | |||
| void updateTextHolderSize (int, int); | |||
| void updateScrollbarVisibility (int, int); | |||
| float getWordWrapWidth() const; | |||
| float getJustificationWidth() const; | |||
| float getMaximumWidth() const; | |||
| float getMaximumHeight() const; | |||
| void timerCallbackInt(); | |||
| void checkFocus(); | |||
| void repaintText (Range<int>); | |||
| @@ -40,9 +40,7 @@ CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const pare | |||
| else | |||
| { | |||
| setAlwaysOnTop (juce_areThereAnyAlwaysOnTopWindows()); | |||
| updatePosition (area, Desktop::getInstance().getDisplays().findDisplayForRect (area).userArea); | |||
| addToDesktop (ComponentPeer::windowIsTemporary); | |||
| startTimer (100); | |||
| @@ -51,15 +49,14 @@ CallOutBox::CallOutBox (Component& c, Rectangle<int> area, Component* const pare | |||
| creationTime = Time::getCurrentTime(); | |||
| } | |||
| CallOutBox::~CallOutBox() = default; | |||
| //============================================================================== | |||
| class CallOutBoxCallback : public ModalComponentManager::Callback, | |||
| private Timer | |||
| { | |||
| 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.enterModalState (true, this); | |||
| @@ -80,11 +77,11 @@ public: | |||
| 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! | |||
| 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); | |||
| } | |||
| void CallOutBox::lookAndFeelChanged() { resized(); repaint(); } | |||
| void CallOutBox::lookAndFeelChanged() | |||
| { | |||
| resized(); | |||
| repaint(); | |||
| } | |||
| void CallOutBox::paint (Graphics& g) | |||
| { | |||
| @@ -158,7 +159,7 @@ void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept | |||
| dismissalMouseClicksAreAlwaysConsumed = b; | |||
| } | |||
| enum { callOutBoxDismissCommandId = 0x4f83a04b }; | |||
| static constexpr int callOutBoxDismissCommandId = 0x4f83a04b; | |||
| void CallOutBox::handleCommandMessage (int commandId) | |||
| { | |||
| @@ -193,9 +194,8 @@ void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const R | |||
| availableArea = newAreaToFitIn; | |||
| 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 hh = newBounds.getHeight() / 2; | |||
| @@ -250,7 +250,7 @@ void CallOutBox::refreshPath() | |||
| 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(), | |||
| targetPoint - getPosition().toFloat(), | |||
| getLookAndFeel().getCallOutBoxCornerSize (*this), arrowSize * 0.7f); | |||
| @@ -43,11 +43,12 @@ namespace juce | |||
| @code | |||
| void mouseUp (const MouseEvent&) | |||
| { | |||
| FoobarContentComp* content = new FoobarContentComp(); | |||
| auto content = std::make_unique<FoobarContentComp>(); | |||
| content->setSize (300, 300); | |||
| CallOutBox& myBox | |||
| = CallOutBox::launchAsynchronously (content, getScreenBounds(), nullptr); | |||
| auto& myBox = CallOutBox::launchAsynchronously (std::move (content), | |||
| getScreenBounds(), | |||
| nullptr); | |||
| } | |||
| @endcode | |||
| @@ -77,9 +78,6 @@ public: | |||
| Rectangle<int> areaToPointTo, | |||
| Component* parentComponent); | |||
| /** Destructor. */ | |||
| ~CallOutBox() override; | |||
| //============================================================================== | |||
| /** Changes the base width of the arrow. */ | |||
| void setArrowSize (float newSize); | |||
| @@ -109,15 +107,13 @@ public: | |||
| @param contentComponent the component to display inside the call-out. This should | |||
| already have a size set (although the call-out will also | |||
| 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 | |||
| a parentComponent is supplied, then this is relative to that | |||
| parent; otherwise, it's a global screen coord. | |||
| @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. | |||
| */ | |||
| static CallOutBox& launchAsynchronously (Component* contentComponent, | |||
| static CallOutBox& launchAsynchronously (std::unique_ptr<Component> contentComponent, | |||
| Rectangle<int> areaToPointTo, | |||
| Component* parentComponent); | |||
| @@ -402,6 +402,16 @@ Rectangle<int> ComponentPeer::globalToLocal (const Rectangle<int>& screenPositio | |||
| 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 | |||
| { | |||
| return ScalingHelpers::scaledScreenPosToUnscaled | |||