@@ -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 | |||