@@ -397,7 +397,7 @@ SUBGROUPING = YES | |||||
# SEPARATE_MEMBER_PAGES. | # SEPARATE_MEMBER_PAGES. | ||||
# The default value is: NO. | # The default value is: NO. | ||||
INLINE_GROUPED_CLASSES = YES | |||||
INLINE_GROUPED_CLASSES = NO | |||||
# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions | # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions | ||||
# with only public data fields or simple typedef fields will be shown inline in | # with only public data fields or simple typedef fields will be shown inline in | ||||
@@ -25,7 +25,7 @@ | |||||
#if JUCE_INTEL | #if JUCE_INTEL | ||||
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; | #define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; | ||||
#else | #else | ||||
#define JUCE_SNAP_TO_ZERO(n) | |||||
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
@@ -36,6 +36,15 @@ struct CoreAudioLayouts | |||||
return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout)); | return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout)); | ||||
} | } | ||||
/** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet. | |||||
Note that this method cannot preserve the order of channels. | |||||
*/ | |||||
static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag) | |||||
{ | |||||
return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag)); | |||||
} | |||||
/** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag. | /** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag. | ||||
Note that this method cannot preserve the order of channels. | Note that this method cannot preserve the order of channels. | ||||
@@ -67,7 +76,7 @@ struct CoreAudioLayouts | |||||
/** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */ | /** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */ | ||||
static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout) | static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout) | ||||
{ | { | ||||
switch (layout.mChannelLayoutTag) | |||||
switch (layout.mChannelLayoutTag & 0xffff0000) | |||||
{ | { | ||||
case kAudioChannelLayoutTag_UseChannelBitmap: | case kAudioChannelLayoutTag_UseChannelBitmap: | ||||
return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes(); | return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes(); | ||||
@@ -94,30 +103,12 @@ struct CoreAudioLayouts | |||||
return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag); | return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag); | ||||
} | } | ||||
//============================================================================== | |||||
/* Convert between a CoreAudio and JUCE channel indices - and vice versa. */ | |||||
// TODO: Fabian remove this | |||||
// static int convertChannelIndex (const AudioChannelLayout& caLayout, const AudioChannelSet& juceLayout, int index, bool fromJUCE) | |||||
// { | |||||
// auto coreAudioChannels = getCoreAudioLayoutChannels (caLayout); | |||||
// | |||||
// jassert (juceLayout.size() == coreAudioChannels.size()); | |||||
// jassert (index >= 0 && index < juceLayout.size()); | |||||
// | |||||
// return (fromJUCE ? coreAudioChannels.indexOf (juceLayout.getTypeOfChannel (index)) | |||||
// : juceLayout.getChannelIndexForType (coreAudioChannels.getReference (index))); | |||||
// } | |||||
private: | |||||
//============================================================================== | |||||
struct LayoutTagSpeakerList | |||||
{ | |||||
AudioChannelLayoutTag tag; | |||||
AudioChannelSet::ChannelType channelTypes[16]; | |||||
}; | |||||
static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag) | static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag) | ||||
{ | { | ||||
// You need to specify the full AudioChannelLayout when using | |||||
// the UseChannelBitmap and UseChannelDescriptions layout tag | |||||
jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions); | |||||
Array<AudioChannelSet::ChannelType> speakers; | Array<AudioChannelSet::ChannelType> speakers; | ||||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | ||||
@@ -139,6 +130,14 @@ private: | |||||
return speakers; | return speakers; | ||||
} | } | ||||
private: | |||||
//============================================================================== | |||||
struct LayoutTagSpeakerList | |||||
{ | |||||
AudioChannelLayoutTag tag; | |||||
AudioChannelSet::ChannelType channelTypes[16]; | |||||
}; | |||||
static Array<AudioChannelLayoutTag> createKnownCoreAudioTags() | static Array<AudioChannelLayoutTag> createKnownCoreAudioTags() | ||||
{ | { | ||||
Array<AudioChannelLayoutTag> tags; | Array<AudioChannelLayoutTag> tags; | ||||
@@ -24,20 +24,8 @@ SynthesiserSound::SynthesiserSound() {} | |||||
SynthesiserSound::~SynthesiserSound() {} | SynthesiserSound::~SynthesiserSound() {} | ||||
//============================================================================== | //============================================================================== | ||||
SynthesiserVoice::SynthesiserVoice() | |||||
: currentSampleRate (44100.0), | |||||
currentlyPlayingNote (-1), | |||||
currentPlayingMidiChannel (0), | |||||
noteOnTime (0), | |||||
keyIsDown (false), | |||||
sustainPedalDown (false), | |||||
sostenutoPedalDown (false) | |||||
{ | |||||
} | |||||
SynthesiserVoice::~SynthesiserVoice() | |||||
{ | |||||
} | |||||
SynthesiserVoice::SynthesiserVoice() {} | |||||
SynthesiserVoice::~SynthesiserVoice() {} | |||||
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const | ||||
{ | { | ||||
@@ -83,11 +71,6 @@ void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer, | |||||
//============================================================================== | //============================================================================== | ||||
Synthesiser::Synthesiser() | Synthesiser::Synthesiser() | ||||
: sampleRate (0), | |||||
lastNoteOnCounter (0), | |||||
minimumSubBlockSize (32), | |||||
subBlockSubdivisionIsStrict (false), | |||||
shouldStealNotes (true) | |||||
{ | { | ||||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | ||||
lastPitchWheelValues[i] = 0x2000; | lastPitchWheelValues[i] = 0x2000; | ||||
@@ -159,13 +142,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||||
if (sampleRate != newRate) | if (sampleRate != newRate) | ||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
allNotesOff (0, false); | allNotesOff (0, false); | ||||
sampleRate = newRate; | sampleRate = newRate; | ||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate); | |||||
for (auto* voice : voices) | |||||
voice->setCurrentPlaybackSampleRate (newRate); | |||||
} | } | ||||
} | } | ||||
@@ -230,25 +211,19 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
} | } | ||||
// explicit template instantiation | // explicit template instantiation | ||||
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>& outputAudio, | |||||
const MidiBuffer& midiData, | |||||
int startSample, | |||||
int numSamples); | |||||
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>& outputAudio, | |||||
const MidiBuffer& midiData, | |||||
int startSample, | |||||
int numSamples); | |||||
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int); | |||||
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int); | |||||
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples) | void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples) | ||||
{ | { | ||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||||
for (auto* voice : voices) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | } | ||||
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples) | void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples) | ||||
{ | { | ||||
for (int i = voices.size(); --i >= 0;) | |||||
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||||
for (auto* voice : voices) | |||||
voice->renderNextBlock (buffer, startSample, numSamples); | |||||
} | } | ||||
void Synthesiser::handleMidiEvent (const MidiMessage& m) | void Synthesiser::handleMidiEvent (const MidiMessage& m) | ||||
@@ -298,23 +273,15 @@ void Synthesiser::noteOn (const int midiChannel, | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = sounds.size(); --i >= 0;) | |||||
for (auto* sound : sounds) | |||||
{ | { | ||||
SynthesiserSound* const sound = sounds.getUnchecked(i); | |||||
if (sound->appliesToNote (midiNoteNumber) | |||||
&& sound->appliesToChannel (midiChannel)) | |||||
if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) | |||||
{ | { | ||||
// If hitting a note that's still ringing, stop it first (it could be | // If hitting a note that's still ringing, stop it first (it could be | ||||
// still playing because of the sustain or sostenuto pedal). | // still playing because of the sustain or sostenuto pedal). | ||||
for (int j = voices.size(); --j >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (j); | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||||
&& voice->isPlayingChannel (midiChannel)) | |||||
for (auto* voice : voices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) | |||||
stopVoice (voice, 1.0f, true); | stopVoice (voice, 1.0f, true); | ||||
} | |||||
startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | ||||
sound, midiChannel, midiNoteNumber, velocity); | sound, midiChannel, midiNoteNumber, velocity); | ||||
@@ -337,7 +304,7 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||||
voice->currentPlayingMidiChannel = midiChannel; | voice->currentPlayingMidiChannel = midiChannel; | ||||
voice->noteOnTime = ++lastNoteOnCounter; | voice->noteOnTime = ++lastNoteOnCounter; | ||||
voice->currentlyPlayingSound = sound; | voice->currentlyPlayingSound = sound; | ||||
voice->keyIsDown = true; | |||||
voice->setKeyDown (true); | |||||
voice->sostenutoPedalDown = false; | voice->sostenutoPedalDown = false; | ||||
voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | ||||
@@ -363,10 +330,8 @@ void Synthesiser::noteOff (const int midiChannel, | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
for (auto* voice : voices) | |||||
{ | { | ||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | if (voice->getCurrentlyPlayingNote() == midiNoteNumber | ||||
&& voice->isPlayingChannel (midiChannel)) | && voice->isPlayingChannel (midiChannel)) | ||||
{ | { | ||||
@@ -377,7 +342,7 @@ void Synthesiser::noteOff (const int midiChannel, | |||||
{ | { | ||||
jassert (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel]); | jassert (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel]); | ||||
voice->keyIsDown = false; | |||||
voice->setKeyDown (false); | |||||
if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | ||||
stopVoice (voice, velocity, allowTailOff); | stopVoice (voice, velocity, allowTailOff); | ||||
@@ -391,13 +356,9 @@ void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | ||||
voice->stopNote (1.0f, allowTailOff); | voice->stopNote (1.0f, allowTailOff); | ||||
} | |||||
sustainPedalsDown.clear(); | sustainPedalsDown.clear(); | ||||
} | } | ||||
@@ -406,13 +367,9 @@ void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | ||||
voice->pitchWheelMoved (wheelValue); | voice->pitchWheelMoved (wheelValue); | ||||
} | |||||
} | } | ||||
void Synthesiser::handleController (const int midiChannel, | void Synthesiser::handleController (const int midiChannel, | ||||
@@ -429,40 +386,28 @@ void Synthesiser::handleController (const int midiChannel, | |||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | ||||
voice->controllerMoved (controllerNumber, controllerValue); | voice->controllerMoved (controllerNumber, controllerValue); | ||||
} | |||||
} | } | ||||
void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | ||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber | if (voice->getCurrentlyPlayingNote() == midiNoteNumber | ||||
&& (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | ||||
voice->aftertouchChanged (aftertouchValue); | voice->aftertouchChanged (aftertouchValue); | ||||
} | |||||
} | } | ||||
void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) | void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) | ||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | ||||
voice->channelPressureChanged (channelPressureValue); | voice->channelPressureChanged (channelPressureValue); | ||||
} | |||||
} | } | ||||
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | ||||
@@ -474,25 +419,19 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||||
{ | { | ||||
sustainPedalsDown.setBit (midiChannel); | sustainPedalsDown.setBit (midiChannel); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) | ||||
voice->sustainPedalDown = true; | voice->sustainPedalDown = true; | ||||
} | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
for (int i = voices.size(); --i >= 0;) | |||||
for (auto* voice : voices) | |||||
{ | { | ||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->isPlayingChannel (midiChannel)) | if (voice->isPlayingChannel (midiChannel)) | ||||
{ | { | ||||
voice->sustainPedalDown = false; | voice->sustainPedalDown = false; | ||||
if (! voice->isKeyDown()) | |||||
if (! (voice->isKeyDown() || voice->isSostenutoPedalDown())) | |||||
stopVoice (voice, 1.0f, true); | stopVoice (voice, 1.0f, true); | ||||
} | } | ||||
} | } | ||||
@@ -506,10 +445,8 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||||
jassert (midiChannel > 0 && midiChannel <= 16); | jassert (midiChannel > 0 && midiChannel <= 16); | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = voices.size(); --i >= 0;) | |||||
for (auto* voice : voices) | |||||
{ | { | ||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->isPlayingChannel (midiChannel)) | if (voice->isPlayingChannel (midiChannel)) | ||||
{ | { | ||||
if (isDown) | if (isDown) | ||||
@@ -539,13 +476,9 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = 0; i < voices.size(); ++i) | |||||
{ | |||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
for (auto* voice : voices) | |||||
if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) | if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) | ||||
return voice; | return voice; | ||||
} | |||||
if (stealIfNoneAvailable) | if (stealIfNoneAvailable) | ||||
return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | ||||
@@ -569,7 +502,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | ||||
// apparently you are trying to render audio without having any voices... | // apparently you are trying to render audio without having any voices... | ||||
jassert (voices.size() > 0); | |||||
jassert (! voices.isEmpty()); | |||||
// These are the voices we want to protect (ie: only steal if unavoidable) | // These are the voices we want to protect (ie: only steal if unavoidable) | ||||
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | ||||
@@ -579,10 +512,8 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
Array<SynthesiserVoice*> usableVoices; | Array<SynthesiserVoice*> usableVoices; | ||||
usableVoices.ensureStorageAllocated (voices.size()); | usableVoices.ensureStorageAllocated (voices.size()); | ||||
for (int i = 0; i < voices.size(); ++i) | |||||
for (auto* voice : voices) | |||||
{ | { | ||||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||||
if (voice->canPlaySound (soundToPlay)) | if (voice->canPlaySound (soundToPlay)) | ||||
{ | { | ||||
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | ||||
@@ -592,7 +523,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
if (! voice->isPlayingButReleased()) // Don't protect released notes | if (! voice->isPlayingButReleased()) // Don't protect released notes | ||||
{ | { | ||||
const int note = voice->getCurrentlyPlayingNote(); | |||||
auto note = voice->getCurrentlyPlayingNote(); | |||||
if (low == nullptr || note < low->getCurrentlyPlayingNote()) | if (low == nullptr || note < low->getCurrentlyPlayingNote()) | ||||
low = voice; | low = voice; | ||||
@@ -607,43 +538,25 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||||
if (top == low) | if (top == low) | ||||
top = nullptr; | top = nullptr; | ||||
const int numUsableVoices = usableVoices.size(); | |||||
// The oldest note that's playing with the target pitch is ideal.. | // The oldest note that's playing with the target pitch is ideal.. | ||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
for (auto* voice : usableVoices) | |||||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | ||||
return voice; | return voice; | ||||
} | |||||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | // Oldest voice that has been released (no finger on it and not held by sustain pedal) | ||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top && voice->isPlayingButReleased()) | if (voice != low && voice != top && voice->isPlayingButReleased()) | ||||
return voice; | return voice; | ||||
} | |||||
// Oldest voice that doesn't have a finger on it: | // Oldest voice that doesn't have a finger on it: | ||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top && ! voice->isKeyDown()) | if (voice != low && voice != top && ! voice->isKeyDown()) | ||||
return voice; | return voice; | ||||
} | |||||
// Oldest voice that isn't protected | // Oldest voice that isn't protected | ||||
for (int i = 0; i < numUsableVoices; ++i) | |||||
{ | |||||
SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||||
for (auto* voice : usableVoices) | |||||
if (voice != low && voice != top) | if (voice != low && voice != top) | ||||
return voice; | return voice; | ||||
} | |||||
// We've only got "protected" voices now: lowest note takes priority | // We've only got "protected" voices now: lowest note takes priority | ||||
jassert (low != nullptr); | jassert (low != nullptr); | ||||
@@ -182,6 +182,8 @@ public: | |||||
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer, | virtual void renderNextBlock (AudioBuffer<float>& outputBuffer, | ||||
int startSample, | int startSample, | ||||
int numSamples) = 0; | int numSamples) = 0; | ||||
/** A double-precision version of renderNextBlock() */ | |||||
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | ||||
int startSample, | int startSample, | ||||
int numSamples); | int numSamples); | ||||
@@ -214,6 +216,11 @@ public: | |||||
*/ | */ | ||||
bool isKeyDown() const noexcept { return keyIsDown; } | bool isKeyDown() const noexcept { return keyIsDown; } | ||||
/** Allows you to modify the flag indicating that the key that triggered this voice is still held down. | |||||
@see isKeyDown | |||||
*/ | |||||
void setKeyDown (bool isNowDown) noexcept { keyIsDown = isNowDown; } | |||||
/** Returns true if the sustain pedal is currently active for this voice. */ | /** Returns true if the sustain pedal is currently active for this voice. */ | ||||
bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | ||||
@@ -249,11 +256,11 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
friend class Synthesiser; | friend class Synthesiser; | ||||
double currentSampleRate; | |||||
int currentlyPlayingNote, currentPlayingMidiChannel; | |||||
uint32 noteOnTime; | |||||
double currentSampleRate = 44100.0; | |||||
int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0; | |||||
uint32 noteOnTime = 0; | |||||
SynthesiserSound::Ptr currentlyPlayingSound; | SynthesiserSound::Ptr currentlyPlayingSound; | ||||
bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||||
bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false; | |||||
AudioBuffer<float> tempBuffer; | AudioBuffer<float> tempBuffer; | ||||
@@ -615,11 +622,11 @@ private: | |||||
int startSample, | int startSample, | ||||
int numSamples); | int numSamples); | ||||
//============================================================================== | //============================================================================== | ||||
double sampleRate; | |||||
uint32 lastNoteOnCounter; | |||||
int minimumSubBlockSize; | |||||
bool subBlockSubdivisionIsStrict; | |||||
bool shouldStealNotes; | |||||
double sampleRate = 0; | |||||
uint32 lastNoteOnCounter = 0; | |||||
int minimumSubBlockSize = 32; | |||||
bool subBlockSubdivisionIsStrict = false; | |||||
bool shouldStealNotes = true; | |||||
BigInteger sustainPedalsDown; | BigInteger sustainPedalsDown; | ||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | ||||
@@ -651,11 +651,16 @@ public: | |||||
{ | { | ||||
OpenSLSession::stop(); | OpenSLSession::stop(); | ||||
while (! guard.compareAndSetBool (1, 0)) | |||||
Thread::sleep (1); | |||||
if (inputChannels > 0) | if (inputChannels > 0) | ||||
recorder->setState (false); | recorder->setState (false); | ||||
if (outputChannels > 0) | if (outputChannels > 0) | ||||
player->setState (false); | player->setState (false); | ||||
guard.set (0); | |||||
} | } | ||||
bool setAudioPreprocessingEnabled (bool shouldEnable) override | bool setAudioPreprocessingEnabled (bool shouldEnable) override | ||||
@@ -576,7 +576,13 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, | |||||
&hostUrl, | &hostUrl, | ||||
&dataSize); | &dataSize); | ||||
if (err == noErr) | if (err == noErr) | ||||
[[UIApplication sharedApplication] openURL:(NSURL*)hostUrl]; | |||||
{ | |||||
#if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) | |||||
[[UIApplication sharedApplication] openURL: (NSURL*)hostUrl]; | |||||
#else | |||||
[[UIApplication sharedApplication] openURL: (NSURL*)hostUrl options: @{} completionHandler: nil]; | |||||
#endif | |||||
} | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -357,7 +357,8 @@ public: | |||||
actualBufferSize (0), | actualBufferSize (0), | ||||
bytesPerSample (0), | bytesPerSample (0), | ||||
bytesPerFrame (0), | bytesPerFrame (0), | ||||
sampleRateHasChanged (false) | |||||
sampleRateHasChanged (false), | |||||
shouldClose (false) | |||||
{ | { | ||||
clientEvent = CreateEvent (nullptr, false, false, nullptr); | clientEvent = CreateEvent (nullptr, false, false, nullptr); | ||||
@@ -468,6 +469,11 @@ public: | |||||
sampleRateHasChanged = true; | sampleRateHasChanged = true; | ||||
} | } | ||||
void deviceBecameInactive() | |||||
{ | |||||
shouldClose = true; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
ComSmartPtr<IMMDevice> device; | ComSmartPtr<IMMDevice> device; | ||||
ComSmartPtr<IAudioClient> client; | ComSmartPtr<IAudioClient> client; | ||||
@@ -482,7 +488,7 @@ public: | |||||
Array<int> channelMaps; | Array<int> channelMaps; | ||||
UINT32 actualBufferSize; | UINT32 actualBufferSize; | ||||
int bytesPerSample, bytesPerFrame; | int bytesPerSample, bytesPerFrame; | ||||
bool sampleRateHasChanged; | |||||
bool sampleRateHasChanged, shouldClose; | |||||
virtual void updateFormat (bool isFloat) = 0; | virtual void updateFormat (bool isFloat) = 0; | ||||
@@ -498,10 +504,17 @@ private: | |||||
JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; } | JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; } | ||||
JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; } | JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, LPCGUID) { return S_OK; } | ||||
JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; } | JUCE_COMRESULT OnGroupingParamChanged (LPCGUID, LPCGUID) { return S_OK; } | ||||
JUCE_COMRESULT OnStateChanged (AudioSessionState) { return S_OK; } | |||||
JUCE_COMRESULT OnStateChanged(AudioSessionState state) | |||||
{ | |||||
if (state == AudioSessionStateInactive || state == AudioSessionStateExpired) | |||||
owner.deviceBecameInactive(); | |||||
return S_OK; | |||||
} | |||||
JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason) | JUCE_COMRESULT OnSessionDisconnected (AudioSessionDisconnectReason reason) | ||||
{ | { | ||||
Logger::writeToLog("OnSessionDisconnected"); | |||||
if (reason == DisconnectReasonFormatChanged) | if (reason == DisconnectReasonFormatChanged) | ||||
owner.deviceSampleRateChanged(); | owner.deviceSampleRateChanged(); | ||||
@@ -969,7 +982,8 @@ public: | |||||
isStarted (false), | isStarted (false), | ||||
currentBufferSizeSamples (0), | currentBufferSizeSamples (0), | ||||
currentSampleRate (0), | currentSampleRate (0), | ||||
callback (nullptr) | |||||
callback (nullptr), | |||||
deviceBecameInactive (false) | |||||
{ | { | ||||
} | } | ||||
@@ -1107,6 +1121,8 @@ public: | |||||
if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); | if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); | ||||
if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); | if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); | ||||
deviceBecameInactive = false; | |||||
startThread (8); | startThread (8); | ||||
Thread::sleep (5); | Thread::sleep (5); | ||||
@@ -1226,8 +1242,14 @@ public: | |||||
while (! threadShouldExit()) | while (! threadShouldExit()) | ||||
{ | { | ||||
if (inputDevice != nullptr) | |||||
if (outputDevice != nullptr && outputDevice->shouldClose) | |||||
deviceBecameInactive = true; | |||||
if (inputDevice != nullptr && ! deviceBecameInactive) | |||||
{ | { | ||||
if (inputDevice->shouldClose) | |||||
deviceBecameInactive = true; | |||||
if (outputDevice == nullptr) | if (outputDevice == nullptr) | ||||
{ | { | ||||
if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT) | if (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT) | ||||
@@ -1253,6 +1275,7 @@ public: | |||||
} | } | ||||
} | } | ||||
if (! deviceBecameInactive) | |||||
{ | { | ||||
const ScopedTryLock sl (startStopLock); | const ScopedTryLock sl (startStopLock); | ||||
@@ -1263,7 +1286,7 @@ public: | |||||
outs.clear(); | outs.clear(); | ||||
} | } | ||||
if (outputDevice != nullptr) | |||||
if (outputDevice != nullptr && !deviceBecameInactive) | |||||
{ | { | ||||
// Note that this function is handed the input device so it can check for the event and make sure | // Note that this function is handed the input device so it can check for the event and make sure | ||||
// the input reservoir is filled up correctly even when bufferSize > device actualBufferSize | // the input reservoir is filled up correctly even when bufferSize > device actualBufferSize | ||||
@@ -1276,7 +1299,7 @@ public: | |||||
} | } | ||||
} | } | ||||
if (sampleRateHasChanged) | |||||
if (sampleRateHasChanged || deviceBecameInactive) | |||||
{ | { | ||||
triggerAsyncUpdate(); | triggerAsyncUpdate(); | ||||
break; // Quit the thread... will restart it later! | break; // Quit the thread... will restart it later! | ||||
@@ -1303,7 +1326,7 @@ private: | |||||
bool isOpen_, isStarted; | bool isOpen_, isStarted; | ||||
int currentBufferSizeSamples; | int currentBufferSizeSamples; | ||||
double currentSampleRate; | double currentSampleRate; | ||||
bool sampleRateChangedByOutput; | |||||
bool sampleRateChangedByOutput, deviceBecameInactive; | |||||
AudioIODeviceCallback* callback; | AudioIODeviceCallback* callback; | ||||
CriticalSection startStopLock; | CriticalSection startStopLock; | ||||
@@ -1354,12 +1377,17 @@ private: | |||||
outputDevice = nullptr; | outputDevice = nullptr; | ||||
inputDevice = nullptr; | inputDevice = nullptr; | ||||
initialise(); | |||||
open (lastKnownInputChannels, lastKnownOutputChannels, | |||||
getChangedSampleRate(), currentBufferSizeSamples); | |||||
// sample rate change | |||||
if (! deviceBecameInactive) | |||||
{ | |||||
initialise(); | |||||
open (lastKnownInputChannels, lastKnownOutputChannels, | |||||
getChangedSampleRate(), currentBufferSizeSamples); | |||||
start (callback); | |||||
start (callback); | |||||
} | |||||
} | } | ||||
double getChangedSampleRate() const | double getChangedSampleRate() const | ||||
@@ -1481,7 +1509,7 @@ private: | |||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } | HRESULT STDMETHODCALLTYPE OnDeviceAdded (LPCWSTR) { return notify(); } | ||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } | HRESULT STDMETHODCALLTYPE OnDeviceRemoved (LPCWSTR) { return notify(); } | ||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged (LPCWSTR, DWORD) { return notify(); } | |||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR, DWORD) { return notify(); } | |||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } | HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged (EDataFlow, ERole, LPCWSTR) { return notify(); } | ||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } | HRESULT STDMETHODCALLTYPE OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } | ||||
@@ -619,8 +619,7 @@ public: | |||||
for (auto tagEntry : knownTags) | for (auto tagEntry : knownTags) | ||||
{ | { | ||||
AudioChannelLayout layout { tagEntry.tag }; | |||||
auto labels = CoreAudioLayouts::fromCoreAudio (layout); | |||||
auto labels = CoreAudioLayouts::fromCoreAudio (tagEntry.tag); | |||||
expect (! labels.isDiscreteLayout(), String ("Tag \"") + String (tagEntry.name) + "\" is not handled by JUCE"); | expect (! labels.isDiscreteLayout(), String ("Tag \"") + String (tagEntry.name) + "\" is not handled by JUCE"); | ||||
} | } | ||||
@@ -631,8 +630,7 @@ public: | |||||
for (auto tagEntry : knownTags) | for (auto tagEntry : knownTags) | ||||
{ | { | ||||
AudioChannelLayout layout { tagEntry.tag }; | |||||
auto labels = CoreAudioLayouts::getCoreAudioLayoutChannels (layout); | |||||
auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag); | |||||
expect (labels.size() == (tagEntry.tag & 0xffff), String ("Tag \"") + String (tagEntry.name) + "\" has incorrect channel count"); | expect (labels.size() == (tagEntry.tag & 0xffff), String ("Tag \"") + String (tagEntry.name) + "\" has incorrect channel count"); | ||||
} | } | ||||
@@ -643,8 +641,7 @@ public: | |||||
for (auto tagEntry : knownTags) | for (auto tagEntry : knownTags) | ||||
{ | { | ||||
AudioChannelLayout layout { tagEntry.tag }; | |||||
auto labels = CoreAudioLayouts::getCoreAudioLayoutChannels (layout); | |||||
auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag); | |||||
labels.sort(); | labels.sort(); | ||||
for (int i = 0; i < (labels.size() - 1); ++i) | for (int i = 0; i < (labels.size() - 1); ++i) | ||||
@@ -657,13 +654,9 @@ public: | |||||
beginTest ("CA speaker list and juce layouts are consistent"); | beginTest ("CA speaker list and juce layouts are consistent"); | ||||
for (auto tagEntry : knownTags) | for (auto tagEntry : knownTags) | ||||
{ | |||||
AudioChannelLayout layout { tagEntry.tag }; | |||||
expect (AudioChannelSet::channelSetWithChannels (CoreAudioLayouts::getCoreAudioLayoutChannels (layout)) | |||||
== CoreAudioLayouts::fromCoreAudio (layout), | |||||
expect (AudioChannelSet::channelSetWithChannels (CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag)) | |||||
== CoreAudioLayouts::fromCoreAudio (tagEntry.tag), | |||||
String ("Tag \"") + String (tagEntry.name) + "\" is not converted consistantly by JUCE"); | String ("Tag \"") + String (tagEntry.name) + "\" is not converted consistantly by JUCE"); | ||||
} | |||||
} | } | ||||
{ | { | ||||
@@ -674,9 +667,7 @@ public: | |||||
if (tagEntry.equivalentChannelSet.isDisabled()) | if (tagEntry.equivalentChannelSet.isDisabled()) | ||||
continue; | continue; | ||||
AudioChannelLayout layout { tagEntry.tag }; | |||||
expect (CoreAudioLayouts::fromCoreAudio (layout) == tagEntry.equivalentChannelSet, | |||||
expect (CoreAudioLayouts::fromCoreAudio (tagEntry.tag) == tagEntry.equivalentChannelSet, | |||||
String ("Documentation for tag \"") + String (tagEntry.name) + "\" is incorrect"); | String ("Documentation for tag \"") + String (tagEntry.name) + "\" is incorrect"); | ||||
} | } | ||||
} | } | ||||
@@ -942,6 +942,13 @@ namespace AAXClasses | |||||
{ | { | ||||
if (type == AAX_eNotificationEvent_EnteringOfflineMode) pluginInstance->setNonRealtime (true); | if (type == AAX_eNotificationEvent_EnteringOfflineMode) pluginInstance->setNonRealtime (true); | ||||
if (type == AAX_eNotificationEvent_ExitingOfflineMode) pluginInstance->setNonRealtime (false); | if (type == AAX_eNotificationEvent_ExitingOfflineMode) pluginInstance->setNonRealtime (false); | ||||
if (type == AAX_eNotificationEvent_TrackNameChanged && data != nullptr) | |||||
{ | |||||
AudioProcessor::TrackProperties props; | |||||
props.name = static_cast<const AAX_IString*> (data)->Get(); | |||||
pluginInstance->updateTrackProperties (props); | |||||
} | |||||
return AAX_CEffectParameters::NotificationReceived (type, data, size); | return AAX_CEffectParameters::NotificationReceived (type, data, size); | ||||
} | } | ||||
@@ -147,6 +147,8 @@ public: | |||||
channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); | channelInfo = AudioUnitHelpers::getAUChannelInfo (*juceFilter); | ||||
#endif | #endif | ||||
AddPropertyListener (kAudioUnitProperty_ContextName, auPropertyListenerDispatcher, this); | |||||
totalInChannels = juceFilter->getTotalNumInputChannels(); | totalInChannels = juceFilter->getTotalNumInputChannels(); | ||||
totalOutChannels = juceFilter->getTotalNumOutputChannels(); | totalOutChannels = juceFilter->getTotalNumOutputChannels(); | ||||
@@ -1818,12 +1820,7 @@ private: | |||||
if (numChannels != tagNumChannels) | if (numChannels != tagNumChannels) | ||||
return kAudioUnitErr_FormatNotSupported; | return kAudioUnitErr_FormatNotSupported; | ||||
AudioChannelLayout layout; | |||||
zerostruct (layout); | |||||
layout.mChannelLayoutTag = currentLayoutTag; | |||||
requestedBuses.add (CoreAudioLayouts::fromCoreAudio (layout)); | |||||
requestedBuses.add (CoreAudioLayouts::fromCoreAudio (currentLayoutTag)); | |||||
} | } | ||||
} | } | ||||
@@ -1909,7 +1906,7 @@ private: | |||||
auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); | auto& knownTags = CoreAudioLayouts::getKnownCoreAudioTags(); | ||||
for (auto tag : knownTags) | for (auto tag : knownTags) | ||||
if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (AudioChannelLayout {tag}))) | |||||
if (bus->isLayoutSupported (CoreAudioLayouts::fromCoreAudio (tag))) | |||||
tags.addIfNotAlreadyThere (tag); | tags.addIfNotAlreadyThere (tag); | ||||
#endif | #endif | ||||
@@ -1961,6 +1958,25 @@ private: | |||||
return (getHostType().isLogic() ? 8 : 64); | return (getHostType().isLogic() ? 8 : 64); | ||||
} | } | ||||
//============================================================================== | |||||
void auPropertyListener (AudioUnitPropertyID propId, AudioUnitScope scope, AudioUnitElement) | |||||
{ | |||||
if (scope == kAudioUnitScope_Global && propId == kAudioUnitProperty_ContextName | |||||
&& juceFilter != nullptr && mContextName != nullptr) | |||||
{ | |||||
AudioProcessor::TrackProperties props; | |||||
props.name = String::fromCFString (mContextName); | |||||
juceFilter->updateTrackProperties (props); | |||||
} | |||||
} | |||||
static void auPropertyListenerDispatcher (void* inRefCon, AudioUnit, AudioUnitPropertyID propId, | |||||
AudioUnitScope scope, AudioUnitElement element) | |||||
{ | |||||
static_cast<JuceAU*> (inRefCon)->auPropertyListener (propId, scope, element); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (JuceAU) | JUCE_DECLARE_NON_COPYABLE (JuceAU) | ||||
}; | }; | ||||
@@ -37,12 +37,20 @@ | |||||
#if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) | #if (! defined MAC_OS_X_VERSION_MIN_REQUIRED) || (! defined MAC_OS_X_VERSION_10_11) || (MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_11) | ||||
#error AUv3 needs Deployment Target OS X 10.11 or higher to compile | #error AUv3 needs Deployment Target OS X 10.11 or higher to compile | ||||
#endif | #endif | ||||
#if (defined MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13) | |||||
#define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 | |||||
#define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 | |||||
#endif | |||||
#endif | #endif | ||||
#if JUCE_IOS | #if JUCE_IOS | ||||
#if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_9_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) | #if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_9_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0) | ||||
#error AUv3 needs Deployment Target iOS 9.0 or higher to compile | #error AUv3 needs Deployment Target iOS 9.0 or higher to compile | ||||
#endif | #endif | ||||
#if (defined __IPHONE_11_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0) | |||||
#define JUCE_AUV3_MIDI_OUTPUT_SUPPORTED 1 | |||||
#define JUCE_AUV3_VIEW_CONFIG_SUPPORTED 1 | |||||
#endif | |||||
#endif | #endif | ||||
#ifndef __OBJC2__ | #ifndef __OBJC2__ | ||||
@@ -97,6 +105,15 @@ struct AudioProcessorHolder : public ReferenceCountedObject | |||||
AudioProcessor* operator->() noexcept { return processor; } | AudioProcessor* operator->() noexcept { return processor; } | ||||
AudioProcessor* get() noexcept { return processor; } | AudioProcessor* get() noexcept { return processor; } | ||||
struct ViewConfig | |||||
{ | |||||
double width; | |||||
double height; | |||||
bool hostHasMIDIController; | |||||
}; | |||||
ScopedPointer<ViewConfig> viewConfiguration; | |||||
typedef ReferenceCountedObjectPtr<AudioProcessorHolder> Ptr; | typedef ReferenceCountedObjectPtr<AudioProcessorHolder> Ptr; | ||||
private: | private: | ||||
@@ -121,8 +138,7 @@ public: | |||||
error: error | error: error | ||||
juceClass: this]) | juceClass: this]) | ||||
#pragma clang diagnostic pop | #pragma clang diagnostic pop | ||||
{ | |||||
} | |||||
{} | |||||
JuceAudioUnitv3Base (AUAudioUnit* audioUnit) : au (audioUnit) | JuceAudioUnitv3Base (AUAudioUnit* audioUnit) : au (audioUnit) | ||||
{ | { | ||||
@@ -134,39 +150,14 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
AUAudioUnit* getAudioUnit() noexcept { return au; } | AUAudioUnit* getAudioUnit() noexcept { return au; } | ||||
virtual int getVirtualMIDICableCount() { return 0; } | |||||
virtual void reset() {} | |||||
virtual bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* bus) | |||||
{ | |||||
objc_super s = { getAudioUnit(), [AUAudioUnit class] }; | |||||
return (ObjCMsgSendSuper<BOOL, AVAudioFormat*,AUAudioUnitBus* > (&s, @selector (shouldChangeToFormat:forBus:), format, bus) == YES); | |||||
} | |||||
virtual AUAudioUnitPreset* getCurrentPreset() { return nullptr; } | |||||
virtual void setCurrentPreset(AUAudioUnitPreset*) {} | |||||
virtual NSTimeInterval getLatency() { return 0.0; } | |||||
virtual NSTimeInterval getTailTime() { return 0.0; } | |||||
virtual bool getCanProcessInPlace() { return false; } | |||||
virtual bool getRenderingOffline() { return false; } | |||||
//============================================================================== | //============================================================================== | ||||
virtual AUAudioUnitBusArray* getInputBusses() = 0; | |||||
virtual AUAudioUnitBusArray* getOutputBusses() = 0; | |||||
virtual AUParameterTree* getParameterTree() = 0; | |||||
virtual AUInternalRenderBlock getInternalRenderBlock() = 0; | |||||
virtual void setRenderingOffline (bool offline) = 0; | |||||
virtual NSArray<NSNumber*> *getChannelCapabilities() = 0; | |||||
virtual void reset() = 0; | |||||
//============================================================================== | //============================================================================== | ||||
virtual NSArray<NSNumber*>* parametersForOverviewWithCount (int) | |||||
{ | |||||
return [NSArray<NSNumber*> array]; | |||||
} | |||||
virtual NSArray<AUAudioUnitPreset*>* getFactoryPresets() | |||||
{ | |||||
return [NSArray<AUAudioUnitPreset*> array]; | |||||
} | |||||
virtual AUAudioUnitPreset* getCurrentPreset() = 0; | |||||
virtual void setCurrentPreset(AUAudioUnitPreset*) = 0; | |||||
virtual NSArray<AUAudioUnitPreset*>* getFactoryPresets() = 0; | |||||
virtual NSDictionary<NSString*, id>* getFullState() | virtual NSDictionary<NSString*, id>* getFullState() | ||||
{ | { | ||||
@@ -180,6 +171,34 @@ public: | |||||
ObjCMsgSendSuper<void, NSDictionary<NSString*, id>*> (&s, @selector (setFullState:), state); | ObjCMsgSendSuper<void, NSDictionary<NSString*, id>*> (&s, @selector (setFullState:), state); | ||||
} | } | ||||
virtual AUParameterTree* getParameterTree() = 0; | |||||
virtual NSArray<NSNumber*>* parametersForOverviewWithCount (int) = 0; | |||||
//============================================================================== | |||||
virtual NSTimeInterval getLatency() = 0; | |||||
virtual NSTimeInterval getTailTime() = 0; | |||||
//============================================================================== | |||||
virtual AUAudioUnitBusArray* getInputBusses() = 0; | |||||
virtual AUAudioUnitBusArray* getOutputBusses() = 0; | |||||
virtual NSArray<NSNumber*>* getChannelCapabilities() = 0; | |||||
virtual bool shouldChangeToFormat (AVAudioFormat*, AUAudioUnitBus*) = 0; | |||||
//============================================================================== | |||||
virtual int getVirtualMIDICableCount() = 0; | |||||
virtual bool getSupportsMPE() = 0; | |||||
virtual NSArray<NSString*>* getMIDIOutputNames() = 0; | |||||
//============================================================================== | |||||
virtual AUInternalRenderBlock getInternalRenderBlock() = 0; | |||||
virtual bool getCanProcessInPlace() { return false; } | |||||
virtual bool getRenderingOffline() = 0; | |||||
virtual void setRenderingOffline (bool offline) = 0; | |||||
//============================================================================== | |||||
virtual NSString* getContextName() const = 0; | |||||
virtual void setContextName (NSString*) = 0; | |||||
virtual bool allocateRenderResourcesAndReturnError (NSError **outError) | virtual bool allocateRenderResourcesAndReturnError (NSError **outError) | ||||
{ | { | ||||
objc_super s = { getAudioUnit(), [AUAudioUnit class] }; | objc_super s = { getAudioUnit(), [AUAudioUnit class] }; | ||||
@@ -192,6 +211,12 @@ public: | |||||
ObjCMsgSendSuper<void> (&s, @selector (deallocateRenderResources)); | ObjCMsgSendSuper<void> (&s, @selector (deallocateRenderResources)); | ||||
} | } | ||||
//============================================================================== | |||||
#if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||||
virtual NSIndexSet* getSupportedViewConfigurations (NSArray<AUAudioUnitViewConfiguration*>*) = 0; | |||||
virtual void selectViewConfiguration (AUAudioUnitViewConfiguration*) = 0; | |||||
#endif | |||||
private: | private: | ||||
struct Class : public ObjCClass<AUAudioUnit> | struct Class : public ObjCClass<AUAudioUnit> | ||||
{ | { | ||||
@@ -212,31 +237,55 @@ private: | |||||
@encode (AudioComponentDescription), | @encode (AudioComponentDescription), | ||||
@encode (AudioComponentInstantiationOptions), "^@"); | @encode (AudioComponentInstantiationOptions), "^@"); | ||||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||||
addMethod (@selector (inputBusses), getInputBusses, "@@:"); | |||||
addMethod (@selector (outputBusses), getOutputBusses, "@@:"); | |||||
addMethod (@selector (parameterTree), getParameterTree, "@@:"); | |||||
addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); | |||||
addMethod (@selector (reset), reset, "v@:"); | |||||
addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "B@:@@"); | |||||
addMethod (@selector (factoryPresets), getFactoryPresets, "@@:"); | |||||
addMethod (@selector (currentPreset), getCurrentPreset, "@@:"); | |||||
addMethod (@selector (setCurrentPreset:), setCurrentPreset, "v@:@"); | |||||
addMethod (@selector (fullState), getFullState, "@@:"); | |||||
addMethod (@selector (setFullState:), setFullState, "v@:@"); | |||||
addMethod (@selector (channelCapabilities), getChannelCapabilities, "@@:"); | |||||
addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); | |||||
addMethod (@selector (dealloc), dealloc, "v@:"); | |||||
//============================================================================== | |||||
addMethod (@selector (reset), reset, "v@:"); | |||||
//============================================================================== | |||||
addMethod (@selector (currentPreset), getCurrentPreset, "@@:"); | |||||
addMethod (@selector (setCurrentPreset:), setCurrentPreset, "v@:@"); | |||||
addMethod (@selector (factoryPresets), getFactoryPresets, "@@:"); | |||||
addMethod (@selector (fullState), getFullState, "@@:"); | |||||
addMethod (@selector (setFullState:), setFullState, "v@:@"); | |||||
addMethod (@selector (parameterTree), getParameterTree, "@@:"); | |||||
addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger)); | addMethod (@selector (parametersForOverviewWithCount:), parametersForOverviewWithCount, "@@:", @encode (NSInteger)); | ||||
//============================================================================== | |||||
addMethod (@selector (latency), getLatency, @encode (NSTimeInterval), "@:"); | |||||
addMethod (@selector (tailTime), getTailTime, @encode (NSTimeInterval), "@:"); | |||||
//============================================================================== | |||||
addMethod (@selector (inputBusses), getInputBusses, "@@:"); | |||||
addMethod (@selector (outputBusses), getOutputBusses, "@@:"); | |||||
addMethod (@selector (channelCapabilities), getChannelCapabilities, "@@:"); | |||||
addMethod (@selector (shouldChangeToFormat:forBus:), shouldChangeToFormat, "B@:@@"); | |||||
//============================================================================== | |||||
addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); | |||||
addMethod (@selector (supportsMPE), getSupportsMPE, @encode (BOOL), "@:"); | |||||
#if JUCE_AUV3_MIDI_OUTPUT_SUPPORTED | |||||
addMethod (@selector (MIDIOutputNames), getMIDIOutputNames, "@@:"); | |||||
#endif | |||||
//============================================================================== | |||||
addMethod (@selector (internalRenderBlock), getInternalRenderBlock, @encode (AUInternalRenderBlock), "@:"); | |||||
addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); | |||||
addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); | |||||
addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); | addMethod (@selector (setRenderingOffline:), setRenderingOffline, "v@:", @encode (BOOL)); | ||||
addMethod (@selector (allocateRenderResourcesAndReturnError:), allocateRenderResourcesAndReturnError, "B@:^@"); | |||||
addMethod (@selector (deallocateRenderResources), deallocateRenderResources, "v@:"); | |||||
//============================================================================== | |||||
addMethod (@selector (contextName), getContextName, "@@:"); | |||||
addMethod (@selector (setContextName:), setContextName, "v@:@"); | |||||
addMethod (@selector (internalRenderBlock), getInternalRenderBlock, @encode (AUInternalRenderBlock), "@:"); | |||||
addMethod (@selector (virtualMIDICableCount), getVirtualMIDICableCount, @encode (NSInteger), "@:"); | |||||
addMethod (@selector (latency), getLatency, @encode (NSTimeInterval), "@:"); | |||||
addMethod (@selector (tailTime), getTailTime, @encode (NSTimeInterval), "@:"); | |||||
addMethod (@selector (canProcessInPlace), getCanProcessInPlace, @encode (BOOL), "@:"); | |||||
addMethod (@selector (isRenderingOffline), getRenderingOffline, @encode (BOOL), "@:"); | |||||
//============================================================================== | |||||
#if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||||
addMethod (@selector (supportedViewConfigurations:), getSupportedViewConfigurations, "@@:@"); | |||||
addMethod (@selector (selectViewConfiguration:), selectViewConfiguration, "v@:@"); | |||||
#endif | |||||
registerClass(); | registerClass(); | ||||
} | } | ||||
@@ -274,27 +323,51 @@ private: | |||||
} | } | ||||
static void dealloc (id self, SEL) { delete _this (self); } | static void dealloc (id self, SEL) { delete _this (self); } | ||||
static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } | |||||
static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } | |||||
static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } | |||||
static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } | |||||
static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } | |||||
static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } | |||||
//============================================================================== | |||||
static void reset (id self, SEL) { _this (self)->reset(); } | static void reset (id self, SEL) { _this (self)->reset(); } | ||||
static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } | |||||
static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } | |||||
static NSArray<NSNumber*>* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast<int> (count)); } | |||||
static NSArray<AUAudioUnitPreset*>* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } | |||||
//============================================================================== | |||||
static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } | static AUAudioUnitPreset* getCurrentPreset (id self, SEL) { return _this (self)->getCurrentPreset(); } | ||||
static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } | static void setCurrentPreset (id self, SEL, AUAudioUnitPreset* preset) { return _this (self)->setCurrentPreset (preset); } | ||||
static NSArray<AUAudioUnitPreset*>* getFactoryPresets (id self, SEL) { return _this (self)->getFactoryPresets(); } | |||||
static NSDictionary<NSString*, id>* getFullState (id self, SEL) { return _this (self)->getFullState(); } | static NSDictionary<NSString*, id>* getFullState (id self, SEL) { return _this (self)->getFullState(); } | ||||
static void setFullState (id self, SEL, NSDictionary<NSString *, id>* state) { return _this (self)->setFullState (state); } | static void setFullState (id self, SEL, NSDictionary<NSString *, id>* state) { return _this (self)->setFullState (state); } | ||||
static AUParameterTree* getParameterTree (id self, SEL) { return _this (self)->getParameterTree(); } | |||||
static NSArray<NSNumber*>* parametersForOverviewWithCount (id self, SEL, NSInteger count) { return _this (self)->parametersForOverviewWithCount (static_cast<int> (count)); } | |||||
//============================================================================== | |||||
static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } | static NSTimeInterval getLatency (id self, SEL) { return _this (self)->getLatency(); } | ||||
static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } | static NSTimeInterval getTailTime (id self, SEL) { return _this (self)->getTailTime(); } | ||||
//============================================================================== | |||||
static AUAudioUnitBusArray* getInputBusses (id self, SEL) { return _this (self)->getInputBusses(); } | |||||
static AUAudioUnitBusArray* getOutputBusses (id self, SEL) { return _this (self)->getOutputBusses(); } | |||||
static NSArray<NSNumber*>* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } | |||||
static BOOL shouldChangeToFormat (id self, SEL, AVAudioFormat* format, AUAudioUnitBus* bus) { return _this (self)->shouldChangeToFormat (format, bus) ? YES : NO; } | |||||
//============================================================================== | |||||
static NSInteger getVirtualMIDICableCount (id self, SEL) { return _this (self)->getVirtualMIDICableCount(); } | |||||
static BOOL getSupportsMPE (id self, SEL) { return _this (self)->getSupportsMPE() ? YES : NO; } | |||||
static NSArray<NSString*>* getMIDIOutputNames (id self, SEL) { return _this (self)->getMIDIOutputNames(); } | |||||
//============================================================================== | |||||
static AUInternalRenderBlock getInternalRenderBlock (id self, SEL) { return _this (self)->getInternalRenderBlock(); } | |||||
static BOOL getCanProcessInPlace (id self, SEL) { return _this (self)->getCanProcessInPlace() ? YES : NO; } | static BOOL getCanProcessInPlace (id self, SEL) { return _this (self)->getCanProcessInPlace() ? YES : NO; } | ||||
static BOOL getRenderingOffline (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; } | static BOOL getRenderingOffline (id self, SEL) { return _this (self)->getRenderingOffline() ? YES : NO; } | ||||
static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } | static void setRenderingOffline (id self, SEL, BOOL renderingOffline) { _this (self)->setRenderingOffline (renderingOffline); } | ||||
static NSArray<NSNumber*>* getChannelCapabilities (id self, SEL) { return _this (self)->getChannelCapabilities(); } | |||||
static BOOL allocateRenderResourcesAndReturnError (id self, SEL, NSError** error) { return _this (self)->allocateRenderResourcesAndReturnError (error) ? YES : NO; } | |||||
static void deallocateRenderResources (id self, SEL) { _this (self)->deallocateRenderResources(); } | |||||
//============================================================================== | |||||
static NSString* getContextName (id self, SEL) { return _this (self)->getContextName(); } | |||||
static void setContextName (id self, SEL, NSString* str) { return _this (self)->setContextName (str); } | |||||
//============================================================================== | |||||
#if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||||
static NSIndexSet* getSupportedViewConfigurations (id self, SEL, NSArray<AUAudioUnitViewConfiguration*>* configs) { return _this (self)->getSupportedViewConfigurations (configs); } | |||||
static void selectViewConfiguration (id self, SEL, AUAudioUnitViewConfiguration* config) { _this (self)->selectViewConfiguration (config); } | |||||
#endif | |||||
}; | }; | ||||
static JuceAudioUnitv3Base* create (AUAudioUnit*, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**); | static JuceAudioUnitv3Base* create (AUAudioUnit*, AudioComponentDescription, AudioComponentInstantiationOptions, NSError**); | ||||
@@ -410,16 +483,14 @@ public: | |||||
addAudioUnitBusses (false); | addAudioUnitBusses (false); | ||||
} | } | ||||
//============================================================================== | |||||
AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } | AudioProcessor& getAudioProcessor() const noexcept { return **processorHolder; } | ||||
AUAudioUnitBusArray* getInputBusses() override { return inputBusses; } | |||||
AUAudioUnitBusArray* getOutputBusses() override { return outputBusses; } | |||||
AUParameterTree* getParameterTree() override { return paramTree; } | |||||
AUInternalRenderBlock getInternalRenderBlock() override { return internalRenderBlock; } | |||||
NSArray<AUAudioUnitPreset*>* getFactoryPresets() override { return factoryPresets; } | |||||
bool getRenderingOffline() override { return getAudioProcessor().isNonRealtime(); } | |||||
void setRenderingOffline (bool offline) override { getAudioProcessor().setNonRealtime (offline); } | |||||
NSArray<NSNumber*>* getChannelCapabilities() override { return channelCapabilities; } | |||||
//============================================================================== | |||||
void reset() override | |||||
{ | |||||
midiMessages.clear(); | |||||
lastTimeStamp.mSampleTime = std::numeric_limits<Float64>::max(); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
AUAudioUnitPreset* getCurrentPreset() override | AUAudioUnitPreset* getCurrentPreset() override | ||||
@@ -442,7 +513,11 @@ public: | |||||
getAudioProcessor().setCurrentProgram (idx); | getAudioProcessor().setCurrentProgram (idx); | ||||
} | } | ||||
//============================================================================== | |||||
NSArray<AUAudioUnitPreset*>* getFactoryPresets() override | |||||
{ | |||||
return factoryPresets; | |||||
} | |||||
NSDictionary<NSString*, id>* getFullState() override | NSDictionary<NSString*, id>* getFullState() override | ||||
{ | { | ||||
NSMutableDictionary<NSString*, id>* retval = [[NSMutableDictionary<NSString*, id> alloc] init]; | NSMutableDictionary<NSString*, id>* retval = [[NSMutableDictionary<NSString*, id> alloc] init]; | ||||
@@ -508,7 +583,11 @@ public: | |||||
[modifiedState release]; | [modifiedState release]; | ||||
} | } | ||||
//============================================================================== | |||||
AUParameterTree* getParameterTree() override | |||||
{ | |||||
return paramTree; | |||||
} | |||||
NSArray<NSNumber*>* parametersForOverviewWithCount (int count) override | NSArray<NSNumber*>* parametersForOverviewWithCount (int count) override | ||||
{ | { | ||||
const int n = static_cast<int> ([overviewParams count]); | const int n = static_cast<int> ([overviewParams count]); | ||||
@@ -522,6 +601,68 @@ public: | |||||
return [retval autorelease]; | return [retval autorelease]; | ||||
} | } | ||||
//============================================================================== | |||||
NSTimeInterval getLatency() override | |||||
{ | |||||
auto& p = getAudioProcessor(); | |||||
return p.getLatencySamples() / p.getSampleRate(); | |||||
} | |||||
NSTimeInterval getTailTime() override | |||||
{ | |||||
return getAudioProcessor().getTailLengthSeconds(); | |||||
} | |||||
//============================================================================== | |||||
AUAudioUnitBusArray* getInputBusses() override { return inputBusses; } | |||||
AUAudioUnitBusArray* getOutputBusses() override { return outputBusses; } | |||||
NSArray<NSNumber*>* getChannelCapabilities() override { return channelCapabilities; } | |||||
bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override | |||||
{ | |||||
const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); | |||||
const int busIdx = static_cast<int> ([auBus index]); | |||||
const int newNumChannels = static_cast<int> ([format channelCount]); | |||||
AudioProcessor& processor = getAudioProcessor(); | |||||
if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) | |||||
{ | |||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
ignoreUnused (bus); | |||||
short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; | |||||
if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) | |||||
return false; | |||||
#else | |||||
const AVAudioChannelLayout* layout = [format channelLayout]; | |||||
const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); | |||||
if (layoutTag != 0) | |||||
{ | |||||
AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); | |||||
if (newLayout.size() != newNumChannels) | |||||
return false; | |||||
if (! bus->isLayoutSupported (newLayout)) | |||||
return false; | |||||
} | |||||
else | |||||
{ | |||||
if (! bus->isNumberOfChannelsSupported (newNumChannels)) | |||||
return false; | |||||
} | |||||
#endif | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
int getVirtualMIDICableCount() override | int getVirtualMIDICableCount() override | ||||
{ | { | ||||
#if JucePlugin_WantsMidiInput | #if JucePlugin_WantsMidiInput | ||||
@@ -531,6 +672,38 @@ public: | |||||
#endif | #endif | ||||
} | } | ||||
bool getSupportsMPE() override | |||||
{ | |||||
return getAudioProcessor().supportsMPE(); | |||||
} | |||||
NSArray<NSString*>* getMIDIOutputNames() override | |||||
{ | |||||
#if JucePlugin_ProducesMidiOutput | |||||
return @[@"MIDI Out"]; | |||||
#else | |||||
return @[]; | |||||
#endif | |||||
} | |||||
//============================================================================== | |||||
AUInternalRenderBlock getInternalRenderBlock() override { return internalRenderBlock; } | |||||
bool getRenderingOffline() override { return getAudioProcessor().isNonRealtime(); } | |||||
void setRenderingOffline (bool offline) override { getAudioProcessor().setNonRealtime (offline); } | |||||
//============================================================================== | |||||
NSString* getContextName() const override { return juceStringToNS (contextName); } | |||||
void setContextName (NSString* str) override | |||||
{ | |||||
if (str != nullptr) | |||||
{ | |||||
AudioProcessor::TrackProperties props; | |||||
props.name = nsStringToJuce (str); | |||||
getAudioProcessor().updateTrackProperties (props); | |||||
} | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
bool allocateRenderResourcesAndReturnError (NSError **outError) override | bool allocateRenderResourcesAndReturnError (NSError **outError) override | ||||
{ | { | ||||
@@ -563,13 +736,7 @@ public: | |||||
const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); | const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); | ||||
if (layoutTag != 0) | if (layoutTag != 0) | ||||
{ | |||||
AudioChannelLayout caLayout; | |||||
zerostruct (caLayout); | |||||
caLayout.mChannelLayoutTag = layoutTag; | |||||
newLayout = CoreAudioLayouts::fromCoreAudio (caLayout); | |||||
} | |||||
newLayout = CoreAudioLayouts::fromCoreAudio (layoutTag); | |||||
else | else | ||||
newLayout = bus->supportedLayoutWithChannels (static_cast<int> ([format channelCount])); | newLayout = bus->supportedLayoutWithChannels (static_cast<int> ([format channelCount])); | ||||
@@ -641,60 +808,45 @@ public: | |||||
JuceAudioUnitv3Base::deallocateRenderResources(); | JuceAudioUnitv3Base::deallocateRenderResources(); | ||||
} | } | ||||
void reset() override | |||||
{ | |||||
midiMessages.clear(); | |||||
lastTimeStamp.mSampleTime = std::numeric_limits<Float64>::max(); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
bool shouldChangeToFormat (AVAudioFormat* format, AUAudioUnitBus* auBus) override | |||||
#if JUCE_AUV3_VIEW_CONFIG_SUPPORTED | |||||
NSIndexSet* getSupportedViewConfigurations (NSArray<AUAudioUnitViewConfiguration*>* configs) override | |||||
{ | { | ||||
const bool isInput = ([auBus busType] == AUAudioUnitBusTypeInput); | |||||
const int busIdx = static_cast<int> ([auBus index]); | |||||
const int newNumChannels = static_cast<int> ([format channelCount]); | |||||
auto supportedViewIndecies = [[NSMutableIndexSet alloc] init]; | |||||
auto n = [configs count]; | |||||
AudioProcessor& processor = getAudioProcessor(); | |||||
if (AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) | |||||
if (auto* editor = getAudioProcessor().createEditorIfNeeded()) | |||||
{ | { | ||||
#ifdef JucePlugin_PreferredChannelConfigurations | |||||
ignoreUnused (bus); | |||||
short configs[][2] = {JucePlugin_PreferredChannelConfigurations}; | |||||
// If you hit this assertion then your plug-in's editor is reporting that it doesn't support | |||||
// any host MIDI controller configurations! | |||||
jassert (editor->supportsHostMIDIControllerPresence (true) || editor->supportsHostMIDIControllerPresence (false)); | |||||
if (! AudioUnitHelpers::isLayoutSupported (processor, isInput, busIdx, newNumChannels, configs)) | |||||
return false; | |||||
#else | |||||
const AVAudioChannelLayout* layout = [format channelLayout]; | |||||
const AudioChannelLayoutTag layoutTag = (layout != nullptr ? [layout layoutTag] : 0); | |||||
if (layoutTag != 0) | |||||
{ | |||||
AudioChannelLayout caLayout; | |||||
zerostruct (caLayout); | |||||
caLayout.mChannelLayoutTag = layoutTag; | |||||
AudioChannelSet newLayout = CoreAudioLayouts::fromCoreAudio (caLayout); | |||||
if (newLayout.size() != newNumChannels) | |||||
return false; | |||||
if (! bus->isLayoutSupported (newLayout)) | |||||
return false; | |||||
} | |||||
else | |||||
for (auto i = 0u; i < n; ++i) | |||||
{ | { | ||||
if (! bus->isNumberOfChannelsSupported (newNumChannels)) | |||||
return false; | |||||
if (auto* viewConfiguration = [configs objectAtIndex:i]) | |||||
{ | |||||
if (editor->supportsHostMIDIControllerPresence ([viewConfiguration hostHasController] == YES)) | |||||
{ | |||||
auto* constrainer = editor->getConstrainer(); | |||||
auto height = (int) [viewConfiguration height]; | |||||
auto width = (int) [viewConfiguration width]; | |||||
if (height <= constrainer->getMaximumHeight() && height >= constrainer->getMinimumHeight() | |||||
&& width <= constrainer->getMaximumWidth() && width >= constrainer->getMinimumWidth()) | |||||
[supportedViewIndecies addIndex: i]; | |||||
} | |||||
} | |||||
} | } | ||||
#endif | |||||
return true; | |||||
} | } | ||||
return false; | |||||
return [supportedViewIndecies autorelease]; | |||||
} | |||||
void selectViewConfiguration (AUAudioUnitViewConfiguration* config) override | |||||
{ | |||||
processorHolder->viewConfiguration = new AudioProcessorHolder::ViewConfig { [config width], [config height], [config hostHasController] == YES }; | |||||
} | } | ||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
void audioProcessorChanged (AudioProcessor* processor) override | void audioProcessorChanged (AudioProcessor* processor) override | ||||
@@ -720,15 +872,6 @@ public: | |||||
} | } | ||||
} | } | ||||
//============================================================================== | |||||
NSTimeInterval getLatency() override | |||||
{ | |||||
auto& p = getAudioProcessor(); | |||||
return p.getLatencySamples() / p.getSampleRate(); | |||||
} | |||||
NSTimeInterval getTailTime() override { return getAudioProcessor().getTailLengthSeconds(); } | |||||
//============================================================================== | //============================================================================== | ||||
bool getCurrentPosition (CurrentPositionInfo& info) override | bool getCurrentPosition (CurrentPositionInfo& info) override | ||||
{ | { | ||||
@@ -802,6 +945,7 @@ public: | |||||
return true; | return true; | ||||
} | } | ||||
//============================================================================== | |||||
static void removeEditor (AudioProcessor& processor) | static void removeEditor (AudioProcessor& processor) | ||||
{ | { | ||||
ScopedLock editorLock (processor.getCallbackLock()); | ScopedLock editorLock (processor.getCallbackLock()); | ||||
@@ -818,7 +962,7 @@ private: | |||||
struct BusBuffer | struct BusBuffer | ||||
{ | { | ||||
BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) | BusBuffer (AUAudioUnitBus* bus, int maxFramesPerBuffer) | ||||
: auBus (bus), bufferList (nullptr), | |||||
: auBus (bus), | |||||
maxFrames (maxFramesPerBuffer), | maxFrames (maxFramesPerBuffer), | ||||
numberOfChannels (static_cast<int> ([[auBus format] channelCount])), | numberOfChannels (static_cast<int> ([[auBus format] channelCount])), | ||||
isInterleaved ([[auBus format] isInterleaved]) | isInterleaved ([[auBus format] isInterleaved]) | ||||
@@ -893,7 +1037,7 @@ private: | |||||
private: | private: | ||||
AUAudioUnitBus* auBus; | AUAudioUnitBus* auBus; | ||||
HeapBlock<char> bufferListStorage; | HeapBlock<char> bufferListStorage; | ||||
AudioBufferList* bufferList; | |||||
AudioBufferList* bufferList = nullptr; | |||||
int maxFrames, numberOfChannels; | int maxFrames, numberOfChannels; | ||||
bool isInterleaved; | bool isInterleaved; | ||||
AudioSampleBuffer scratchBuffer; | AudioSampleBuffer scratchBuffer; | ||||
@@ -1048,6 +1192,7 @@ private: | |||||
static_cast<int> (maxFrames))); | static_cast<int> (maxFrames))); | ||||
} | } | ||||
//============================================================================== | |||||
void processEvents (const AURenderEvent *__nullable realtimeEventListHead, int numParams, AUEventSampleTime startTime) | void processEvents (const AURenderEvent *__nullable realtimeEventListHead, int numParams, AUEventSampleTime startTime) | ||||
{ | { | ||||
for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) | for (const AURenderEvent* event = realtimeEventListHead; event != nullptr; event = event->head.next) | ||||
@@ -1175,6 +1320,17 @@ private: | |||||
// process audio | // process audio | ||||
processBlock (audioBuffer.getBuffer (frameCount), midiMessages); | processBlock (audioBuffer.getBuffer (frameCount), midiMessages); | ||||
// send MIDI | |||||
#if JucePlugin_ProducesMidiOutput && JUCE_AUV3_MIDI_OUTPUT_SUPPORTED | |||||
auto midiOut = [au MIDIOutputEventBlock]; | |||||
MidiMessage msg; | |||||
int samplePosition; | |||||
for (MidiBuffer::Iterator it (midiMessages); it.getNextEvent (msg, samplePosition);) | |||||
midiOut (samplePosition, 0, msg.getRawDataSize(), msg.getRawData()); | |||||
#endif | |||||
midiMessages.clear(); | midiMessages.clear(); | ||||
} | } | ||||
@@ -1288,8 +1444,8 @@ private: | |||||
AUParameterObserverToken editorObserverToken; | AUParameterObserverToken editorObserverToken; | ||||
ScopedPointer<AUParameterTree> paramTree; | ScopedPointer<AUParameterTree> paramTree; | ||||
ScopedPointer<NSMutableArray<NSNumber*> > overviewParams; | |||||
ScopedPointer<NSMutableArray<NSNumber*> > channelCapabilities; | |||||
ScopedPointer<NSMutableArray<NSNumber*>> overviewParams; | |||||
ScopedPointer<NSMutableArray<NSNumber*>> channelCapabilities; | |||||
ScopedPointer<NSMutableArray<AUAudioUnitPreset*> > factoryPresets; | ScopedPointer<NSMutableArray<AUAudioUnitPreset*> > factoryPresets; | ||||
@@ -1306,6 +1462,8 @@ private: | |||||
AudioTimeStamp lastTimeStamp; | AudioTimeStamp lastTimeStamp; | ||||
CurrentPositionInfo lastAudioHead; | CurrentPositionInfo lastAudioHead; | ||||
String contextName; | |||||
}; | }; | ||||
const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; | const double JuceAudioUnitv3::kDefaultSampleRate = 44100.0; | ||||
@@ -1320,9 +1478,8 @@ JuceAudioUnitv3Base* JuceAudioUnitv3Base::create (AUAudioUnit* audioUnit, AudioC | |||||
class JuceAUViewController | class JuceAUViewController | ||||
{ | { | ||||
public: | public: | ||||
JuceAUViewController (AUViewController<AUAudioUnitFactory>* p) | JuceAUViewController (AUViewController<AUAudioUnitFactory>* p) | ||||
: myself (p), processorHolder (nullptr), preferredSize (1.0f, 1.0f) | |||||
: myself (p) | |||||
{ | { | ||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | jassert (MessageManager::getInstance()->isThisTheMessageThread()); | ||||
@@ -1370,6 +1527,9 @@ public: | |||||
{ | { | ||||
if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | if (AudioProcessorEditor* editor = getAudioProcessor().getActiveEditor()) | ||||
{ | { | ||||
if (processorHolder->viewConfiguration != nullptr) | |||||
editor->hostMIDIControllerIsAvailable (processorHolder->viewConfiguration->hostHasMIDIController); | |||||
editor->setBounds (convertToRectInt ([[myself view] bounds])); | editor->setBounds (convertToRectInt ([[myself view] bounds])); | ||||
if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) | if (JUCE_IOS_MAC_VIEW* peerView = [[[myself view] subviews] objectAtIndex: 0]) | ||||
@@ -1433,8 +1593,8 @@ public: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
AUViewController<AUAudioUnitFactory>* myself; | AUViewController<AUAudioUnitFactory>* myself; | ||||
AudioProcessorHolder::Ptr processorHolder; | |||||
Rectangle<int> preferredSize; | |||||
AudioProcessorHolder::Ptr processorHolder = nullptr; | |||||
Rectangle<int> preferredSize { 1, 1 }; | |||||
//============================================================================== | //============================================================================== | ||||
AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) | AUAudioUnit* createAudioUnitOnMessageThread (const AudioComponentDescription& descr, NSError** error) | ||||
@@ -520,22 +520,12 @@ private: | |||||
if (newMidiDevices != lastMidiDevices) | if (newMidiDevices != lastMidiDevices) | ||||
{ | { | ||||
for (auto& oldDevice : lastMidiDevices) | for (auto& oldDevice : lastMidiDevices) | ||||
{ | |||||
if (! newMidiDevices.contains (oldDevice)) | if (! newMidiDevices.contains (oldDevice)) | ||||
{ | |||||
deviceManager.setMidiInputEnabled (oldDevice, false); | deviceManager.setMidiInputEnabled (oldDevice, false); | ||||
deviceManager.removeMidiInputCallback (oldDevice, &player); | |||||
} | |||||
} | |||||
for (auto& newDevice : newMidiDevices) | for (auto& newDevice : newMidiDevices) | ||||
{ | |||||
if (! lastMidiDevices.contains (newDevice)) | if (! lastMidiDevices.contains (newDevice)) | ||||
{ | |||||
deviceManager.addMidiInputCallback (newDevice, &player); | |||||
deviceManager.setMidiInputEnabled (newDevice, true); | deviceManager.setMidiInputEnabled (newDevice, true); | ||||
} | |||||
} | |||||
} | } | ||||
} | } | ||||
#endif | #endif | ||||
@@ -552,7 +542,7 @@ private: | |||||
that the other plugin wrappers use. | that the other plugin wrappers use. | ||||
*/ | */ | ||||
class StandaloneFilterWindow : public DocumentWindow, | class StandaloneFilterWindow : public DocumentWindow, | ||||
public ButtonListener // (can't use Button::Listener due to VC2005 bug) | |||||
public Button::Listener | |||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -1320,7 +1320,9 @@ public: | |||||
if (auto host = wrapper.hostCallback) | if (auto host = wrapper.hostCallback) | ||||
{ | { | ||||
if (host (wrapper.getVstEffectInterface(), hostOpcodeCanHostDo, 0, 0, const_cast<char*> ("sizeWindow"), 0) == (pointer_sized_int) 1) | |||||
auto status = host (wrapper.getVstEffectInterface(), hostOpcodeCanHostDo, 0, 0, const_cast<char*> ("sizeWindow"), 0); | |||||
if (status == (pointer_sized_int) 1 || getHostType().isAbletonLive()) | |||||
{ | { | ||||
isInSizeWindow = true; | isInSizeWindow = true; | ||||
sizeWasSuccessful = (host (wrapper.getVstEffectInterface(), hostOpcodeWindowSize, newWidth, newHeight, 0, 0) != 0); | sizeWasSuccessful = (host (wrapper.getVstEffectInterface(), hostOpcodeWindowSize, newWidth, newHeight, 0, 0) != 0); | ||||
@@ -115,6 +115,7 @@ class JuceVST3Component; | |||||
//============================================================================== | //============================================================================== | ||||
class JuceVST3EditController : public Vst::EditController, | class JuceVST3EditController : public Vst::EditController, | ||||
public Vst::IMidiMapping, | public Vst::IMidiMapping, | ||||
public Vst::ChannelContext::IInfoListener, | |||||
public AudioProcessorListener | public AudioProcessorListener | ||||
{ | { | ||||
public: | public: | ||||
@@ -147,6 +148,7 @@ public: | |||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController2) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IEditController2) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IMidiMapping) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IMidiMapping) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::ChannelContext::IInfoListener) | |||||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IPluginBase, Vst::IEditController) | TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IPluginBase, Vst::IEditController) | ||||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IDependent, Vst::IEditController) | TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, IDependent, Vst::IEditController) | ||||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IEditController) | TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IEditController) | ||||
@@ -429,6 +431,41 @@ public: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProgramChangeParameter) | ||||
}; | }; | ||||
//============================================================================== | |||||
tresult PLUGIN_API setChannelContextInfos (Vst::IAttributeList* list) override | |||||
{ | |||||
if (auto* instance = getPluginInstance()) | |||||
{ | |||||
if (list != nullptr) | |||||
{ | |||||
AudioProcessor::TrackProperties trackProperties; | |||||
{ | |||||
Vst::String128 channelName; | |||||
if (list->getString (Vst::ChannelContext::kChannelNameKey, channelName, sizeof (channelName)) == kResultTrue) | |||||
trackProperties.name = toString (channelName); | |||||
} | |||||
{ | |||||
int64 colour; | |||||
if (list->getInt (Vst::ChannelContext::kChannelColorKey, colour) == kResultTrue) | |||||
trackProperties.colour = Colour (Vst::ChannelContext::GetRed ((uint32) colour), Vst::ChannelContext::GetGreen ((uint32) colour), | |||||
Vst::ChannelContext::GetBlue ((uint32) colour), Vst::ChannelContext::GetAlpha ((uint32) colour)); | |||||
} | |||||
if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
instance->updateTrackProperties (trackProperties); | |||||
else | |||||
MessageManager::callAsync ([trackProperties, instance] () | |||||
{ instance->updateTrackProperties (trackProperties); }); | |||||
} | |||||
} | |||||
return kResultOk; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
tresult PLUGIN_API setComponentState (IBStream* stream) override | tresult PLUGIN_API setComponentState (IBStream* stream) override | ||||
{ | { | ||||
@@ -1120,6 +1157,7 @@ public: | |||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IAudioProcessor) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IAudioProcessor) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IUnitInfo) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::IConnectionPoint) | ||||
TEST_FOR_AND_RETURN_IF_VALID (targetIID, Vst::ChannelContext::IInfoListener) | |||||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IComponent) | TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (targetIID, FUnknown, Vst::IComponent) | ||||
if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) | if (doUIDsMatch (targetIID, JuceAudioProcessor::iid)) | ||||
@@ -35,9 +35,14 @@ | |||||
#pragma clang diagnostic ignored "-Wextra-semi" | #pragma clang diagnostic ignored "-Wextra-semi" | ||||
#endif | #endif | ||||
#ifdef _MSC_VER | |||||
#pragma warning (push) | |||||
// #pragma warning (disable : 4127) | |||||
// From MacOS 10.13 and iOS 11 Apple has (sensibly!) stopped defining a whole | |||||
// set of functions with rather generic names. However, we still need a couple | |||||
// of them to compile the files below. | |||||
#ifndef verify | |||||
#define verify(assertion) __Verify(assertion) | |||||
#endif | |||||
#ifndef verify_noerr | |||||
#define verify_noerr(errorCode) __Verify_noErr(errorCode) | |||||
#endif | #endif | ||||
#include "AU/CoreAudioUtilityClasses/AUBase.cpp" | #include "AU/CoreAudioUtilityClasses/AUBase.cpp" | ||||
@@ -60,10 +65,9 @@ | |||||
#include "AU/CoreAudioUtilityClasses/ComponentBase.cpp" | #include "AU/CoreAudioUtilityClasses/ComponentBase.cpp" | ||||
#include "AU/CoreAudioUtilityClasses/MusicDeviceBase.cpp" | #include "AU/CoreAudioUtilityClasses/MusicDeviceBase.cpp" | ||||
#undef verify | |||||
#undef verify_noerr | |||||
#ifdef __clang__ | #ifdef __clang__ | ||||
#pragma clang diagnostic pop | #pragma clang diagnostic pop | ||||
#endif | #endif | ||||
#ifdef _MSC_VER | |||||
#pragma warning (pop) | |||||
#endif |
@@ -1065,6 +1065,19 @@ public: | |||||
jassertfalse; // xxx not implemented! | jassertfalse; // xxx not implemented! | ||||
} | } | ||||
//============================================================================== | |||||
void updateTrackProperties (const TrackProperties& properties) override | |||||
{ | |||||
if (properties.name.isNotEmpty()) | |||||
{ | |||||
CFStringRef contextName = properties.name.toCFString(); | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_ContextName, kAudioUnitScope_Global, | |||||
0, &contextName, sizeof (CFStringRef)); | |||||
CFRelease (contextName); | |||||
} | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void getStateInformation (MemoryBlock& destData) override | void getStateInformation (MemoryBlock& destData) override | ||||
{ | { | ||||
@@ -1128,6 +1141,7 @@ public: | |||||
void refreshParameterList() override | void refreshParameterList() override | ||||
{ | { | ||||
parameters.clear(); | parameters.clear(); | ||||
paramIDToIndex.clear(); | |||||
if (audioUnit != nullptr) | if (audioUnit != nullptr) | ||||
{ | { | ||||
@@ -1158,6 +1172,7 @@ public: | |||||
ParamInfo* const param = new ParamInfo(); | ParamInfo* const param = new ParamInfo(); | ||||
parameters.add (param); | parameters.add (param); | ||||
param->paramID = ids[i]; | param->paramID = ids[i]; | ||||
paramIDToIndex[ids[i]] = i; | |||||
param->minValue = info.minValue; | param->minValue = info.minValue; | ||||
param->maxValue = info.maxValue; | param->maxValue = info.maxValue; | ||||
param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | ||||
@@ -1250,6 +1265,7 @@ private: | |||||
}; | }; | ||||
OwnedArray<ParamInfo> parameters; | OwnedArray<ParamInfo> parameters; | ||||
std::unordered_map<AudioUnitParameterID, int> paramIDToIndex; | |||||
MidiDataConcatenator midiConcatenator; | MidiDataConcatenator midiConcatenator; | ||||
CriticalSection midiInLock; | CriticalSection midiInLock; | ||||
@@ -1335,13 +1351,10 @@ private: | |||||
|| event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture | || event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture | ||||
|| event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) | || event.mEventType == kAudioUnitEvent_EndParameterChangeGesture) | ||||
{ | { | ||||
for (paramIndex = 0; paramIndex < parameters.size(); ++paramIndex) | |||||
{ | |||||
const ParamInfo& p = *parameters.getUnchecked(paramIndex); | |||||
auto it = paramIDToIndex.find (event.mArgument.mParameter.mParameterID) | |||||
if (p.paramID == event.mArgument.mParameter.mParameterID) | |||||
break; | |||||
} | |||||
if (it != paramIDToIndex.end()) | |||||
paramIndex = it->second; | |||||
if (! isPositiveAndBelow (paramIndex, parameters.size())) | if (! isPositiveAndBelow (paramIndex, parameters.size())) | ||||
return; | return; | ||||
@@ -1351,7 +1364,7 @@ private: | |||||
{ | { | ||||
case kAudioUnitEvent_ParameterValueChange: | case kAudioUnitEvent_ParameterValueChange: | ||||
{ | { | ||||
const ParamInfo& p = *parameters.getUnchecked(paramIndex); | |||||
auto& p = *parameters.getUnchecked (paramIndex); | |||||
sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); | sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); | ||||
} | } | ||||
break; | break; | ||||
@@ -68,6 +68,7 @@ | |||||
#include <pluginterfaces/base/ipluginbase.h> | #include <pluginterfaces/base/ipluginbase.h> | ||||
#include <pluginterfaces/base/ustring.h> | #include <pluginterfaces/base/ustring.h> | ||||
#include <pluginterfaces/gui/iplugview.h> | #include <pluginterfaces/gui/iplugview.h> | ||||
#include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||||
#include <pluginterfaces/vst/ivstattributes.h> | #include <pluginterfaces/vst/ivstattributes.h> | ||||
#include <pluginterfaces/vst/ivstaudioprocessor.h> | #include <pluginterfaces/vst/ivstaudioprocessor.h> | ||||
#include <pluginterfaces/vst/ivstcomponent.h> | #include <pluginterfaces/vst/ivstcomponent.h> | ||||
@@ -83,6 +84,7 @@ | |||||
#include <pluginterfaces/vst/vsttypes.h> | #include <pluginterfaces/vst/vsttypes.h> | ||||
#include <pluginterfaces/vst/ivstunits.h> | #include <pluginterfaces/vst/ivstunits.h> | ||||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | #include <pluginterfaces/vst/ivstmidicontrollers.h> | ||||
#include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||||
#include <public.sdk/source/common/memorystream.h> | #include <public.sdk/source/common/memorystream.h> | ||||
#include <public.sdk/source/vst/vsteditcontroller.h> | #include <public.sdk/source/vst/vsteditcontroller.h> | ||||
#else | #else | ||||
@@ -105,6 +107,7 @@ | |||||
#include <pluginterfaces/gui/iplugview.h> | #include <pluginterfaces/gui/iplugview.h> | ||||
#include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | #include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | ||||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | #include <pluginterfaces/vst/ivstmidicontrollers.h> | ||||
#include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||||
#include <public.sdk/source/common/memorystream.cpp> | #include <public.sdk/source/common/memorystream.cpp> | ||||
#include <public.sdk/source/common/pluginview.cpp> | #include <public.sdk/source/common/pluginview.cpp> | ||||
#include <public.sdk/source/vst/vsteditcontroller.cpp> | #include <public.sdk/source/vst/vsteditcontroller.cpp> | ||||
@@ -2101,6 +2101,67 @@ struct VST3PluginInstance : public AudioPluginInstance | |||||
return result; | return result; | ||||
} | } | ||||
//============================================================================== | |||||
void updateTrackProperties (const TrackProperties& properties) override | |||||
{ | |||||
if (trackInfoListener != nullptr) | |||||
{ | |||||
ComSmartPtr<Vst::IAttributeList> l (new TrackPropertiesAttributeList (properties)); | |||||
trackInfoListener->setChannelContextInfos (l); | |||||
} | |||||
} | |||||
struct TrackPropertiesAttributeList : public Vst::IAttributeList | |||||
{ | |||||
TrackPropertiesAttributeList (const TrackProperties& properties) : props (properties) {} | |||||
virtual ~TrackPropertiesAttributeList() {} | |||||
JUCE_DECLARE_VST3_COM_REF_METHODS | |||||
tresult PLUGIN_API queryInterface (const TUID queryIid, void** obj) override | |||||
{ | |||||
TEST_FOR_AND_RETURN_IF_VALID (queryIid, Vst::IAttributeList) | |||||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (queryIid, FUnknown, Vst::IAttributeList) | |||||
*obj = nullptr; | |||||
return kNotImplemented; | |||||
} | |||||
tresult PLUGIN_API setInt (AttrID, Steinberg::int64) override { return kOutOfMemory; } | |||||
tresult PLUGIN_API setFloat (AttrID, double) override { return kOutOfMemory; } | |||||
tresult PLUGIN_API setString (AttrID, const Vst::TChar*) override { return kOutOfMemory; } | |||||
tresult PLUGIN_API setBinary (AttrID, const void*, Steinberg::uint32) override { return kOutOfMemory; } | |||||
tresult PLUGIN_API getFloat (AttrID, double&) override { return kResultFalse; } | |||||
tresult PLUGIN_API getBinary (AttrID, const void*&, Steinberg::uint32&) override { return kResultFalse; } | |||||
tresult PLUGIN_API getString (AttrID id, Vst::TChar* string, Steinberg::uint32 size) override | |||||
{ | |||||
if (! std::strcmp (id, Vst::ChannelContext::kChannelNameKey)) | |||||
{ | |||||
Steinberg::String str (props.name.toRawUTF8()); | |||||
str.copyTo (string, 0, (Steinberg::int32) jmin (size, (Steinberg::uint32) std::numeric_limits<Steinberg::int32>::max())); | |||||
return kResultTrue; | |||||
} | |||||
return kResultFalse; | |||||
} | |||||
tresult PLUGIN_API getInt (AttrID id, Steinberg::int64& value) override | |||||
{ | |||||
if (! std::strcmp (Vst::ChannelContext::kChannelNameLengthKey, id)) value = props.name.length(); | |||||
else if (! std::strcmp (Vst::ChannelContext::kChannelColorKey, id)) value = static_cast<Steinberg::int64> (props.colour.getARGB()); | |||||
else return kResultFalse; | |||||
return kResultTrue; | |||||
} | |||||
Atomic<int> refCount; | |||||
TrackProperties props; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TrackPropertiesAttributeList) | |||||
}; | |||||
//============================================================================== | //============================================================================== | ||||
String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const | String getChannelName (int channelIndex, bool forInput, bool forAudioChannel) const | ||||
{ | { | ||||
@@ -2450,6 +2511,7 @@ private: | |||||
ComSmartPtr<Vst::IUnitData> unitData; | ComSmartPtr<Vst::IUnitData> unitData; | ||||
ComSmartPtr<Vst::IProgramListData> programListData; | ComSmartPtr<Vst::IProgramListData> programListData; | ||||
ComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | ComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | ||||
ComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener; | |||||
/** The number of IO buses MUST match that of the plugin, | /** The number of IO buses MUST match that of the plugin, | ||||
even if there aren't enough channels to process, | even if there aren't enough channels to process, | ||||
@@ -2534,6 +2596,7 @@ private: | |||||
editController2.loadFrom (holder->component); | editController2.loadFrom (holder->component); | ||||
componentHandler.loadFrom (holder->component); | componentHandler.loadFrom (holder->component); | ||||
componentHandler2.loadFrom (holder->component); | componentHandler2.loadFrom (holder->component); | ||||
trackInfoListener.loadFrom (holder->component); | |||||
if (processor == nullptr) processor.loadFrom (editController); | if (processor == nullptr) processor.loadFrom (editController); | ||||
if (unitInfo == nullptr) unitInfo.loadFrom (editController); | if (unitInfo == nullptr) unitInfo.loadFrom (editController); | ||||
@@ -2542,6 +2605,7 @@ private: | |||||
if (editController2 == nullptr) editController2.loadFrom (editController); | if (editController2 == nullptr) editController2.loadFrom (editController); | ||||
if (componentHandler == nullptr) componentHandler.loadFrom (editController); | if (componentHandler == nullptr) componentHandler.loadFrom (editController); | ||||
if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); | if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); | ||||
if (trackInfoListener == nullptr) trackInfoListener.loadFrom (editController); | |||||
} | } | ||||
void setStateForAllMidiBuses (bool newState) | void setStateForAllMidiBuses (bool newState) | ||||
@@ -180,8 +180,8 @@ struct SpeakerMappings : private AudioChannelSet // (inheritance only to give e | |||||
VstSpeakerConfiguration* allocate (int numChannels) | VstSpeakerConfiguration* allocate (int numChannels) | ||||
{ | { | ||||
auto arrangementSize = (sizeof (VstSpeakerConfiguration) - (sizeof(VstIndividualSpeakerInfo) * 8)) | |||||
+ (sizeof (VstIndividualSpeakerInfo) * static_cast<size_t> (numChannels)); | |||||
auto arrangementSize = sizeof (VstSpeakerConfiguration) | |||||
+ sizeof (VstIndividualSpeakerInfo) * static_cast<size_t> (jmax (8, numChannels) - 8); | |||||
storage.malloc (1, arrangementSize); | storage.malloc (1, arrangementSize); | ||||
return storage.getData(); | return storage.getData(); | ||||
@@ -1029,6 +1029,9 @@ void AudioProcessor::setCurrentProgramStateInformation (const void* data, int si | |||||
setStateInformation (data, sizeInBytes); | setStateInformation (data, sizeInBytes); | ||||
} | } | ||||
//============================================================================== | |||||
void AudioProcessor::updateTrackProperties (const AudioProcessor::TrackProperties&) {} | |||||
//============================================================================== | //============================================================================== | ||||
// magic number to identify memory blocks that we've stored as XML | // magic number to identify memory blocks that we've stored as XML | ||||
const uint32 magicXmlNumber = 0x21324356; | const uint32 magicXmlNumber = 0x21324356; | ||||
@@ -1320,6 +1320,32 @@ public: | |||||
*/ | */ | ||||
WrapperType wrapperType; | WrapperType wrapperType; | ||||
/** A struct containing information about the DAW track inside which your | |||||
AudioProcessor is loaded. */ | |||||
struct TrackProperties | |||||
{ | |||||
String name; // The name of the track - this will be empty if the track name is not known | |||||
Colour colour; // The colour of the track - this will be transparentBlack if the colour is not known | |||||
// other properties may be added in the future | |||||
}; | |||||
/** Informs the AudioProcessor that track properties such as the track's name or | |||||
colour has been changed. | |||||
If you are hosting this AudioProcessor then use this method to inform the | |||||
AudioProcessor about which track the AudioProcessor is loaded on. This method | |||||
may only be called on the message thread. | |||||
If you are implemeting an AudioProcessor then you can override this callback | |||||
to do something useful with the track properties such as changing the colour | |||||
of your AudioProcessor's editor. It's entirely up to the host when and how | |||||
often this callback will be called. | |||||
The default implementation of this callback will do nothing. | |||||
*/ | |||||
virtual void updateTrackProperties (const TrackProperties& properties); | |||||
//============================================================================== | //============================================================================== | ||||
#ifndef DOXYGEN | #ifndef DOXYGEN | ||||
/** Deprecated: use getTotalNumInputChannels instead. */ | /** Deprecated: use getTotalNumInputChannels instead. */ | ||||
@@ -45,7 +45,10 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||||
} | } | ||||
void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | ||||
int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | |||||
int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | |||||
bool AudioProcessorEditor::supportsHostMIDIControllerPresence (bool) { return true; } | |||||
void AudioProcessorEditor::hostMIDIControllerIsAvailable (bool) {} | |||||
void AudioProcessorEditor::initialise() | void AudioProcessorEditor::initialise() | ||||
{ | { | ||||
@@ -85,6 +85,28 @@ public: | |||||
*/ | */ | ||||
virtual int getControlParameterIndex (Component&); | virtual int getControlParameterIndex (Component&); | ||||
/** Override this method to indicate if your editor supports the presence or | |||||
absence of a host-provided MIDI controller. | |||||
Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) | |||||
support this functionality, and even then the host may choose to ignore this | |||||
information. | |||||
The default behaviour is to report support for both cases. | |||||
*/ | |||||
virtual bool supportsHostMIDIControllerPresence (bool hostMIDIControllerIsAvailable); | |||||
/** Called to indicate if a host is providing a MIDI controller when the host | |||||
reconfigures its layout. | |||||
Use this as an opportunity to hide or display your own onscreen keyboard or | |||||
other input component. | |||||
Currently only AUv3 plug-ins compiled for MacOS 10.13 or iOS 11.0 (or later) | |||||
support this functionality. | |||||
*/ | |||||
virtual void hostMIDIControllerIsAvailable (bool controllerIsAvailable); | |||||
/** Can be called by a host to tell the editor that it should use a non-unity | /** Can be called by a host to tell the editor that it should use a non-unity | ||||
GUI scale. | GUI scale. | ||||
*/ | */ | ||||
@@ -1466,6 +1466,7 @@ void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuff | |||||
AudioBuffer<FloatType>& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | AudioBuffer<FloatType>& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | ||||
const int numSamples = buffer.getNumSamples(); | const int numSamples = buffer.getNumSamples(); | ||||
jassert (numSamples <= getBlockSize()); | |||||
currentAudioInputBuffer = &buffer; | currentAudioInputBuffer = &buffer; | ||||
currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); | currentAudioOutputBuffer.setSize (jmax (1, buffer.getNumChannels()), numSamples); | ||||
@@ -1488,6 +1489,25 @@ void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuff | |||||
midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); | midiMessages.addEvents (currentMidiOutputBuffer, 0, buffer.getNumSamples(), 0); | ||||
} | } | ||||
template <typename FloatType> | |||||
void AudioProcessorGraph::sliceAndProcess (AudioBuffer<FloatType>& buffer, MidiBuffer& midiMessages) | |||||
{ | |||||
auto n = buffer.getNumSamples(); | |||||
auto ch = buffer.getNumChannels(); | |||||
auto max = 0; | |||||
for (auto pos = 0; pos < n; pos += max) | |||||
{ | |||||
max = jmin (n - pos, getBlockSize()); | |||||
AudioBuffer<FloatType> audioSlice (buffer.getArrayOfWritePointers(), ch, pos, max); | |||||
MidiBuffer midiSlice; | |||||
midiSlice.addEvents (midiMessages, pos, max, 0); | |||||
processAudio (audioSlice, midiSlice); | |||||
} | |||||
} | |||||
double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } | double AudioProcessorGraph::getTailLengthSeconds() const { return 0; } | ||||
bool AudioProcessorGraph::acceptsMidi() const { return true; } | bool AudioProcessorGraph::acceptsMidi() const { return true; } | ||||
bool AudioProcessorGraph::producesMidi() const { return true; } | bool AudioProcessorGraph::producesMidi() const { return true; } | ||||
@@ -1496,12 +1516,12 @@ void AudioProcessorGraph::setStateInformation (const void*, int) {} | |||||
void AudioProcessorGraph::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | void AudioProcessorGraph::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | ||||
{ | { | ||||
processAudio (buffer, midiMessages); | |||||
sliceAndProcess (buffer, midiMessages); | |||||
} | } | ||||
void AudioProcessorGraph::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | void AudioProcessorGraph::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | ||||
{ | { | ||||
processAudio (buffer, midiMessages); | |||||
sliceAndProcess (buffer, midiMessages); | |||||
} | } | ||||
// explicit template instantiation | // explicit template instantiation | ||||
@@ -380,6 +380,9 @@ private: | |||||
template <typename floatType> | template <typename floatType> | ||||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | ||||
template <typename floatType> | |||||
void sliceAndProcess (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||||
//============================================================================== | //============================================================================== | ||||
ReferenceCountedArray<Node> nodes; | ReferenceCountedArray<Node> nodes; | ||||
OwnedArray<Connection> connections; | OwnedArray<Connection> connections; | ||||
@@ -35,7 +35,7 @@ | |||||
class JUCE_API PluginListComponent : public Component, | class JUCE_API PluginListComponent : public Component, | ||||
public FileDragAndDropTarget, | public FileDragAndDropTarget, | ||||
private ChangeListener, | private ChangeListener, | ||||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
private Button::Listener | |||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -199,8 +199,8 @@ static String getNoDeviceString() { return "<< " + TRANS("none") + " >>"; } | |||||
//============================================================================== | //============================================================================== | ||||
class AudioDeviceSettingsPanel : public Component, | class AudioDeviceSettingsPanel : public Component, | ||||
private ChangeListener, | private ChangeListener, | ||||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||||
private ButtonListener | |||||
private ComboBox::Listener, | |||||
private Button::Listener | |||||
{ | { | ||||
public: | public: | ||||
AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails, | AudioDeviceSettingsPanel (AudioIODeviceType& t, AudioDeviceSetupDetails& setupDetails, | ||||
@@ -37,8 +37,8 @@ | |||||
@see AudioDeviceManager | @see AudioDeviceManager | ||||
*/ | */ | ||||
class JUCE_API AudioDeviceSelectorComponent : public Component, | class JUCE_API AudioDeviceSelectorComponent : public Component, | ||||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||||
private ChangeListener, | private ChangeListener, | ||||
private ComboBox::Listener, | |||||
private Button::Listener, | private Button::Listener, | ||||
private Timer | private Timer | ||||
{ | { | ||||
@@ -28,9 +28,9 @@ | |||||
// This is an AudioTransportSource which will own it's assigned source | // This is an AudioTransportSource which will own it's assigned source | ||||
struct AudioSourceOwningTransportSource : public AudioTransportSource | struct AudioSourceOwningTransportSource : public AudioTransportSource | ||||
{ | { | ||||
AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||||
AudioSourceOwningTransportSource (PositionableAudioSource* s, double sampleRate) : source (s) | |||||
{ | { | ||||
AudioTransportSource::setSource (s); | |||||
AudioTransportSource::setSource (s, 0, nullptr, sampleRate); | |||||
} | } | ||||
~AudioSourceOwningTransportSource() | ~AudioSourceOwningTransportSource() | ||||
@@ -180,7 +180,7 @@ void SoundPlayer::play (const void* resourceData, size_t resourceSize) | |||||
void SoundPlayer::play (AudioFormatReader* reader, bool deleteWhenFinished) | void SoundPlayer::play (AudioFormatReader* reader, bool deleteWhenFinished) | ||||
{ | { | ||||
if (reader != nullptr) | if (reader != nullptr) | ||||
play (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||||
play (new AudioFormatReaderSource (reader, deleteWhenFinished), true, reader->sampleRate); | |||||
} | } | ||||
void SoundPlayer::play (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | void SoundPlayer::play (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | ||||
@@ -189,7 +189,7 @@ void SoundPlayer::play (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool | |||||
play (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | play (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | ||||
} | } | ||||
void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||||
void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFinished, double fileSampleRate) | |||||
{ | { | ||||
if (audioSource != nullptr) | if (audioSource != nullptr) | ||||
{ | { | ||||
@@ -199,12 +199,12 @@ void SoundPlayer::play (PositionableAudioSource* audioSource, bool deleteWhenFin | |||||
{ | { | ||||
if (deleteWhenFinished) | if (deleteWhenFinished) | ||||
{ | { | ||||
transport = new AudioSourceOwningTransportSource (audioSource); | |||||
transport = new AudioSourceOwningTransportSource (audioSource, fileSampleRate); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
transport = new AudioTransportSource(); | transport = new AudioTransportSource(); | ||||
transport->setSource (audioSource); | |||||
transport->setSource (audioSource, 0, nullptr, fileSampleRate); | |||||
deleteWhenFinished = true; | deleteWhenFinished = true; | ||||
} | } | ||||
} | } | ||||
@@ -77,8 +77,12 @@ public: | |||||
@param deleteWhenFinished If this is true then the audio source will | @param deleteWhenFinished If this is true then the audio source will | ||||
be deleted once the device manager has finished | be deleted once the device manager has finished | ||||
playing. | playing. | ||||
@param sampleRateOfSource The sample rate of the source. If this is zero, JUCE | |||||
will assume that the sample rate is the same as the | |||||
audio output device. | |||||
*/ | */ | ||||
void play (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); | |||||
void play (PositionableAudioSource* audioSource, bool deleteWhenFinished = false, | |||||
double sampleRateOfSource = 0.0); | |||||
/** Plays the sound from an audio sample buffer. | /** Plays the sound from an audio sample buffer. | ||||
@@ -91,7 +91,7 @@ DynamicObject::Ptr DynamicObject::clone() | |||||
return d; | return d; | ||||
} | } | ||||
void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const bool allOnOneLine) | |||||
void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const bool allOnOneLine, int maximumDecimalPlaces) | |||||
{ | { | ||||
out << '{'; | out << '{'; | ||||
if (! allOnOneLine) | if (! allOnOneLine) | ||||
@@ -107,7 +107,7 @@ void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const | |||||
out << '"'; | out << '"'; | ||||
JSONFormatter::writeString (out, properties.getName (i)); | JSONFormatter::writeString (out, properties.getName (i)); | ||||
out << "\": "; | out << "\": "; | ||||
JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine); | |||||
JSONFormatter::write (out, properties.getValueAt (i), indentLevel + JSONFormatter::indentSize, allOnOneLine, maximumDecimalPlaces); | |||||
if (i < numValues - 1) | if (i < numValues - 1) | ||||
{ | { | ||||
@@ -113,14 +113,14 @@ public: | |||||
never need to call it directly, but it's virtual so that custom object types | never need to call it directly, but it's virtual so that custom object types | ||||
can stringify themselves appropriately. | can stringify themselves appropriately. | ||||
*/ | */ | ||||
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine); | |||||
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine, int maximumDecimalPlaces); | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
NamedValueSet properties; | NamedValueSet properties; | ||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | ||||
// These methods have been deprecated - use var::invoke instead | |||||
// This method has been deprecated - use var::invoke instead | |||||
virtual void invokeMethod (const Identifier&, const var*, int) {} | virtual void invokeMethod (const Identifier&, const var*, int) {} | ||||
#endif | #endif | ||||
@@ -31,16 +31,20 @@ | |||||
*/ | */ | ||||
struct DefaultHashFunctions | struct DefaultHashFunctions | ||||
{ | { | ||||
/** Generates a simple hash from an unsigned int. */ | |||||
static int generateHash (uint32 key, int upperLimit) noexcept { return (int) (key % (uint32) upperLimit); } | |||||
/** Generates a simple hash from an integer. */ | /** Generates a simple hash from an integer. */ | ||||
int generateHash (const int key, const int upperLimit) const noexcept { return std::abs (key) % upperLimit; } | |||||
static int generateHash (int32 key, int upperLimit) noexcept { return generateHash ((uint32) key, upperLimit); } | |||||
/** Generates a simple hash from a uint64. */ | |||||
static int generateHash (uint64 key, int upperLimit) noexcept { return (int) (key % (uint64) upperLimit); } | |||||
/** Generates a simple hash from an int64. */ | /** Generates a simple hash from an int64. */ | ||||
int generateHash (const int64 key, const int upperLimit) const noexcept { return std::abs ((int) key) % upperLimit; } | |||||
static int generateHash (int64 key, int upperLimit) noexcept { return generateHash ((uint64) key, upperLimit); } | |||||
/** Generates a simple hash from a string. */ | /** Generates a simple hash from a string. */ | ||||
int generateHash (const String& key, const int upperLimit) const noexcept { return (int) (((uint32) key.hashCode()) % (uint32) upperLimit); } | |||||
static int generateHash (const String& key, int upperLimit) noexcept { return generateHash ((uint32) key.hashCode(), upperLimit); } | |||||
/** Generates a simple hash from a variant. */ | /** Generates a simple hash from a variant. */ | ||||
int generateHash (const var& key, const int upperLimit) const noexcept { return generateHash (key.toString(), upperLimit); } | |||||
static int generateHash (const var& key, int upperLimit) noexcept { return generateHash (key.toString(), upperLimit); } | |||||
/** Generates a simple hash from a void ptr. */ | /** Generates a simple hash from a void ptr. */ | ||||
int generateHash (const void* key, const int upperLimit) const noexcept { return (int)(((pointer_sized_uint) key) % ((pointer_sized_uint) upperLimit)); } | |||||
static int generateHash (const void* key, int upperLimit) noexcept { return generateHash ((pointer_sized_uint) key, upperLimit); } | |||||
}; | }; | ||||
@@ -55,31 +55,22 @@ class SortedSet | |||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
/** Creates an empty set. */ | /** Creates an empty set. */ | ||||
SortedSet() noexcept | |||||
{ | |||||
} | |||||
SortedSet() noexcept = default; | |||||
/** Creates a copy of another set. | |||||
@param other the set to copy | |||||
*/ | |||||
SortedSet (const SortedSet& other) | |||||
: data (other.data) | |||||
{ | |||||
} | |||||
/** Creates a copy of another set. */ | |||||
SortedSet (const SortedSet&) = default; | |||||
/** Destructor. */ | |||||
~SortedSet() noexcept | |||||
{ | |||||
} | |||||
/** Creates a copy of another set. */ | |||||
SortedSet (SortedSet&&) noexcept = default; | |||||
/** Copies another set over this one. | |||||
@param other the set to copy | |||||
*/ | |||||
SortedSet& operator= (const SortedSet& other) noexcept | |||||
{ | |||||
data = other.data; | |||||
return *this; | |||||
} | |||||
/** Makes a copy of another set. */ | |||||
SortedSet& operator= (const SortedSet&) = default; | |||||
/** Makes a copy of another set. */ | |||||
SortedSet& operator= (SortedSet&&) noexcept = default; | |||||
/** Destructor. */ | |||||
~SortedSet() noexcept {} | |||||
//============================================================================== | //============================================================================== | ||||
/** Compares this set to another one. | /** Compares this set to another one. | ||||
@@ -234,7 +225,7 @@ public: | |||||
if (elementToLookFor == data.getReference (s)) | if (elementToLookFor == data.getReference (s)) | ||||
return s; | return s; | ||||
const int halfway = (s + e) / 2; | |||||
auto halfway = (s + e) / 2; | |||||
if (halfway == s) | if (halfway == s) | ||||
return -1; | return -1; | ||||
@@ -277,15 +268,16 @@ public: | |||||
while (s < e) | while (s < e) | ||||
{ | { | ||||
ElementType& elem = data.getReference (s); | |||||
auto& elem = data.getReference (s); | |||||
if (newElement == elem) | if (newElement == elem) | ||||
{ | { | ||||
elem = newElement; // force an update in case operator== permits differences. | elem = newElement; // force an update in case operator== permits differences. | ||||
return false; | return false; | ||||
} | } | ||||
const int halfway = (s + e) / 2; | |||||
const bool isBeforeHalfway = (newElement < data.getReference (halfway)); | |||||
auto halfway = (s + e) / 2; | |||||
bool isBeforeHalfway = (newElement < data.getReference (halfway)); | |||||
if (halfway == s) | if (halfway == s) | ||||
{ | { | ||||
@@ -335,25 +327,22 @@ public: | |||||
int numElementsToAdd = -1) noexcept | int numElementsToAdd = -1) noexcept | ||||
{ | { | ||||
const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock()); | const typename OtherSetType::ScopedLockType lock1 (setToAddFrom.getLock()); | ||||
const ScopedLockType lock2 (getLock()); | |||||
jassert (this != &setToAddFrom); | |||||
if (this != &setToAddFrom) | |||||
{ | { | ||||
const ScopedLockType lock2 (getLock()); | |||||
jassert (this != &setToAddFrom); | |||||
if (this != &setToAddFrom) | |||||
if (startIndex < 0) | |||||
{ | { | ||||
if (startIndex < 0) | |||||
{ | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
jassertfalse; | |||||
startIndex = 0; | |||||
} | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) | |||||
numElementsToAdd = setToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd < 0 || startIndex + numElementsToAdd > setToAddFrom.size()) | |||||
numElementsToAdd = setToAddFrom.size() - startIndex; | |||||
if (numElementsToAdd > 0) | |||||
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); | |||||
} | |||||
if (numElementsToAdd > 0) | |||||
addArray (&setToAddFrom.data.getReference (startIndex), numElementsToAdd); | |||||
} | } | ||||
} | } | ||||
@@ -400,7 +389,7 @@ public: | |||||
{ | { | ||||
clear(); | clear(); | ||||
} | } | ||||
else if (otherSet.size() > 0) | |||||
else if (! otherSet.isEmpty()) | |||||
{ | { | ||||
for (int i = data.size(); --i >= 0;) | for (int i = data.size(); --i >= 0;) | ||||
if (otherSet.contains (data.getReference (i))) | if (otherSet.contains (data.getReference (i))) | ||||
@@ -423,7 +412,7 @@ public: | |||||
if (this != &otherSet) | if (this != &otherSet) | ||||
{ | { | ||||
if (otherSet.size() <= 0) | |||||
if (otherSet.isEmpty()) | |||||
{ | { | ||||
clear(); | clear(); | ||||
} | } | ||||
@@ -323,7 +323,8 @@ class JSONFormatter | |||||
{ | { | ||||
public: | public: | ||||
static void write (OutputStream& out, const var& v, | static void write (OutputStream& out, const var& v, | ||||
const int indentLevel, const bool allOnOneLine) | |||||
const int indentLevel, const bool allOnOneLine, | |||||
int maximumDecimalPlaces) | |||||
{ | { | ||||
if (v.isString()) | if (v.isString()) | ||||
{ | { | ||||
@@ -343,14 +344,18 @@ public: | |||||
{ | { | ||||
out << (static_cast<bool> (v) ? "true" : "false"); | out << (static_cast<bool> (v) ? "true" : "false"); | ||||
} | } | ||||
else if (v.isDouble()) | |||||
{ | |||||
out << String (static_cast<double> (v), maximumDecimalPlaces); | |||||
} | |||||
else if (v.isArray()) | else if (v.isArray()) | ||||
{ | { | ||||
writeArray (out, *v.getArray(), indentLevel, allOnOneLine); | |||||
writeArray (out, *v.getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces); | |||||
} | } | ||||
else if (v.isObject()) | else if (v.isObject()) | ||||
{ | { | ||||
if (DynamicObject* object = v.getDynamicObject()) | if (DynamicObject* object = v.getDynamicObject()) | ||||
object->writeAsJSON (out, indentLevel, allOnOneLine); | |||||
object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces); | |||||
else | else | ||||
jassertfalse; // Only DynamicObjects can be converted to JSON! | jassertfalse; // Only DynamicObjects can be converted to JSON! | ||||
} | } | ||||
@@ -420,7 +425,8 @@ public: | |||||
} | } | ||||
static void writeArray (OutputStream& out, const Array<var>& array, | static void writeArray (OutputStream& out, const Array<var>& array, | ||||
const int indentLevel, const bool allOnOneLine) | |||||
const int indentLevel, const bool allOnOneLine, | |||||
int maximumDecimalPlaces) | |||||
{ | { | ||||
out << '['; | out << '['; | ||||
@@ -434,7 +440,7 @@ public: | |||||
if (! allOnOneLine) | if (! allOnOneLine) | ||||
writeSpaces (out, indentLevel + indentSize); | writeSpaces (out, indentLevel + indentSize); | ||||
write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine); | |||||
write (out, array.getReference(i), indentLevel + indentSize, allOnOneLine, maximumDecimalPlaces); | |||||
if (i < array.size() - 1) | if (i < array.size() - 1) | ||||
{ | { | ||||
@@ -493,16 +499,16 @@ Result JSON::parse (const String& text, var& result) | |||||
return JSONParser::parseObjectOrArray (text.getCharPointer(), result); | return JSONParser::parseObjectOrArray (text.getCharPointer(), result); | ||||
} | } | ||||
String JSON::toString (const var& data, const bool allOnOneLine) | |||||
String JSON::toString (const var& data, const bool allOnOneLine, int maximumDecimalPlaces) | |||||
{ | { | ||||
MemoryOutputStream mo (1024); | MemoryOutputStream mo (1024); | ||||
JSONFormatter::write (mo, data, 0, allOnOneLine); | |||||
JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces); | |||||
return mo.toUTF8(); | return mo.toUTF8(); | ||||
} | } | ||||
void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine) | |||||
void JSON::writeToStream (OutputStream& output, const var& data, const bool allOnOneLine, int maximumDecimalPlaces) | |||||
{ | { | ||||
JSONFormatter::write (output, data, 0, allOnOneLine); | |||||
JSONFormatter::write (output, data, 0, allOnOneLine, maximumDecimalPlaces); | |||||
} | } | ||||
String JSON::escapeString (StringRef s) | String JSON::escapeString (StringRef s) | ||||
@@ -90,10 +90,12 @@ public: | |||||
/** Returns a string which contains a JSON-formatted representation of the var object. | /** Returns a string which contains a JSON-formatted representation of the var object. | ||||
If allOnOneLine is true, the result will be compacted into a single line of text | If allOnOneLine is true, the result will be compacted into a single line of text | ||||
with no carriage-returns. If false, it will be laid-out in a more human-readable format. | with no carriage-returns. If false, it will be laid-out in a more human-readable format. | ||||
The maximumDecimalPlaces parameter determines the precision of floating point numbers. | |||||
@see writeToStream | @see writeToStream | ||||
*/ | */ | ||||
static String toString (const var& objectToFormat, | static String toString (const var& objectToFormat, | ||||
bool allOnOneLine = false); | |||||
bool allOnOneLine = false, | |||||
int maximumDecimalPlaces = 20); | |||||
/** Parses a string that was created with the toString() method. | /** Parses a string that was created with the toString() method. | ||||
This is slightly different to the parse() methods because they will reject primitive | This is slightly different to the parse() methods because they will reject primitive | ||||
@@ -105,11 +107,13 @@ public: | |||||
/** Writes a JSON-formatted representation of the var object to the given stream. | /** Writes a JSON-formatted representation of the var object to the given stream. | ||||
If allOnOneLine is true, the result will be compacted into a single line of text | If allOnOneLine is true, the result will be compacted into a single line of text | ||||
with no carriage-returns. If false, it will be laid-out in a more human-readable format. | with no carriage-returns. If false, it will be laid-out in a more human-readable format. | ||||
The maximumDecimalPlaces parameter determines the precision of floating point numbers. | |||||
@see toString | @see toString | ||||
*/ | */ | ||||
static void writeToStream (OutputStream& output, | static void writeToStream (OutputStream& output, | ||||
const var& objectToFormat, | const var& objectToFormat, | ||||
bool allOnOneLine = false); | |||||
bool allOnOneLine = false, | |||||
int maximumDecimalPlaces = 20); | |||||
/** Returns a version of a string with any extended characters escaped. */ | /** Returns a version of a string with any extended characters escaped. */ | ||||
static String escapeString (StringRef); | static String escapeString (StringRef); | ||||
@@ -827,7 +827,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||||
DynamicObject::Ptr clone() override { return new FunctionObject (*this); } | DynamicObject::Ptr clone() override { return new FunctionObject (*this); } | ||||
void writeAsJSON (OutputStream& out, int /*indentLevel*/, bool /*allOnOneLine*/) override | |||||
void writeAsJSON (OutputStream& out, int /*indentLevel*/, bool /*allOnOneLine*/, int /*maximumDecimalPlaces*/) override | |||||
{ | { | ||||
out << "function " << functionCode; | out << "function " << functionCode; | ||||
} | } | ||||
@@ -149,6 +149,13 @@ public: | |||||
*/ | */ | ||||
inline operator ElementType*() const noexcept { return data; } | inline operator ElementType*() const noexcept { return data; } | ||||
/** Returns a raw pointer to the allocated data. | |||||
This may be a null pointer if the data hasn't yet been allocated, or if it has been | |||||
freed by calling the free() method. | |||||
*/ | |||||
inline ElementType* get() const noexcept { return data; } | |||||
/** Returns a raw pointer to the allocated data. | /** Returns a raw pointer to the allocated data. | ||||
This may be a null pointer if the data hasn't yet been allocated, or if it has been | This may be a null pointer if the data hasn't yet been allocated, or if it has been | ||||
freed by calling the free() method. | freed by calling the free() method. | ||||
@@ -0,0 +1,971 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
The code included in this file is provided under the terms of the ISC license | |||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission | |||||
To use, copy, modify, and/or distribute this software for any purpose with or | |||||
without fee is hereby granted provided that the above copyright notice and | |||||
this permission notice appear in all copies. | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
package com.android.vending.billing; | |||||
/** | |||||
* InAppBillingService is the service that provides in-app billing version 3 and beyond. | |||||
* This service provides the following features: | |||||
* 1. Provides a new API to get details of in-app items published for the app including | |||||
* price, type, title and description. | |||||
* 2. The purchase flow is synchronous and purchase information is available immediately | |||||
* after it completes. | |||||
* 3. Purchase information of in-app purchases is maintained within the Google Play system | |||||
* till the purchase is consumed. | |||||
* 4. An API to consume a purchase of an inapp item. All purchases of one-time | |||||
* in-app items are consumable and thereafter can be purchased again. | |||||
* 5. An API to get current purchases of the user immediately. This will not contain any | |||||
* consumed purchases. | |||||
* | |||||
* All calls will give a response code with the following possible values | |||||
* RESULT_OK = 0 - success | |||||
* RESULT_USER_CANCELED = 1 - User pressed back or canceled a dialog | |||||
* RESULT_SERVICE_UNAVAILABLE = 2 - The network connection is down | |||||
* RESULT_BILLING_UNAVAILABLE = 3 - This billing API version is not supported for the type requested | |||||
* RESULT_ITEM_UNAVAILABLE = 4 - Requested SKU is not available for purchase | |||||
* RESULT_DEVELOPER_ERROR = 5 - Invalid arguments provided to the API | |||||
* RESULT_ERROR = 6 - Fatal error during the API action | |||||
* RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned | |||||
* RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned | |||||
*/ | |||||
public interface IInAppBillingService extends android.os.IInterface | |||||
{ | |||||
/** Local-side IPC implementation stub class. */ | |||||
public static abstract class Stub extends android.os.Binder implements com.android.vending.billing.IInAppBillingService | |||||
{ | |||||
private static final java.lang.String DESCRIPTOR = "com.android.vending.billing.IInAppBillingService"; | |||||
/** Construct the stub at attach it to the interface. */ | |||||
public Stub() | |||||
{ | |||||
this.attachInterface(this, DESCRIPTOR); | |||||
} | |||||
/** | |||||
* Cast an IBinder object into an com.android.vending.billing.IInAppBillingService interface, | |||||
* generating a proxy if needed. | |||||
*/ | |||||
public static com.android.vending.billing.IInAppBillingService asInterface(android.os.IBinder obj) | |||||
{ | |||||
if ((obj==null)) { | |||||
return null; | |||||
} | |||||
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); | |||||
if (((iin!=null)&&(iin instanceof com.android.vending.billing.IInAppBillingService))) { | |||||
return ((com.android.vending.billing.IInAppBillingService)iin); | |||||
} | |||||
return new com.android.vending.billing.IInAppBillingService.Stub.Proxy(obj); | |||||
} | |||||
@Override public android.os.IBinder asBinder() | |||||
{ | |||||
return this; | |||||
} | |||||
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException | |||||
{ | |||||
switch (code) | |||||
{ | |||||
case INTERFACE_TRANSACTION: | |||||
{ | |||||
reply.writeString(DESCRIPTOR); | |||||
return true; | |||||
} | |||||
case TRANSACTION_isBillingSupported: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
int _result = this.isBillingSupported(_arg0, _arg1, _arg2); | |||||
reply.writeNoException(); | |||||
reply.writeInt(_result); | |||||
return true; | |||||
} | |||||
case TRANSACTION_getSkuDetails: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
android.os.Bundle _arg3; | |||||
if ((0!=data.readInt())) { | |||||
_arg3 = android.os.Bundle.CREATOR.createFromParcel(data); | |||||
} | |||||
else { | |||||
_arg3 = null; | |||||
} | |||||
android.os.Bundle _result = this.getSkuDetails(_arg0, _arg1, _arg2, _arg3); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_getBuyIntent: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
java.lang.String _arg3; | |||||
_arg3 = data.readString(); | |||||
java.lang.String _arg4; | |||||
_arg4 = data.readString(); | |||||
android.os.Bundle _result = this.getBuyIntent(_arg0, _arg1, _arg2, _arg3, _arg4); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_getPurchases: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
java.lang.String _arg3; | |||||
_arg3 = data.readString(); | |||||
android.os.Bundle _result = this.getPurchases(_arg0, _arg1, _arg2, _arg3); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_consumePurchase: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
int _result = this.consumePurchase(_arg0, _arg1, _arg2); | |||||
reply.writeNoException(); | |||||
reply.writeInt(_result); | |||||
return true; | |||||
} | |||||
case TRANSACTION_stub: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
int _result = this.stub(_arg0, _arg1, _arg2); | |||||
reply.writeNoException(); | |||||
reply.writeInt(_result); | |||||
return true; | |||||
} | |||||
case TRANSACTION_getBuyIntentToReplaceSkus: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.util.List<java.lang.String> _arg2; | |||||
_arg2 = data.createStringArrayList(); | |||||
java.lang.String _arg3; | |||||
_arg3 = data.readString(); | |||||
java.lang.String _arg4; | |||||
_arg4 = data.readString(); | |||||
java.lang.String _arg5; | |||||
_arg5 = data.readString(); | |||||
android.os.Bundle _result = this.getBuyIntentToReplaceSkus(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_getBuyIntentExtraParams: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
java.lang.String _arg3; | |||||
_arg3 = data.readString(); | |||||
java.lang.String _arg4; | |||||
_arg4 = data.readString(); | |||||
android.os.Bundle _arg5; | |||||
if ((0!=data.readInt())) { | |||||
_arg5 = android.os.Bundle.CREATOR.createFromParcel(data); | |||||
} | |||||
else { | |||||
_arg5 = null; | |||||
} | |||||
android.os.Bundle _result = this.getBuyIntentExtraParams(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_getPurchaseHistory: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
java.lang.String _arg3; | |||||
_arg3 = data.readString(); | |||||
android.os.Bundle _arg4; | |||||
if ((0!=data.readInt())) { | |||||
_arg4 = android.os.Bundle.CREATOR.createFromParcel(data); | |||||
} | |||||
else { | |||||
_arg4 = null; | |||||
} | |||||
android.os.Bundle _result = this.getPurchaseHistory(_arg0, _arg1, _arg2, _arg3, _arg4); | |||||
reply.writeNoException(); | |||||
if ((_result!=null)) { | |||||
reply.writeInt(1); | |||||
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); | |||||
} | |||||
else { | |||||
reply.writeInt(0); | |||||
} | |||||
return true; | |||||
} | |||||
case TRANSACTION_isBillingSupportedExtraParams: | |||||
{ | |||||
data.enforceInterface(DESCRIPTOR); | |||||
int _arg0; | |||||
_arg0 = data.readInt(); | |||||
java.lang.String _arg1; | |||||
_arg1 = data.readString(); | |||||
java.lang.String _arg2; | |||||
_arg2 = data.readString(); | |||||
android.os.Bundle _arg3; | |||||
if ((0!=data.readInt())) { | |||||
_arg3 = android.os.Bundle.CREATOR.createFromParcel(data); | |||||
} | |||||
else { | |||||
_arg3 = null; | |||||
} | |||||
int _result = this.isBillingSupportedExtraParams(_arg0, _arg1, _arg2, _arg3); | |||||
reply.writeNoException(); | |||||
reply.writeInt(_result); | |||||
return true; | |||||
} | |||||
} | |||||
return super.onTransact(code, data, reply, flags); | |||||
} | |||||
private static class Proxy implements com.android.vending.billing.IInAppBillingService | |||||
{ | |||||
private android.os.IBinder mRemote; | |||||
Proxy(android.os.IBinder remote) | |||||
{ | |||||
mRemote = remote; | |||||
} | |||||
@Override public android.os.IBinder asBinder() | |||||
{ | |||||
return mRemote; | |||||
} | |||||
public java.lang.String getInterfaceDescriptor() | |||||
{ | |||||
return DESCRIPTOR; | |||||
} | |||||
@Override public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
int _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
mRemote.transact(Stub.TRANSACTION_isBillingSupported, _data, _reply, 0); | |||||
_reply.readException(); | |||||
_result = _reply.readInt(); | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Provides details of a list of SKUs | |||||
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle | |||||
* with a list JSON strings containing the productId, price, title and description. | |||||
* This API can be called with a maximum of 20 SKUs. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName the package name of the calling app | |||||
* @param type of the in-app items ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "DETAILS_LIST" with a StringArrayList containing purchase information | |||||
* in JSON format similar to: | |||||
* '{ "productId" : "exampleSku", | |||||
* "type" : "inapp", | |||||
* "price" : "$5.00", | |||||
* "price_currency": "USD", | |||||
* "price_amount_micros": 5000000, | |||||
* "title : "Example Title", | |||||
* "description" : "This is an example description" }' | |||||
*/ | |||||
@Override public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
if ((skusBundle!=null)) { | |||||
_data.writeInt(1); | |||||
skusBundle.writeToParcel(_data, 0); | |||||
} | |||||
else { | |||||
_data.writeInt(0); | |||||
} | |||||
mRemote.transact(Stub.TRANSACTION_getSkuDetails, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, | |||||
* the type, a unique purchase token and an optional developer payload. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName package name of the calling app | |||||
* @param sku the SKU of the in-app item as published in the developer console | |||||
* @param type of the in-app item being purchased ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "BUY_INTENT" - PendingIntent to start the purchase flow | |||||
* | |||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow | |||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED. | |||||
* If the purchase is successful, the result data will contain the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response | |||||
* codes on failures. | |||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to | |||||
* '{"orderId":"12999763169054705758.1371079406387615", | |||||
* "packageName":"com.example.app", | |||||
* "productId":"exampleSku", | |||||
* "purchaseTime":1345678900000, | |||||
* "purchaseToken" : "122333444455555", | |||||
* "developerPayload":"example developer payload" }' | |||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that | |||||
* was signed with the private key of the developer | |||||
*/ | |||||
@Override public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(sku); | |||||
_data.writeString(type); | |||||
_data.writeString(developerPayload); | |||||
mRemote.transact(Stub.TRANSACTION_getBuyIntent, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Returns the current SKUs owned by the user of the type and package name specified along with | |||||
* purchase information and a signature of the data to be validated. | |||||
* This will return all SKUs that have been purchased in V3 and managed items purchased using | |||||
* V1 and V2 that have not been consumed. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName package name of the calling app | |||||
* @param type of the in-app items being requested ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param continuationToken to be set as null for the first call, if the number of owned | |||||
* skus are too many, a continuationToken is returned in the response bundle. | |||||
* This method can be called again with the continuation token to get the next set of | |||||
* owned skus. | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
on failures. | |||||
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs | |||||
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information | |||||
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures | |||||
* of the purchase information | |||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the | |||||
* next set of in-app purchases. Only set if the | |||||
* user has more owned skus than the current list. | |||||
*/ | |||||
@Override public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
_data.writeString(continuationToken); | |||||
mRemote.transact(Stub.TRANSACTION_getPurchases, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
@Override public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
int _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(purchaseToken); | |||||
mRemote.transact(Stub.TRANSACTION_consumePurchase, _data, _reply, 0); | |||||
_reply.readException(); | |||||
_result = _reply.readInt(); | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
@Override public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
int _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
mRemote.transact(Stub.TRANSACTION_stub, _data, _reply, 0); | |||||
_reply.readException(); | |||||
_result = _reply.readInt(); | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for upgrading or downgrading a | |||||
* subscription. The existing owned SKU(s) should be provided along with the new SKU that | |||||
* the user is upgrading or downgrading to. | |||||
* @param apiVersion billing API version that the app is using, must be 5 or later | |||||
* @param packageName package name of the calling app | |||||
* @param oldSkus the SKU(s) that the user is upgrading or downgrading from, | |||||
* if null or empty this method will behave like {@link #getBuyIntent} | |||||
* @param newSku the SKU that the user is upgrading or downgrading to | |||||
* @param type of the item being purchased, currently must be "subs" | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "BUY_INTENT" - PendingIntent to start the purchase flow | |||||
* | |||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow | |||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED. | |||||
* If the purchase is successful, the result data will contain the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response | |||||
* codes on failures. | |||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to | |||||
* '{"orderId":"12999763169054705758.1371079406387615", | |||||
* "packageName":"com.example.app", | |||||
* "productId":"exampleSku", | |||||
* "purchaseTime":1345678900000, | |||||
* "purchaseToken" : "122333444455555", | |||||
* "developerPayload":"example developer payload" }' | |||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that | |||||
* was signed with the private key of the developer | |||||
*/ | |||||
@Override public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List<java.lang.String> oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeStringList(oldSkus); | |||||
_data.writeString(newSku); | |||||
_data.writeString(type); | |||||
_data.writeString(developerPayload); | |||||
mRemote.transact(Stub.TRANSACTION_getBuyIntentToReplaceSkus, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for an in-app item. This method is | |||||
* a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} | |||||
* parameter. This parameter is a Bundle of optional keys and values that affect the | |||||
* operation of the method. | |||||
* @param apiVersion billing API version that the app is using, must be 6 or later | |||||
* @param packageName package name of the calling app | |||||
* @param sku the SKU of the in-app item as published in the developer console | |||||
* @param type of the in-app item being purchased ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @extraParams a Bundle with the following optional keys: | |||||
* "skusToReplace" - List<String> - an optional list of SKUs that the user is | |||||
* upgrading or downgrading from. | |||||
* Pass this field if the purchase is upgrading or downgrading | |||||
* existing subscriptions. | |||||
* The specified SKUs are replaced with the SKUs that the user is | |||||
* purchasing. Google Play replaces the specified SKUs at the start of | |||||
* the next billing cycle. | |||||
* "replaceSkusProration" - Boolean - whether the user should be credited for any unused | |||||
* subscription time on the SKUs they are upgrading or downgrading. | |||||
* If you set this field to true, Google Play swaps out the old SKUs | |||||
* and credits the user with the unused value of their subscription | |||||
* time on a pro-rated basis. | |||||
* Google Play applies this credit to the new subscription, and does | |||||
* not begin billing the user for the new subscription until after | |||||
* the credit is used up. | |||||
* If you set this field to false, the user does not receive credit for | |||||
* any unused subscription time and the recurrence date does not | |||||
* change. | |||||
* Default value is true. Ignored if you do not pass skusToReplace. | |||||
* "accountId" - String - an optional obfuscated string that is uniquely | |||||
* associated with the user's account in your app. | |||||
* If you pass this value, Google Play can use it to detect irregular | |||||
* activity, such as many devices making purchases on the same | |||||
* account in a short period of time. | |||||
* Do not use the developer ID or the user's Google ID for this field. | |||||
* In addition, this field should not contain the user's ID in | |||||
* cleartext. | |||||
* We recommend that you use a one-way hash to generate a string from | |||||
* the user's ID, and store the hashed string in this field. | |||||
* "vr" - Boolean - an optional flag indicating whether the returned intent | |||||
* should start a VR purchase flow. The apiVersion must also be 7 or | |||||
* later to use this flag. | |||||
*/ | |||||
@Override public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(sku); | |||||
_data.writeString(type); | |||||
_data.writeString(developerPayload); | |||||
if ((extraParams!=null)) { | |||||
_data.writeInt(1); | |||||
extraParams.writeToParcel(_data, 0); | |||||
} | |||||
else { | |||||
_data.writeInt(0); | |||||
} | |||||
mRemote.transact(Stub.TRANSACTION_getBuyIntentExtraParams, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
/** | |||||
* Returns the most recent purchase made by the user for each SKU, even if that purchase is | |||||
* expired, canceled, or consumed. | |||||
* @param apiVersion billing API version that the app is using, must be 6 or later | |||||
* @param packageName package name of the calling app | |||||
* @param type of the in-app items being requested ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param continuationToken to be set as null for the first call, if the number of owned | |||||
* skus is too large, a continuationToken is returned in the response bundle. | |||||
* This method can be called again with the continuation token to get the next set of | |||||
* owned skus. | |||||
* @param extraParams a Bundle with extra params that would be appended into http request | |||||
* query string. Not used at this moment. Reserved for future functionality. | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value: RESULT_OK(0) if success, | |||||
* {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. | |||||
* | |||||
* "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs | |||||
* "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information | |||||
* "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures | |||||
* of the purchase information | |||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the | |||||
* next set of in-app purchases. Only set if the | |||||
* user has more owned skus than the current list. | |||||
*/ | |||||
@Override public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
android.os.Bundle _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
_data.writeString(continuationToken); | |||||
if ((extraParams!=null)) { | |||||
_data.writeInt(1); | |||||
extraParams.writeToParcel(_data, 0); | |||||
} | |||||
else { | |||||
_data.writeInt(0); | |||||
} | |||||
mRemote.transact(Stub.TRANSACTION_getPurchaseHistory, _data, _reply, 0); | |||||
_reply.readException(); | |||||
if ((0!=_reply.readInt())) { | |||||
_result = android.os.Bundle.CREATOR.createFromParcel(_reply); | |||||
} | |||||
else { | |||||
_result = null; | |||||
} | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
@Override public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException | |||||
{ | |||||
android.os.Parcel _data = android.os.Parcel.obtain(); | |||||
android.os.Parcel _reply = android.os.Parcel.obtain(); | |||||
int _result; | |||||
try { | |||||
_data.writeInterfaceToken(DESCRIPTOR); | |||||
_data.writeInt(apiVersion); | |||||
_data.writeString(packageName); | |||||
_data.writeString(type); | |||||
if ((extraParams!=null)) { | |||||
_data.writeInt(1); | |||||
extraParams.writeToParcel(_data, 0); | |||||
} | |||||
else { | |||||
_data.writeInt(0); | |||||
} | |||||
mRemote.transact(Stub.TRANSACTION_isBillingSupportedExtraParams, _data, _reply, 0); | |||||
_reply.readException(); | |||||
_result = _reply.readInt(); | |||||
} | |||||
finally { | |||||
_reply.recycle(); | |||||
_data.recycle(); | |||||
} | |||||
return _result; | |||||
} | |||||
} | |||||
static final int TRANSACTION_isBillingSupported = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); | |||||
static final int TRANSACTION_getSkuDetails = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); | |||||
static final int TRANSACTION_getBuyIntent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); | |||||
static final int TRANSACTION_getPurchases = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); | |||||
static final int TRANSACTION_consumePurchase = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); | |||||
static final int TRANSACTION_stub = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5); | |||||
static final int TRANSACTION_getBuyIntentToReplaceSkus = (android.os.IBinder.FIRST_CALL_TRANSACTION + 6); | |||||
static final int TRANSACTION_getBuyIntentExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 7); | |||||
static final int TRANSACTION_getPurchaseHistory = (android.os.IBinder.FIRST_CALL_TRANSACTION + 8); | |||||
static final int TRANSACTION_isBillingSupportedExtraParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 9); | |||||
} | |||||
public int isBillingSupported(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException; | |||||
/** | |||||
* Provides details of a list of SKUs | |||||
* Given a list of SKUs of a valid type in the skusBundle, this returns a bundle | |||||
* with a list JSON strings containing the productId, price, title and description. | |||||
* This API can be called with a maximum of 20 SKUs. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName the package name of the calling app | |||||
* @param type of the in-app items ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "DETAILS_LIST" with a StringArrayList containing purchase information | |||||
* in JSON format similar to: | |||||
* '{ "productId" : "exampleSku", | |||||
* "type" : "inapp", | |||||
* "price" : "$5.00", | |||||
* "price_currency": "USD", | |||||
* "price_amount_micros": 5000000, | |||||
* "title : "Example Title", | |||||
* "description" : "This is an example description" }' | |||||
*/ | |||||
public android.os.Bundle getSkuDetails(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle skusBundle) throws android.os.RemoteException; | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, | |||||
* the type, a unique purchase token and an optional developer payload. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName package name of the calling app | |||||
* @param sku the SKU of the in-app item as published in the developer console | |||||
* @param type of the in-app item being purchased ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "BUY_INTENT" - PendingIntent to start the purchase flow | |||||
* | |||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow | |||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED. | |||||
* If the purchase is successful, the result data will contain the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response | |||||
* codes on failures. | |||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to | |||||
* '{"orderId":"12999763169054705758.1371079406387615", | |||||
* "packageName":"com.example.app", | |||||
* "productId":"exampleSku", | |||||
* "purchaseTime":1345678900000, | |||||
* "purchaseToken" : "122333444455555", | |||||
* "developerPayload":"example developer payload" }' | |||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that | |||||
* was signed with the private key of the developer | |||||
*/ | |||||
public android.os.Bundle getBuyIntent(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException; | |||||
/** | |||||
* Returns the current SKUs owned by the user of the type and package name specified along with | |||||
* purchase information and a signature of the data to be validated. | |||||
* This will return all SKUs that have been purchased in V3 and managed items purchased using | |||||
* V1 and V2 that have not been consumed. | |||||
* @param apiVersion billing API version that the app is using | |||||
* @param packageName package name of the calling app | |||||
* @param type of the in-app items being requested ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param continuationToken to be set as null for the first call, if the number of owned | |||||
* skus are too many, a continuationToken is returned in the response bundle. | |||||
* This method can be called again with the continuation token to get the next set of | |||||
* owned skus. | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
on failures. | |||||
* "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs | |||||
* "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information | |||||
* "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures | |||||
* of the purchase information | |||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the | |||||
* next set of in-app purchases. Only set if the | |||||
* user has more owned skus than the current list. | |||||
*/ | |||||
public android.os.Bundle getPurchases(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken) throws android.os.RemoteException; | |||||
public int consumePurchase(int apiVersion, java.lang.String packageName, java.lang.String purchaseToken) throws android.os.RemoteException; | |||||
public int stub(int apiVersion, java.lang.String packageName, java.lang.String type) throws android.os.RemoteException; | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for upgrading or downgrading a | |||||
* subscription. The existing owned SKU(s) should be provided along with the new SKU that | |||||
* the user is upgrading or downgrading to. | |||||
* @param apiVersion billing API version that the app is using, must be 5 or later | |||||
* @param packageName package name of the calling app | |||||
* @param oldSkus the SKU(s) that the user is upgrading or downgrading from, | |||||
* if null or empty this method will behave like {@link #getBuyIntent} | |||||
* @param newSku the SKU that the user is upgrading or downgrading to | |||||
* @param type of the item being purchased, currently must be "subs" | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response codes | |||||
* on failures. | |||||
* "BUY_INTENT" - PendingIntent to start the purchase flow | |||||
* | |||||
* The Pending intent should be launched with startIntentSenderForResult. When purchase flow | |||||
* has completed, the onActivityResult() will give a resultCode of OK or CANCELED. | |||||
* If the purchase is successful, the result data will contain the following key-value pairs | |||||
* "RESPONSE_CODE" with int value, RESULT_OK(0) if success, appropriate response | |||||
* codes on failures. | |||||
* "INAPP_PURCHASE_DATA" - String in JSON format similar to | |||||
* '{"orderId":"12999763169054705758.1371079406387615", | |||||
* "packageName":"com.example.app", | |||||
* "productId":"exampleSku", | |||||
* "purchaseTime":1345678900000, | |||||
* "purchaseToken" : "122333444455555", | |||||
* "developerPayload":"example developer payload" }' | |||||
* "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that | |||||
* was signed with the private key of the developer | |||||
*/ | |||||
public android.os.Bundle getBuyIntentToReplaceSkus(int apiVersion, java.lang.String packageName, java.util.List<java.lang.String> oldSkus, java.lang.String newSku, java.lang.String type, java.lang.String developerPayload) throws android.os.RemoteException; | |||||
/** | |||||
* Returns a pending intent to launch the purchase flow for an in-app item. This method is | |||||
* a variant of the {@link #getBuyIntent} method and takes an additional {@code extraParams} | |||||
* parameter. This parameter is a Bundle of optional keys and values that affect the | |||||
* operation of the method. | |||||
* @param apiVersion billing API version that the app is using, must be 6 or later | |||||
* @param packageName package name of the calling app | |||||
* @param sku the SKU of the in-app item as published in the developer console | |||||
* @param type of the in-app item being purchased ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param developerPayload optional argument to be sent back with the purchase information | |||||
* @extraParams a Bundle with the following optional keys: | |||||
* "skusToReplace" - List<String> - an optional list of SKUs that the user is | |||||
* upgrading or downgrading from. | |||||
* Pass this field if the purchase is upgrading or downgrading | |||||
* existing subscriptions. | |||||
* The specified SKUs are replaced with the SKUs that the user is | |||||
* purchasing. Google Play replaces the specified SKUs at the start of | |||||
* the next billing cycle. | |||||
* "replaceSkusProration" - Boolean - whether the user should be credited for any unused | |||||
* subscription time on the SKUs they are upgrading or downgrading. | |||||
* If you set this field to true, Google Play swaps out the old SKUs | |||||
* and credits the user with the unused value of their subscription | |||||
* time on a pro-rated basis. | |||||
* Google Play applies this credit to the new subscription, and does | |||||
* not begin billing the user for the new subscription until after | |||||
* the credit is used up. | |||||
* If you set this field to false, the user does not receive credit for | |||||
* any unused subscription time and the recurrence date does not | |||||
* change. | |||||
* Default value is true. Ignored if you do not pass skusToReplace. | |||||
* "accountId" - String - an optional obfuscated string that is uniquely | |||||
* associated with the user's account in your app. | |||||
* If you pass this value, Google Play can use it to detect irregular | |||||
* activity, such as many devices making purchases on the same | |||||
* account in a short period of time. | |||||
* Do not use the developer ID or the user's Google ID for this field. | |||||
* In addition, this field should not contain the user's ID in | |||||
* cleartext. | |||||
* We recommend that you use a one-way hash to generate a string from | |||||
* the user's ID, and store the hashed string in this field. | |||||
* "vr" - Boolean - an optional flag indicating whether the returned intent | |||||
* should start a VR purchase flow. The apiVersion must also be 7 or | |||||
* later to use this flag. | |||||
*/ | |||||
public android.os.Bundle getBuyIntentExtraParams(int apiVersion, java.lang.String packageName, java.lang.String sku, java.lang.String type, java.lang.String developerPayload, android.os.Bundle extraParams) throws android.os.RemoteException; | |||||
/** | |||||
* Returns the most recent purchase made by the user for each SKU, even if that purchase is | |||||
* expired, canceled, or consumed. | |||||
* @param apiVersion billing API version that the app is using, must be 6 or later | |||||
* @param packageName package name of the calling app | |||||
* @param type of the in-app items being requested ("inapp" for one-time purchases | |||||
* and "subs" for subscriptions) | |||||
* @param continuationToken to be set as null for the first call, if the number of owned | |||||
* skus is too large, a continuationToken is returned in the response bundle. | |||||
* This method can be called again with the continuation token to get the next set of | |||||
* owned skus. | |||||
* @param extraParams a Bundle with extra params that would be appended into http request | |||||
* query string. Not used at this moment. Reserved for future functionality. | |||||
* @return Bundle containing the following key-value pairs | |||||
* "RESPONSE_CODE" with int value: RESULT_OK(0) if success, | |||||
* {@link IabHelper#BILLING_RESPONSE_RESULT_*} response codes on failures. | |||||
* | |||||
* "INAPP_PURCHASE_ITEM_LIST" - ArrayList<String> containing the list of SKUs | |||||
* "INAPP_PURCHASE_DATA_LIST" - ArrayList<String> containing the purchase information | |||||
* "INAPP_DATA_SIGNATURE_LIST"- ArrayList<String> containing the signatures | |||||
* of the purchase information | |||||
* "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the | |||||
* next set of in-app purchases. Only set if the | |||||
* user has more owned skus than the current list. | |||||
*/ | |||||
public android.os.Bundle getPurchaseHistory(int apiVersion, java.lang.String packageName, java.lang.String type, java.lang.String continuationToken, android.os.Bundle extraParams) throws android.os.RemoteException; | |||||
public int isBillingSupportedExtraParams(int apiVersion, java.lang.String packageName, java.lang.String type, android.os.Bundle extraParams) throws android.os.RemoteException; | |||||
} |
@@ -55,8 +55,6 @@ import java.io.*; | |||||
import java.net.URL; | import java.net.URL; | ||||
import java.net.HttpURLConnection; | import java.net.HttpURLConnection; | ||||
import android.media.AudioManager; | import android.media.AudioManager; | ||||
import android.media.MediaScannerConnection; | |||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient; | |||||
import android.Manifest; | import android.Manifest; | ||||
import java.util.concurrent.CancellationException; | import java.util.concurrent.CancellationException; | ||||
import java.util.concurrent.Future; | import java.util.concurrent.Future; | ||||
@@ -68,7 +66,7 @@ import java.util.concurrent.Callable; | |||||
import java.util.concurrent.TimeoutException; | import java.util.concurrent.TimeoutException; | ||||
import java.util.concurrent.locks.ReentrantLock; | import java.util.concurrent.locks.ReentrantLock; | ||||
import java.util.concurrent.atomic.*; | import java.util.concurrent.atomic.*; | ||||
$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! | |||||
$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! | |||||
//============================================================================== | //============================================================================== | ||||
@@ -301,23 +299,7 @@ public class JuceAppActivity extends Activity | |||||
private native void suspendApp(); | private native void suspendApp(); | ||||
private native void resumeApp(); | private native void resumeApp(); | ||||
private native void setScreenSize (int screenWidth, int screenHeight, int dpi); | private native void setScreenSize (int screenWidth, int screenHeight, int dpi); | ||||
//============================================================================== | |||||
public native void deliverMessage (long value); | |||||
private android.os.Handler messageHandler = new android.os.Handler(); | |||||
public final void postMessage (long value) | |||||
{ | |||||
messageHandler.post (new MessageCallback (value)); | |||||
} | |||||
private final class MessageCallback implements Runnable | |||||
{ | |||||
public MessageCallback (long value_) { value = value_; } | |||||
public final void run() { deliverMessage (value); } | |||||
private long value; | |||||
} | |||||
private native void appActivityResult (int requestCode, int resultCode, Intent data); | |||||
//============================================================================== | //============================================================================== | ||||
private ViewHolder viewHolder; | private ViewHolder viewHolder; | ||||
@@ -882,6 +864,38 @@ public class JuceAppActivity extends Activity | |||||
private int[] cachedRenderArray = new int [256]; | private int[] cachedRenderArray = new int [256]; | ||||
//============================================================================== | |||||
public static class NativeInvocationHandler implements InvocationHandler | |||||
{ | |||||
public NativeInvocationHandler (long nativeContextRef) | |||||
{ | |||||
nativeContext = nativeContextRef; | |||||
} | |||||
@Override | |||||
public void finalize() | |||||
{ | |||||
dispatchFinalize (nativeContext); | |||||
} | |||||
@Override | |||||
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable | |||||
{ | |||||
return dispatchInvoke (nativeContext, proxy, method, args); | |||||
} | |||||
//============================================================================== | |||||
private long nativeContext = 0; | |||||
private native void dispatchFinalize (long nativeContextRef); | |||||
private native Object dispatchInvoke (long nativeContextRef, Object proxy, Method method, Object[] args); | |||||
} | |||||
public static InvocationHandler createInvocationHandler (long nativeContextRef) | |||||
{ | |||||
return new NativeInvocationHandler (nativeContextRef); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
public static class HTTPStream | public static class HTTPStream | ||||
{ | { | ||||
@@ -1185,36 +1199,13 @@ public class JuceAppActivity extends Activity | |||||
public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); } | public static final String getDownloadsFolder() { return getFileLocation (Environment.DIRECTORY_DOWNLOADS); } | ||||
//============================================================================== | //============================================================================== | ||||
private final class SingleMediaScanner implements MediaScannerConnectionClient | |||||
{ | |||||
public SingleMediaScanner (Context context, String filename) | |||||
{ | |||||
file = filename; | |||||
msc = new MediaScannerConnection (context, this); | |||||
msc.connect(); | |||||
} | |||||
@Override | |||||
public void onMediaScannerConnected() | |||||
{ | |||||
msc.scanFile (file, null); | |||||
} | |||||
@Override | |||||
public void onScanCompleted (String path, Uri uri) | |||||
{ | |||||
msc.disconnect(); | |||||
} | |||||
private MediaScannerConnection msc; | |||||
private String file; | |||||
} | |||||
public final void scanFile (String filename) | |||||
@Override | |||||
protected void onActivityResult (int requestCode, int resultCode, Intent data) | |||||
{ | { | ||||
new SingleMediaScanner (this, filename); | |||||
appActivityResult (requestCode, resultCode, data); | |||||
} | } | ||||
//============================================================================== | |||||
public final Typeface getTypeFaceFromAsset (String assetName) | public final Typeface getTypeFaceFromAsset (String assetName) | ||||
{ | { | ||||
try | try | ||||
@@ -20,6 +20,46 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (constructor, "<init>", "(Landroid/content/Context;Landroid/media/MediaScannerConnection$MediaScannerConnectionClient;)V") \ | |||||
METHOD (connect, "connect", "()V") \ | |||||
METHOD (disconnect, "disconnect", "()V") \ | |||||
METHOD (scanFile, "scanFile", "(Ljava/lang/String;Ljava/lang/String;)V") \ | |||||
DECLARE_JNI_CLASS (MediaScannerConnection, "android/media/MediaScannerConnection"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
class MediaScannerConnectionClient : public AndroidInterfaceImplementer | |||||
{ | |||||
public: | |||||
virtual void onMediaScannerConnected() = 0; | |||||
virtual void onScanCompleted() = 0; | |||||
private: | |||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||||
{ | |||||
auto* env = getEnv(); | |||||
auto methodName = juceString ((jstring) env->CallObjectMethod (method, Method.getName)); | |||||
if (methodName == "onMediaScannerConnected") | |||||
{ | |||||
onMediaScannerConnected(); | |||||
return nullptr; | |||||
} | |||||
else if (methodName == "onScanCompleted") | |||||
{ | |||||
onScanCompleted(); | |||||
return nullptr; | |||||
} | |||||
return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
bool File::isOnCDRomDrive() const | bool File::isOnCDRomDrive() const | ||||
{ | { | ||||
return false; | return false; | ||||
@@ -100,3 +140,49 @@ JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const | |||||
void File::revealToUser() const | void File::revealToUser() const | ||||
{ | { | ||||
} | } | ||||
//============================================================================== | |||||
class SingleMediaScanner : public MediaScannerConnectionClient | |||||
{ | |||||
public: | |||||
SingleMediaScanner (const String& filename) | |||||
: msc (getEnv()->NewObject (MediaScannerConnection, | |||||
MediaScannerConnection.constructor, | |||||
android.activity.get(), | |||||
CreateJavaInterface (this, "android/media/MediaScannerConnection$MediaScannerConnectionClient").get())), | |||||
file (filename) | |||||
{ | |||||
getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.connect); | |||||
} | |||||
void onMediaScannerConnected() override | |||||
{ | |||||
auto* env = getEnv(); | |||||
env->CallVoidMethod (msc.get(), MediaScannerConnection.scanFile, javaString (file).get(), 0); | |||||
} | |||||
void onScanCompleted() override | |||||
{ | |||||
getEnv()->CallVoidMethod (msc.get(), MediaScannerConnection.disconnect); | |||||
} | |||||
private: | |||||
GlobalRef msc; | |||||
String file; | |||||
}; | |||||
void FileOutputStream::flushInternal() | |||||
{ | |||||
if (fileHandle != 0) | |||||
{ | |||||
if (fsync (getFD (fileHandle)) == -1) | |||||
status = getResultForErrno(); | |||||
// This stuff tells the OS to asynchronously update the metadata | |||||
// that the OS has cached aboud the file - this metadata is used | |||||
// when the device is acting as a USB drive, and unless it's explicitly | |||||
// refreshed, it'll get out of step with the real file. | |||||
new SingleMediaScanner (file.getFullPathName()); | |||||
} | |||||
} |
@@ -40,10 +40,11 @@ extern JNIEnv* attachAndroidJNI() noexcept; | |||||
class GlobalRef | class GlobalRef | ||||
{ | { | ||||
public: | public: | ||||
inline GlobalRef() noexcept : obj (0) {} | |||||
inline explicit GlobalRef (jobject o) : obj (retain (o)) {} | |||||
inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj)) {} | |||||
~GlobalRef() { clear(); } | |||||
inline GlobalRef() noexcept : obj (0) {} | |||||
inline explicit GlobalRef (jobject o) : obj (retain (o)) {} | |||||
inline GlobalRef (const GlobalRef& other) : obj (retain (other.obj)) {} | |||||
inline GlobalRef (GlobalRef && other) noexcept : obj (0) { std::swap (other.obj, obj); } | |||||
~GlobalRef() { clear(); } | |||||
inline void clear() | inline void clear() | ||||
{ | { | ||||
@@ -62,6 +63,14 @@ public: | |||||
return *this; | return *this; | ||||
} | } | ||||
inline GlobalRef& operator= (GlobalRef&& other) | |||||
{ | |||||
clear(); | |||||
std::swap (obj, other.obj); | |||||
return *this; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
inline operator jobject() const noexcept { return obj; } | inline operator jobject() const noexcept { return obj; } | ||||
inline jobject get() const noexcept { return obj; } | inline jobject get() const noexcept { return obj; } | ||||
@@ -98,7 +107,7 @@ public: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
jobject obj; | |||||
jobject obj = 0; | |||||
static inline jobject retain (jobject obj) | static inline jobject retain (jobject obj) | ||||
{ | { | ||||
@@ -111,14 +120,19 @@ template <typename JavaType> | |||||
class LocalRef | class LocalRef | ||||
{ | { | ||||
public: | public: | ||||
explicit inline LocalRef () noexcept : obj (0) {} | |||||
explicit inline LocalRef (JavaType o) noexcept : obj (o) {} | explicit inline LocalRef (JavaType o) noexcept : obj (o) {} | ||||
inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} | inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} | ||||
inline LocalRef (LocalRef&& other) noexcept : obj (0) { std::swap (obj, other.obj); } | |||||
~LocalRef() { clear(); } | ~LocalRef() { clear(); } | ||||
void clear() | void clear() | ||||
{ | { | ||||
if (obj != 0) | if (obj != 0) | ||||
{ | |||||
getEnv()->DeleteLocalRef (obj); | getEnv()->DeleteLocalRef (obj); | ||||
obj = 0; | |||||
} | |||||
} | } | ||||
LocalRef& operator= (const LocalRef& other) | LocalRef& operator= (const LocalRef& other) | ||||
@@ -129,6 +143,13 @@ public: | |||||
return *this; | return *this; | ||||
} | } | ||||
LocalRef& operator= (LocalRef&& other) | |||||
{ | |||||
clear(); | |||||
std::swap (other.obj, obj); | |||||
return *this; | |||||
} | |||||
inline operator JavaType() const noexcept { return obj; } | inline operator JavaType() const noexcept { return obj; } | ||||
inline JavaType get() const noexcept { return obj; } | inline JavaType get() const noexcept { return obj; } | ||||
@@ -258,29 +279,27 @@ extern AndroidSystem android; | |||||
//============================================================================== | //============================================================================== | ||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | ||||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ | |||||
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ | |||||
METHOD (createNewView, "createNewView", "(ZJ)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;") \ | |||||
METHOD (deleteView, "deleteView", "(L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView;)V") \ | |||||
METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ | METHOD (createNativeSurfaceView, "createNativeSurfaceView", "(J)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$NativeSurfaceView;") \ | ||||
METHOD (postMessage, "postMessage", "(J)V") \ | |||||
METHOD (finish, "finish", "()V") \ | |||||
METHOD (finish, "finish", "()V") \ | |||||
METHOD (setRequestedOrientation,"setRequestedOrientation", "(I)V") \ | METHOD (setRequestedOrientation,"setRequestedOrientation", "(I)V") \ | ||||
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | |||||
METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | |||||
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | |||||
METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ | |||||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ | |||||
METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ | |||||
METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||||
METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \ | |||||
METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||||
STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ | |||||
STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ | |||||
METHOD (scanFile, "scanFile", "(Ljava/lang/String;)V") \ | |||||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | |||||
METHOD (getClipboardContent, "getClipboardContent", "()Ljava/lang/String;") \ | |||||
METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | |||||
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | |||||
METHOD (renderGlyph, "renderGlyph", "(CCLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ | |||||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;ILjava/lang/String;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ | |||||
METHOD (launchURL, "launchURL", "(Ljava/lang/String;)V") \ | |||||
METHOD (showMessageBox, "showMessageBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||||
METHOD (showOkCancelBox, "showOkCancelBox", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)V") \ | |||||
METHOD (showYesNoCancelBox, "showYesNoCancelBox", "(Ljava/lang/String;Ljava/lang/String;J)V") \ | |||||
STATICMETHOD (getLocaleValue, "getLocaleValue", "(Z)Ljava/lang/String;") \ | |||||
STATICMETHOD (getDocumentsFolder, "getDocumentsFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getPicturesFolder, "getPicturesFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getMusicFolder, "getMusicFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getDownloadsFolder, "getDownloadsFolder", "()Ljava/lang/String;") \ | |||||
STATICMETHOD (getMoviesFolder, "getMoviesFolder", "()Ljava/lang/String;") \ | |||||
METHOD (getTypeFaceFromAsset, "getTypeFaceFromAsset", "(Ljava/lang/String;)Landroid/graphics/Typeface;") \ | |||||
METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | METHOD (getTypeFaceFromByteArray,"getTypeFaceFromByteArray","([B)Landroid/graphics/Typeface;") \ | ||||
METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ | METHOD (setScreenSaver, "setScreenSaver", "(Z)V") \ | ||||
METHOD (getScreenSaver, "getScreenSaver", "()Z") \ | METHOD (getScreenSaver, "getScreenSaver", "()Z") \ | ||||
@@ -288,11 +307,16 @@ extern AndroidSystem android; | |||||
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | ||||
METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | ||||
METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | METHOD (audioManagerGetProperty, "audioManagerGetProperty", "(Ljava/lang/String;)Ljava/lang/String;") \ | ||||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | |||||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \ | |||||
METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \ | ||||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | |||||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | |||||
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | ||||
METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \ | METHOD (getSystemService, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;") \ | ||||
STATICMETHOD (createInvocationHandler, "createInvocationHandler", "(J)Ljava/lang/reflect/InvocationHandler;") \ | |||||
METHOD (bindService, "bindService", "(Landroid/content/Intent;Landroid/content/ServiceConnection;I)Z") \ | |||||
METHOD (unbindService, "unbindService", "(Landroid/content/ServiceConnection;)V") \ | |||||
METHOD (startIntentSenderForResult, "startIntentSenderForResult", "(Landroid/content/IntentSender;ILandroid/content/Intent;III)V") \ | |||||
METHOD (getPackageName, "getPackageName", "()Ljava/lang/String;") \ | |||||
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); | DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH); | ||||
#undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
@@ -332,3 +356,76 @@ DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | |||||
DECLARE_JNI_CLASS (RectClass, "android/graphics/Rect"); | DECLARE_JNI_CLASS (RectClass, "android/graphics/Rect"); | ||||
#undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (getName, "getName", "()Ljava/lang/String;") \ | |||||
METHOD (getModifiers, "getModifiers", "()I") \ | |||||
METHOD (getParameterTypes, "getParameterTypes", "()[Ljava/lang/Class;") \ | |||||
METHOD (getReturnType, "getReturnType", "()Ljava/lang/Class;") \ | |||||
METHOD (invoke, "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;") \ | |||||
METHOD (hashCode, "hashCode", "()I") \ | |||||
METHOD (equals, "equals", "(Ljava/lang/Object;)Z") \ | |||||
DECLARE_JNI_CLASS (Method, "java/lang/reflect/Method"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (getName, "getName", "()Ljava/lang/String;") \ | |||||
METHOD (getModifiers, "getModifiers", "()I") \ | |||||
METHOD (isAnnotation, "isAnnotation", "()Z") \ | |||||
METHOD (isAnonymousClass, "isAnonymousClass", "()Z") \ | |||||
METHOD (isArray, "isArray", "()Z") \ | |||||
METHOD (isEnum, "isEnum", "()Z") \ | |||||
METHOD (isInterface, "isInterface", "()Z") \ | |||||
METHOD (isLocalClass, "isLocalClass", "()Z") \ | |||||
METHOD (isMemberClass, "isMemberClass", "()Z") \ | |||||
METHOD (isPrimitive, "isPrimitive", "()Z") \ | |||||
METHOD (isSynthetic, "isSynthetic", "()Z") \ | |||||
METHOD (getComponentType, "getComponentType", "()Ljava/lang/Class;") \ | |||||
METHOD (getSuperclass, "getSuperclass", "()Ljava/lang/Class;") \ | |||||
METHOD (getClassLoader, "getClassLoader", "()Ljava/lang/ClassLoader;") \ | |||||
DECLARE_JNI_CLASS (JavaClass, "java/lang/Class"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (constructor, "<init>", "()V") \ | |||||
DECLARE_JNI_CLASS (JavaObject, "java/lang/Object"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
class AndroidInterfaceImplementer; | |||||
// This function takes ownership of the implementer. When the returned GlobalRef | |||||
// goes out of scope (and no other Java routine has a reference on the return-value) | |||||
// then the implementer will be deleted as well. | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const StringArray& interfaceNames, | |||||
LocalRef<jobject> subclass); | |||||
//============================================================================== | |||||
jobject juce_invokeImplementer (JNIEnv*, jlong, jobject, jobject, jobjectArray); | |||||
void juce_dispatchDelete (JNIEnv*, jlong); | |||||
//============================================================================== | |||||
class AndroidInterfaceImplementer | |||||
{ | |||||
protected: | |||||
virtual ~AndroidInterfaceImplementer() {} | |||||
virtual jobject invoke (jobject proxy, jobject method, jobjectArray args); | |||||
//============================================================================== | |||||
friend LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer*, const StringArray&, LocalRef<jobject>); | |||||
friend jobject juce_invokeImplementer (JNIEnv*, jlong, jobject, jobject, jobjectArray); | |||||
friend void juce_dispatchDelete (JNIEnv*, jlong); | |||||
private: | |||||
GlobalRef javaSubClass; | |||||
}; | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const StringArray& interfaceNames); | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const String& interfaceName); |
@@ -20,6 +20,13 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
STATICMETHOD (newProxyInstance, "newProxyInstance", "(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;") \ | |||||
DECLARE_JNI_CLASS (JavaProxy, "java/lang/reflect/Proxy"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
JNIClassBase::JNIClassBase (const char* cp) : classPath (cp), classRef (0) | JNIClassBase::JNIClassBase (const char* cp) : classPath (cp), classRef (0) | ||||
{ | { | ||||
getClasses().add (this); | getClasses().add (this); | ||||
@@ -91,6 +98,92 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c | |||||
return f; | return f; | ||||
} | } | ||||
//============================================================================== | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const StringArray& interfaceNames, | |||||
LocalRef<jobject> subclass) | |||||
{ | |||||
auto* env = getEnv(); | |||||
implementer->javaSubClass = GlobalRef (subclass); | |||||
// you need to override at least one interface | |||||
jassert (interfaceNames.size() > 0); | |||||
auto classArray = LocalRef<jobject> (env->NewObjectArray (interfaceNames.size(), JavaClass, nullptr)); | |||||
LocalRef<jobject> classLoader; | |||||
for (auto i = 0; i < interfaceNames.size(); ++i) | |||||
{ | |||||
auto aClass = LocalRef<jobject> (env->FindClass (interfaceNames[i].toRawUTF8())); | |||||
if (aClass != nullptr) | |||||
{ | |||||
if (i == 0) | |||||
classLoader = LocalRef<jobject> (env->CallObjectMethod (aClass, JavaClass.getClassLoader)); | |||||
env->SetObjectArrayElement ((jobjectArray) classArray.get(), i, aClass); | |||||
} | |||||
else | |||||
{ | |||||
// interface class not found | |||||
jassertfalse; | |||||
} | |||||
} | |||||
auto invocationHandler = LocalRef<jobject> (env->CallStaticObjectMethod (JuceAppActivity, | |||||
JuceAppActivity.createInvocationHandler, | |||||
reinterpret_cast<jlong> (implementer))); | |||||
return LocalRef<jobject> (env->CallStaticObjectMethod (JavaProxy, JavaProxy.newProxyInstance, | |||||
classLoader.get(), classArray.get(), | |||||
invocationHandler.get())); | |||||
} | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const StringArray& interfaceNames) | |||||
{ | |||||
return CreateJavaInterface (implementer, interfaceNames, | |||||
LocalRef<jobject> (getEnv()->NewObject (JavaObject, | |||||
JavaObject.constructor))); | |||||
} | |||||
LocalRef<jobject> CreateJavaInterface (AndroidInterfaceImplementer* implementer, | |||||
const String& interfaceName) | |||||
{ | |||||
return CreateJavaInterface (implementer, StringArray (interfaceName)); | |||||
} | |||||
jobject AndroidInterfaceImplementer::invoke (jobject /*proxy*/, jobject method, jobjectArray args) | |||||
{ | |||||
auto* env = getEnv(); | |||||
return env->CallObjectMethod (method, Method.invoke, javaSubClass.get(), args); | |||||
} | |||||
jobject juce_invokeImplementer (JNIEnv* env, jlong thisPtr, jobject proxy, jobject method, jobjectArray args) | |||||
{ | |||||
setEnv (env); | |||||
return reinterpret_cast<AndroidInterfaceImplementer*> (thisPtr)->invoke (proxy, method, args); | |||||
} | |||||
void juce_dispatchDelete (JNIEnv* env, jlong thisPtr) | |||||
{ | |||||
setEnv (env); | |||||
delete reinterpret_cast<AndroidInterfaceImplementer*> (thisPtr); | |||||
} | |||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchInvoke, | |||||
jobject, (JNIEnv* env, jobject /*object*/, jlong thisPtr, jobject proxy, jobject method, jobjectArray args)) | |||||
{ | |||||
return juce_invokeImplementer (env, thisPtr, proxy, method, args); | |||||
} | |||||
JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024NativeInvocationHandler), dispatchFinalize, | |||||
void, (JNIEnv* env, jobject /*object*/, jlong thisPtr)) | |||||
{ | |||||
juce_dispatchDelete (env, thisPtr); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
JavaVM* androidJNIJavaVM = nullptr; | JavaVM* androidJNIJavaVM = nullptr; | ||||
@@ -27,4 +27,9 @@ | |||||
#include <CoreFoundation/CFAvailability.h> | #include <CoreFoundation/CFAvailability.h> | ||||
#undef CF_OPTIONS | #undef CF_OPTIONS | ||||
#define CF_OPTIONS(_type, _name) _type _name; enum | #define CF_OPTIONS(_type, _name) _type _name; enum | ||||
// This is a workaround for the XCode 9 version of NSUUID.h causing some errors | |||||
// in the live-build engine. | |||||
#define _Nullable | |||||
#define _Nonnull | |||||
#endif | #endif |
@@ -401,7 +401,12 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||||
if (SystemStats::isRunningInAppExtensionSandbox()) | if (SystemStats::isRunningInAppExtensionSandbox()) | ||||
return false; | return false; | ||||
#if (! defined __IPHONE_OS_VERSION_MIN_REQUIRED) || (! defined __IPHONE_10_0) || (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0) | |||||
return [[UIApplication sharedApplication] openURL: filenameAsURL]; | return [[UIApplication sharedApplication] openURL: filenameAsURL]; | ||||
#else | |||||
[[UIApplication sharedApplication] openURL: filenameAsURL options: @{} completionHandler: nil]; | |||||
return true; | |||||
#endif | |||||
#else | #else | ||||
NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; | NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; | ||||
@@ -137,6 +137,8 @@ public: | |||||
[task release]; | [task release]; | ||||
[request release]; | [request release]; | ||||
[headers release]; | [headers release]; | ||||
[session finishTasksAndInvalidate]; | |||||
[session release]; | [session release]; | ||||
const ScopedLock sl (dataLock); | const ScopedLock sl (dataLock); | ||||
@@ -59,6 +59,16 @@ namespace | |||||
return createNSURLFromFile (f.getFullPathName()); | return createNSURLFromFile (f.getFullPathName()); | ||||
} | } | ||||
static inline NSArray* createNSArrayFromStringArray (const StringArray& strings) | |||||
{ | |||||
auto* array = [[NSMutableArray alloc] init]; | |||||
for (auto string: strings) | |||||
[array addObject:juceStringToNS (string)]; | |||||
return [array autorelease]; | |||||
} | |||||
#if JUCE_MAC | #if JUCE_MAC | ||||
template <typename RectangleType> | template <typename RectangleType> | ||||
static NSRect makeNSRect (const RectangleType& r) noexcept | static NSRect makeNSRect (const RectangleType& r) noexcept | ||||
@@ -393,7 +393,11 @@ void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int | |||||
{ | { | ||||
modificationTime = (int64) info.st_mtime * 1000; | modificationTime = (int64) info.st_mtime * 1000; | ||||
accessTime = (int64) info.st_atime * 1000; | accessTime = (int64) info.st_atime * 1000; | ||||
#if (JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5) || JUCE_IOS | |||||
creationTime = (int64) info.st_birthtime * 1000; | |||||
#else | |||||
creationTime = (int64) info.st_ctime * 1000; | creationTime = (int64) info.st_ctime * 1000; | ||||
#endif | |||||
} | } | ||||
} | } | ||||
@@ -554,23 +558,13 @@ ssize_t FileOutputStream::writeInternal (const void* const data, const size_t nu | |||||
return result; | return result; | ||||
} | } | ||||
#ifndef JUCE_ANDROID | |||||
void FileOutputStream::flushInternal() | void FileOutputStream::flushInternal() | ||||
{ | { | ||||
if (fileHandle != 0) | |||||
{ | |||||
if (fsync (getFD (fileHandle)) == -1) | |||||
status = getResultForErrno(); | |||||
#if JUCE_ANDROID | |||||
// This stuff tells the OS to asynchronously update the metadata | |||||
// that the OS has cached aboud the file - this metadata is used | |||||
// when the device is acting as a USB drive, and unless it's explicitly | |||||
// refreshed, it'll get out of step with the real file. | |||||
const LocalRef<jstring> t (javaString (file.getFullPathName())); | |||||
android.activity.callVoidMethod (JuceAppActivity.scanFile, t.get()); | |||||
#endif | |||||
} | |||||
if (fileHandle != 0 && fsync (getFD (fileHandle)) == -1) | |||||
status = getResultForErrno(); | |||||
} | } | ||||
#endif | |||||
Result FileOutputStream::truncate() | Result FileOutputStream::truncate() | ||||
{ | { | ||||
@@ -227,7 +227,19 @@ String SystemStats::getOperatingSystemName() | |||||
String SystemStats::getDeviceDescription() | String SystemStats::getDeviceDescription() | ||||
{ | { | ||||
return {}; | |||||
#if WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP | |||||
return "Windows (Desktop)"; | |||||
#elif WINAPI_FAMILY == WINAPI_FAMILY_PC_APP | |||||
return "Windows (Store)"; | |||||
#elif WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP | |||||
return "Windows (Phone)"; | |||||
#elif WINAPI_FAMILY == WINAPI_FAMILY_SYSTEM | |||||
return "Windows (System)"; | |||||
#elif WINAPI_FAMILY == WINAPI_FAMILY_SERVER | |||||
return "Windows (Server)"; | |||||
#else | |||||
return "Windows"; | |||||
#endif | |||||
} | } | ||||
bool SystemStats::isOperatingSystem64Bit() | bool SystemStats::isOperatingSystem64Bit() | ||||
@@ -47,6 +47,10 @@ | |||||
#define JUCE_COMPILER_SUPPORTS_THREAD_LOCAL 1 | #define JUCE_COMPILER_SUPPORTS_THREAD_LOCAL 1 | ||||
#endif | #endif | ||||
#if __cpp_constexpr >= 201304 | |||||
#define JUCE_HAS_CONSTEXPR 1 | |||||
#endif | |||||
#ifndef JUCE_EXCEPTIONS_DISABLED | #ifndef JUCE_EXCEPTIONS_DISABLED | ||||
#if ! __EXCEPTIONS | #if ! __EXCEPTIONS | ||||
#define JUCE_EXCEPTIONS_DISABLED 1 | #define JUCE_EXCEPTIONS_DISABLED 1 | ||||
@@ -86,6 +90,10 @@ | |||||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | ||||
#endif | #endif | ||||
#if __has_feature(cxx_relaxed_constexpr) | |||||
#define JUCE_HAS_CONSTEXPR 1 | |||||
#endif | |||||
#ifndef JUCE_COMPILER_SUPPORTS_ARC | #ifndef JUCE_COMPILER_SUPPORTS_ARC | ||||
#define JUCE_COMPILER_SUPPORTS_ARC 1 | #define JUCE_COMPILER_SUPPORTS_ARC 1 | ||||
#endif | #endif | ||||
@@ -118,6 +126,10 @@ | |||||
#define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 | #define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 | ||||
#endif | #endif | ||||
#if _MSC_VER >= 1910 | |||||
#define JUCE_HAS_CONSTEXPR 1 | |||||
#endif | |||||
#ifndef JUCE_EXCEPTIONS_DISABLED | #ifndef JUCE_EXCEPTIONS_DISABLED | ||||
#if ! _CPPUNWIND | #if ! _CPPUNWIND | ||||
#define JUCE_EXCEPTIONS_DISABLED 1 | #define JUCE_EXCEPTIONS_DISABLED 1 | ||||
@@ -137,6 +149,12 @@ | |||||
#define JUCE_DELETED_FUNCTION | #define JUCE_DELETED_FUNCTION | ||||
#endif | #endif | ||||
#if JUCE_HAS_CONSTEXPR | |||||
#define JUCE_CONSTEXPR constexpr | |||||
#else | |||||
#define JUCE_CONSTEXPR | |||||
#endif | |||||
#if ! DOXYGEN | #if ! DOXYGEN | ||||
#if ! JUCE_COMPILER_SUPPORTS_NOEXCEPT | #if ! JUCE_COMPILER_SUPPORTS_NOEXCEPT | ||||
#ifdef noexcept | #ifdef noexcept | ||||
@@ -563,7 +563,7 @@ struct HashGenerator | |||||
template <typename CharPointer> | template <typename CharPointer> | ||||
static Type calculate (CharPointer t) noexcept | static Type calculate (CharPointer t) noexcept | ||||
{ | { | ||||
Type result = Type(); | |||||
Type result = {}; | |||||
while (! t.isEmpty()) | while (! t.isEmpty()) | ||||
result = ((Type) multiplier) * result + (Type) t.getAndAdvance(); | result = ((Type) multiplier) * result + (Type) t.getAndAdvance(); | ||||
@@ -574,9 +574,9 @@ struct HashGenerator | |||||
enum { multiplier = sizeof (Type) > 4 ? 101 : 31 }; | enum { multiplier = sizeof (Type) > 4 ? 101 : 31 }; | ||||
}; | }; | ||||
int String::hashCode() const noexcept { return HashGenerator<int> ::calculate (text); } | |||||
int64 String::hashCode64() const noexcept { return HashGenerator<int64> ::calculate (text); } | |||||
size_t String::hash() const noexcept { return HashGenerator<size_t> ::calculate (text); } | |||||
int String::hashCode() const noexcept { return (int) HashGenerator<uint32> ::calculate (text); } | |||||
int64 String::hashCode64() const noexcept { return (int64) HashGenerator<uint64> ::calculate (text); } | |||||
size_t String::hash() const noexcept { return HashGenerator<size_t> ::calculate (text); } | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } | JUCE_API bool JUCE_CALLTYPE operator== (const String& s1, const String& s2) noexcept { return s1.compare (s2) == 0; } | ||||
@@ -1884,7 +1884,7 @@ String String::formattedRaw (const char* pf, ...) | |||||
va_end (args); | va_end (args); | ||||
if (num > 0) | if (num > 0) | ||||
return String (temp); | |||||
return String (temp.get()); | |||||
bufferSize += 256; | bufferSize += 256; | ||||
@@ -987,6 +987,10 @@ public: | |||||
*/ | */ | ||||
String (double doubleValue, int numberOfDecimalPlaces); | String (double doubleValue, int numberOfDecimalPlaces); | ||||
// Automatically creating a String from a bool opens up lots of nasty type conversion edge cases. | |||||
// If you want a String representation of a bool you can cast the bool to an int first. | |||||
explicit String (bool) = delete; | |||||
/** Reads the value of the string as a decimal number (up to 32 bits in size). | /** Reads the value of the string as a decimal number (up to 32 bits in size). | ||||
@returns the value of the string as a 32 bit signed base-10 integer. | @returns the value of the string as a 32 bit signed base-10 integer. | ||||
@@ -1323,23 +1327,27 @@ JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, const String& string | |||||
/** Appends a string to the end of the first one. */ | /** Appends a string to the end of the first one. */ | ||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, StringRef string2); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, StringRef string2); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, short number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, long number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, long number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, unsigned long number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, unsigned long number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int64 number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, int64 number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, uint64 number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, uint64 number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, float number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, float number); | ||||
/** Appends a decimal number at the end of a string. */ | |||||
/** Appends a decimal number to the end of a string. */ | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, double number); | JUCE_API String& JUCE_CALLTYPE operator<< (String& string1, double number); | ||||
// Automatically creating a String from a bool opens up lots of nasty type conversion edge cases. | |||||
// If you want a String representation of a bool you can cast the bool to an int first. | |||||
JUCE_API String& JUCE_CALLTYPE operator<< (String&, bool) = delete; | |||||
//============================================================================== | //============================================================================== | ||||
/** Case-sensitive comparison of two strings. */ | /** Case-sensitive comparison of two strings. */ | ||||
JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) noexcept; | JUCE_API bool JUCE_CALLTYPE operator== (const String& string1, const String& string2) noexcept; | ||||
@@ -20,13 +20,16 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
Thread::Thread (const String& name, const size_t stackSize) | |||||
Thread::Thread (const String& name, size_t stackSize) | |||||
: threadName (name), threadStackSize (stackSize) | : threadName (name), threadStackSize (stackSize) | ||||
{ | { | ||||
} | } | ||||
Thread::~Thread() | Thread::~Thread() | ||||
{ | { | ||||
if (deleteOnThreadEnd) | |||||
return; | |||||
/* If your thread class's destructor has been called without first stopping the thread, that | /* If your thread class's destructor has been called without first stopping the thread, that | ||||
means that this partially destructed object is still performing some work - and that's | means that this partially destructed object is still performing some work - and that's | ||||
probably a Bad Thing! | probably a Bad Thing! | ||||
@@ -97,6 +100,9 @@ void Thread::threadEntryPoint() | |||||
currentThreadHolder->value.releaseCurrentThreadStorage(); | currentThreadHolder->value.releaseCurrentThreadStorage(); | ||||
closeThreadHandle(); | closeThreadHandle(); | ||||
if (deleteOnThreadEnd) | |||||
delete this; | |||||
} | } | ||||
// used to wrap the incoming call from the platform-specific code | // used to wrap the incoming call from the platform-specific code | ||||
@@ -162,7 +168,7 @@ void Thread::signalThreadShouldExit() | |||||
bool Thread::currentThreadShouldExit() | bool Thread::currentThreadShouldExit() | ||||
{ | { | ||||
if (Thread* currentThread = getCurrentThread()) | |||||
if (auto* currentThread = getCurrentThread()) | |||||
return currentThread->threadShouldExit(); | return currentThread->threadShouldExit(); | ||||
return false; | return false; | ||||
@@ -273,6 +279,29 @@ void Thread::notify() const | |||||
defaultEvent.signal(); | defaultEvent.signal(); | ||||
} | } | ||||
//============================================================================== | |||||
struct LambdaThread : public Thread | |||||
{ | |||||
LambdaThread (std::function<void()> f) : Thread ("anonymous"), fn (f) {} | |||||
void run() override | |||||
{ | |||||
fn(); | |||||
fn = {}; // free any objects that the lambda might contain while the thread is still active | |||||
} | |||||
std::function<void()> fn; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LambdaThread) | |||||
}; | |||||
void Thread::launch (std::function<void()> functionToRun) | |||||
{ | |||||
auto anon = new LambdaThread (functionToRun); | |||||
anon->deleteOnThreadEnd = true; | |||||
anon->startThread(); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void SpinLock::enter() const noexcept | void SpinLock::enter() const noexcept | ||||
{ | { | ||||
@@ -76,8 +76,6 @@ public: | |||||
virtual void run() = 0; | virtual void run() = 0; | ||||
//============================================================================== | //============================================================================== | ||||
// Thread control functions.. | |||||
/** Starts the thread running. | /** Starts the thread running. | ||||
This will cause the thread's run() method to be called by a new thread. | This will cause the thread's run() method to be called by a new thread. | ||||
@@ -118,6 +116,18 @@ public: | |||||
*/ | */ | ||||
bool stopThread (int timeOutMilliseconds); | bool stopThread (int timeOutMilliseconds); | ||||
//============================================================================== | |||||
/** Invokes a lambda or function on its own thread. | |||||
This will spin up a Thread object which calls the function and then exits. | |||||
Bear in mind that starting and stopping a thread can be a fairly heavyweight | |||||
operation, so you might prefer to use a ThreadPool if you're kicking off a lot | |||||
of short background tasks. | |||||
Also note that using an anonymous thread makes it very difficult to interrupt | |||||
the function when you need to stop it, e.g. when your app quits. So it's up to | |||||
you to deal with situations where the function may fail to stop in time. | |||||
*/ | |||||
static void launch (std::function<void()> functionToRun); | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns true if the thread is currently active */ | /** Returns true if the thread is currently active */ | ||||
bool isThreadRunning() const; | bool isThreadRunning() const; | ||||
@@ -154,8 +164,7 @@ public: | |||||
static bool currentThreadShouldExit(); | static bool currentThreadShouldExit(); | ||||
/** Waits for the thread to stop. | /** Waits for the thread to stop. | ||||
This will waits until isThreadRunning() is false or until a timeout expires. | |||||
This will wait until isThreadRunning() is false or until a timeout expires. | |||||
@param timeOutMilliseconds the time to wait, in milliseconds. If this value | @param timeOutMilliseconds the time to wait, in milliseconds. If this value | ||||
is less than zero, it will wait forever. | is less than zero, it will wait forever. | ||||
@@ -274,21 +283,17 @@ public: | |||||
static Thread* JUCE_CALLTYPE getCurrentThread(); | static Thread* JUCE_CALLTYPE getCurrentThread(); | ||||
/** Returns the ID of this thread. | /** Returns the ID of this thread. | ||||
That means the ID of this thread object - not of the thread that's calling the method. | That means the ID of this thread object - not of the thread that's calling the method. | ||||
This can change when the thread is started and stopped, and will be invalid if the | This can change when the thread is started and stopped, and will be invalid if the | ||||
thread's not actually running. | thread's not actually running. | ||||
@see getCurrentThreadId | @see getCurrentThreadId | ||||
*/ | */ | ||||
ThreadID getThreadId() const noexcept { return threadId; } | ThreadID getThreadId() const noexcept { return threadId; } | ||||
/** Returns the name of the thread. | /** Returns the name of the thread. | ||||
This is the name that gets set in the constructor. | This is the name that gets set in the constructor. | ||||
*/ | */ | ||||
const String& getThreadName() const { return threadName; } | |||||
const String& getThreadName() const noexcept { return threadName; } | |||||
/** Changes the name of the caller thread. | /** Changes the name of the caller thread. | ||||
Different OSes may place different length or content limits on this name. | Different OSes may place different length or content limits on this name. | ||||
@@ -306,6 +311,7 @@ private: | |||||
int threadPriority = 5; | int threadPriority = 5; | ||||
size_t threadStackSize; | size_t threadStackSize; | ||||
uint32 affinityMask = 0; | uint32 affinityMask = 0; | ||||
bool deleteOnThreadEnd = false; | |||||
bool volatile shouldExit = false; | bool volatile shouldExit = false; | ||||
#if JUCE_ANDROID | #if JUCE_ANDROID | ||||
@@ -23,8 +23,8 @@ | |||||
class ThreadPool::ThreadPoolThread : public Thread | class ThreadPool::ThreadPoolThread : public Thread | ||||
{ | { | ||||
public: | public: | ||||
ThreadPoolThread (ThreadPool& p, size_t stackSize = 0) | |||||
: Thread ("Pool", stackSize), currentJob (nullptr), pool (p) | |||||
ThreadPoolThread (ThreadPool& p, size_t stackSize) | |||||
: Thread ("Pool", stackSize), pool (p) | |||||
{ | { | ||||
} | } | ||||
@@ -35,16 +35,14 @@ public: | |||||
wait (500); | wait (500); | ||||
} | } | ||||
ThreadPoolJob* volatile currentJob; | |||||
ThreadPoolJob* volatile currentJob = nullptr; | |||||
ThreadPool& pool; | ThreadPool& pool; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolThread) | ||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
ThreadPoolJob::ThreadPoolJob (const String& name) | |||||
: jobName (name), pool (nullptr), | |||||
shouldStop (false), isActive (false), shouldBeDeleted (false) | |||||
ThreadPoolJob::ThreadPoolJob (const String& name) : jobName (name) | |||||
{ | { | ||||
} | } | ||||
@@ -72,7 +70,7 @@ void ThreadPoolJob::signalJobShouldExit() | |||||
ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() | ThreadPoolJob* ThreadPoolJob::getCurrentThreadPoolJob() | ||||
{ | { | ||||
if (ThreadPool::ThreadPoolThread* t = dynamic_cast<ThreadPool::ThreadPoolThread*> (Thread::getCurrentThread())) | |||||
if (auto* t = dynamic_cast<ThreadPool::ThreadPoolThread*> (Thread::getCurrentThread())) | |||||
return t->currentJob; | return t->currentJob; | ||||
return nullptr; | return nullptr; | ||||
@@ -102,17 +100,17 @@ void ThreadPool::createThreads (int numThreads, size_t threadStackSize) | |||||
for (int i = jmax (1, numThreads); --i >= 0;) | for (int i = jmax (1, numThreads); --i >= 0;) | ||||
threads.add (new ThreadPoolThread (*this, threadStackSize)); | threads.add (new ThreadPoolThread (*this, threadStackSize)); | ||||
for (int i = threads.size(); --i >= 0;) | |||||
threads.getUnchecked(i)->startThread(); | |||||
for (auto* t : threads) | |||||
t->startThread(); | |||||
} | } | ||||
void ThreadPool::stopThreads() | void ThreadPool::stopThreads() | ||||
{ | { | ||||
for (int i = threads.size(); --i >= 0;) | |||||
threads.getUnchecked(i)->signalThreadShouldExit(); | |||||
for (auto* t : threads) | |||||
t->signalThreadShouldExit(); | |||||
for (int i = threads.size(); --i >= 0;) | |||||
threads.getUnchecked(i)->stopThread (500); | |||||
for (auto* t : threads) | |||||
t->stopThread (500); | |||||
} | } | ||||
void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinished) | void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinished) | ||||
@@ -132,44 +130,83 @@ void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinis | |||||
jobs.add (job); | jobs.add (job); | ||||
} | } | ||||
for (int i = threads.size(); --i >= 0;) | |||||
threads.getUnchecked(i)->notify(); | |||||
for (auto* t : threads) | |||||
t->notify(); | |||||
} | } | ||||
} | } | ||||
int ThreadPool::getNumJobs() const | |||||
void ThreadPool::addJob (std::function<ThreadPoolJob::JobStatus()> jobToRun) | |||||
{ | |||||
struct LambdaJobWrapper : public ThreadPoolJob | |||||
{ | |||||
LambdaJobWrapper (std::function<ThreadPoolJob::JobStatus()> j) : ThreadPoolJob ("lambda"), job (j) {} | |||||
JobStatus runJob() override { return job(); } | |||||
std::function<ThreadPoolJob::JobStatus()> job; | |||||
}; | |||||
addJob (new LambdaJobWrapper (jobToRun), true); | |||||
} | |||||
void ThreadPool::addJob (std::function<void()> jobToRun) | |||||
{ | |||||
struct LambdaJobWrapper : public ThreadPoolJob | |||||
{ | |||||
LambdaJobWrapper (std::function<void()> j) : ThreadPoolJob ("lambda"), job (j) {} | |||||
JobStatus runJob() override { job(); return ThreadPoolJob::jobHasFinished; } | |||||
std::function<void()> job; | |||||
}; | |||||
addJob (new LambdaJobWrapper (jobToRun), true); | |||||
} | |||||
int ThreadPool::getNumJobs() const noexcept | |||||
{ | { | ||||
return jobs.size(); | return jobs.size(); | ||||
} | } | ||||
int ThreadPool::getNumThreads() const | |||||
int ThreadPool::getNumThreads() const noexcept | |||||
{ | { | ||||
return threads.size(); | return threads.size(); | ||||
} | } | ||||
ThreadPoolJob* ThreadPool::getJob (const int index) const | |||||
ThreadPoolJob* ThreadPool::getJob (int index) const noexcept | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
return jobs [index]; | return jobs [index]; | ||||
} | } | ||||
bool ThreadPool::contains (const ThreadPoolJob* const job) const | |||||
bool ThreadPool::contains (const ThreadPoolJob* const job) const noexcept | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
return jobs.contains (const_cast<ThreadPoolJob*> (job)); | return jobs.contains (const_cast<ThreadPoolJob*> (job)); | ||||
} | } | ||||
bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const | |||||
bool ThreadPool::isJobRunning (const ThreadPoolJob* const job) const noexcept | |||||
{ | { | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
return jobs.contains (const_cast<ThreadPoolJob*> (job)) && job->isActive; | return jobs.contains (const_cast<ThreadPoolJob*> (job)) && job->isActive; | ||||
} | } | ||||
void ThreadPool::moveJobToFront (const ThreadPoolJob* job) noexcept | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
if (! ! job->isActive) | |||||
{ | |||||
auto index = jobs.indexOf (const_cast<ThreadPoolJob*> (job)); | |||||
if (index > 0) | |||||
jobs.move (index, 0); | |||||
} | |||||
} | |||||
bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const | bool ThreadPool::waitForJobToFinish (const ThreadPoolJob* const job, const int timeOutMs) const | ||||
{ | { | ||||
if (job != nullptr) | if (job != nullptr) | ||||
{ | { | ||||
const uint32 start = Time::getMillisecondCounter(); | |||||
auto start = Time::getMillisecondCounter(); | |||||
while (contains (job)) | while (contains (job)) | ||||
{ | { | ||||
@@ -217,7 +254,7 @@ bool ThreadPool::removeJob (ThreadPoolJob* const job, | |||||
bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | ||||
ThreadPool::JobSelector* const selectedJobsToRemove) | ThreadPool::JobSelector* const selectedJobsToRemove) | ||||
{ | { | ||||
Array <ThreadPoolJob*> jobsToWaitFor; | |||||
Array<ThreadPoolJob*> jobsToWaitFor; | |||||
{ | { | ||||
OwnedArray<ThreadPoolJob> deletionList; | OwnedArray<ThreadPoolJob> deletionList; | ||||
@@ -227,7 +264,7 @@ bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeO | |||||
for (int i = jobs.size(); --i >= 0;) | for (int i = jobs.size(); --i >= 0;) | ||||
{ | { | ||||
ThreadPoolJob* const job = jobs.getUnchecked(i); | |||||
auto* job = jobs.getUnchecked(i); | |||||
if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) | if (selectedJobsToRemove == nullptr || selectedJobsToRemove->isJobSuitable (job)) | ||||
{ | { | ||||
@@ -248,13 +285,13 @@ bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeO | |||||
} | } | ||||
} | } | ||||
const uint32 start = Time::getMillisecondCounter(); | |||||
auto start = Time::getMillisecondCounter(); | |||||
for (;;) | for (;;) | ||||
{ | { | ||||
for (int i = jobsToWaitFor.size(); --i >= 0;) | for (int i = jobsToWaitFor.size(); --i >= 0;) | ||||
{ | { | ||||
ThreadPoolJob* const job = jobsToWaitFor.getUnchecked (i); | |||||
auto* job = jobsToWaitFor.getUnchecked (i); | |||||
if (! isJobRunning (job)) | if (! isJobRunning (job)) | ||||
jobsToWaitFor.remove (i); | jobsToWaitFor.remove (i); | ||||
@@ -277,12 +314,9 @@ StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) cons | |||||
StringArray s; | StringArray s; | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
for (int i = 0; i < jobs.size(); ++i) | |||||
{ | |||||
const ThreadPoolJob* const job = jobs.getUnchecked(i); | |||||
for (auto* job : jobs) | |||||
if (job->isActive || ! onlyReturnActiveJobs) | if (job->isActive || ! onlyReturnActiveJobs) | ||||
s.add (job->getJobName()); | s.add (job->getJobName()); | ||||
} | |||||
return s; | return s; | ||||
} | } | ||||
@@ -291,8 +325,8 @@ bool ThreadPool::setThreadPriorities (const int newPriority) | |||||
{ | { | ||||
bool ok = true; | bool ok = true; | ||||
for (int i = threads.size(); --i >= 0;) | |||||
if (! threads.getUnchecked(i)->setPriority (newPriority)) | |||||
for (auto* t : threads) | |||||
if (! t->setPriority (newPriority)) | |||||
ok = false; | ok = false; | ||||
return ok; | return ok; | ||||
@@ -307,20 +341,21 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||||
for (int i = 0; i < jobs.size(); ++i) | for (int i = 0; i < jobs.size(); ++i) | ||||
{ | { | ||||
ThreadPoolJob* job = jobs[i]; | |||||
if (job != nullptr && ! job->isActive) | |||||
if (auto* job = jobs[i]) | |||||
{ | { | ||||
if (job->shouldStop) | |||||
if (! job->isActive) | |||||
{ | { | ||||
jobs.remove (i); | |||||
addToDeleteList (deletionList, job); | |||||
--i; | |||||
continue; | |||||
} | |||||
if (job->shouldStop) | |||||
{ | |||||
jobs.remove (i); | |||||
addToDeleteList (deletionList, job); | |||||
--i; | |||||
continue; | |||||
} | |||||
job->isActive = true; | |||||
return job; | |||||
job->isActive = true; | |||||
return job; | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -330,9 +365,9 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||||
bool ThreadPool::runNextJob (ThreadPoolThread& thread) | bool ThreadPool::runNextJob (ThreadPoolThread& thread) | ||||
{ | { | ||||
if (ThreadPoolJob* const job = pickNextJobToRun()) | |||||
if (auto* job = pickNextJobToRun()) | |||||
{ | { | ||||
ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; | |||||
auto result = ThreadPoolJob::jobHasFinished; | |||||
thread.currentJob = job; | thread.currentJob = job; | ||||
try | try | ||||
@@ -123,8 +123,8 @@ private: | |||||
friend class ThreadPool; | friend class ThreadPool; | ||||
friend class ThreadPoolThread; | friend class ThreadPoolThread; | ||||
String jobName; | String jobName; | ||||
ThreadPool* pool; | |||||
bool shouldStop, isActive, shouldBeDeleted; | |||||
ThreadPool* pool = nullptr; | |||||
bool shouldStop = false, isActive = false, shouldBeDeleted = false; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolJob) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadPoolJob) | ||||
}; | }; | ||||
@@ -207,6 +207,16 @@ public: | |||||
void addJob (ThreadPoolJob* job, | void addJob (ThreadPoolJob* job, | ||||
bool deleteJobWhenFinished); | bool deleteJobWhenFinished); | ||||
/** Adds a lambda function to be called as a job. | |||||
This will create an internal ThreadPoolJob object to encapsulate and call the lambda. | |||||
*/ | |||||
void addJob (std::function<ThreadPoolJob::JobStatus()> job); | |||||
/** Adds a lambda function to be called as a job. | |||||
This will create an internal ThreadPoolJob object to encapsulate and call the lambda. | |||||
*/ | |||||
void addJob (std::function<void()> job); | |||||
/** Tries to remove a job from the pool. | /** Tries to remove a job from the pool. | ||||
If the job isn't yet running, this will simply remove it. If it is running, it | If the job isn't yet running, this will simply remove it. If it is running, it | ||||
@@ -244,27 +254,26 @@ public: | |||||
JobSelector* selectedJobsToRemove = nullptr); | JobSelector* selectedJobsToRemove = nullptr); | ||||
/** Returns the number of jobs currently running or queued. */ | /** Returns the number of jobs currently running or queued. */ | ||||
int getNumJobs() const; | |||||
int getNumJobs() const noexcept; | |||||
/** Returns the number of threads assigned to this thread pool. */ | /** Returns the number of threads assigned to this thread pool. */ | ||||
int getNumThreads() const; | |||||
int getNumThreads() const noexcept; | |||||
/** Returns one of the jobs in the queue. | /** Returns one of the jobs in the queue. | ||||
Note that this can be a very volatile list as jobs might be continuously getting shifted | Note that this can be a very volatile list as jobs might be continuously getting shifted | ||||
around in the list, and this method may return nullptr if the index is currently out-of-range. | around in the list, and this method may return nullptr if the index is currently out-of-range. | ||||
*/ | */ | ||||
ThreadPoolJob* getJob (int index) const; | |||||
ThreadPoolJob* getJob (int index) const noexcept; | |||||
/** Returns true if the given job is currently queued or running. | /** Returns true if the given job is currently queued or running. | ||||
@see isJobRunning() | @see isJobRunning() | ||||
*/ | */ | ||||
bool contains (const ThreadPoolJob* job) const; | |||||
bool contains (const ThreadPoolJob* job) const noexcept; | |||||
/** Returns true if the given job is currently being run by a thread. | |||||
*/ | |||||
bool isJobRunning (const ThreadPoolJob* job) const; | |||||
/** Returns true if the given job is currently being run by a thread. */ | |||||
bool isJobRunning (const ThreadPoolJob* job) const noexcept; | |||||
/** Waits until a job has finished running and has been removed from the pool. | /** Waits until a job has finished running and has been removed from the pool. | ||||
@@ -277,13 +286,17 @@ public: | |||||
bool waitForJobToFinish (const ThreadPoolJob* job, | bool waitForJobToFinish (const ThreadPoolJob* job, | ||||
int timeOutMilliseconds) const; | int timeOutMilliseconds) const; | ||||
/** If the given job is in the queue, this will move it to the front so that it | |||||
is the next one to be executed. | |||||
*/ | |||||
void moveJobToFront (const ThreadPoolJob* jobToMove) noexcept; | |||||
/** Returns a list of the names of all the jobs currently running or queued. | /** Returns a list of the names of all the jobs currently running or queued. | ||||
If onlyReturnActiveJobs is true, only the ones currently running are returned. | If onlyReturnActiveJobs is true, only the ones currently running are returned. | ||||
*/ | */ | ||||
StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; | StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; | ||||
/** Changes the priority of all the threads. | /** Changes the priority of all the threads. | ||||
This will call Thread::setPriority() for each thread in the pool. | This will call Thread::setPriority() for each thread in the pool. | ||||
May return false if for some reason the priority can't be changed. | May return false if for some reason the priority can't be changed. | ||||
*/ | */ | ||||
@@ -292,7 +305,7 @@ public: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
Array <ThreadPoolJob*> jobs; | |||||
Array<ThreadPoolJob*> jobs; | |||||
class ThreadPoolThread; | class ThreadPoolThread; | ||||
friend class ThreadPoolJob; | friend class ThreadPoolJob; | ||||
@@ -139,7 +139,7 @@ private: | |||||
class JUCE_API ScopedTimeMeasurement | class JUCE_API ScopedTimeMeasurement | ||||
{ | { | ||||
public: | public: | ||||
ScopedTimeMeasurement (double& resultInSeconds) | |||||
ScopedTimeMeasurement (double& resultInSeconds) noexcept | |||||
: result (resultInSeconds) | : result (resultInSeconds) | ||||
{ | { | ||||
result = 0.0; | result = 0.0; | ||||
@@ -154,4 +154,6 @@ public: | |||||
private: | private: | ||||
int64 startTimeTicks = Time::getHighResolutionTicks(); | int64 startTimeTicks = Time::getHighResolutionTicks(); | ||||
double& result; | double& result; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ScopedTimeMeasurement) | |||||
}; | }; |
@@ -48,14 +48,17 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
/** Causes the callback to be triggered at a later time. | /** Causes the callback to be triggered at a later time. | ||||
This method returns immediately, having made sure that a callback | |||||
to the handleAsyncUpdate() method will occur as soon as possible. | |||||
This method returns immediately, after which a callback to the | |||||
handleAsyncUpdate() method will be made by the message thread as | |||||
soon as possible. | |||||
If an update callback is already pending but hasn't happened yet, calls | |||||
to this method will be ignored. | |||||
If an update callback is already pending but hasn't happened yet, calling | |||||
this method will have no effect. | |||||
It's thread-safe to call this method from any number of threads without | |||||
needing to worry about locking. | |||||
It's thread-safe to call this method from any thread, BUT beware of calling | |||||
it from a real-time (e.g. audio) thread, because it involves posting a message | |||||
to the system queue, which means it may block (and in general will do on | |||||
most OSes). | |||||
*/ | */ | ||||
void triggerAsyncUpdate(); | void triggerAsyncUpdate(); | ||||
@@ -20,8 +20,61 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
void MessageManager::doPlatformSpecificInitialisation() {} | |||||
void MessageManager::doPlatformSpecificShutdown() {} | |||||
//============================================================================== | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (constructor, "<init>", "()V") \ | |||||
METHOD (post, "post", "(Ljava/lang/Runnable;)Z") \ | |||||
DECLARE_JNI_CLASS (JNIHandler, "android/os/Handler"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
//============================================================================== | |||||
namespace Android | |||||
{ | |||||
class Runnable : public juce::AndroidInterfaceImplementer | |||||
{ | |||||
public: | |||||
virtual void run() = 0; | |||||
private: | |||||
jobject invoke (jobject proxy, jobject method, jobjectArray args) override | |||||
{ | |||||
auto* env = getEnv(); | |||||
auto methodName = juce::juceString ((jstring) env->CallObjectMethod (method, Method.getName)); | |||||
if (methodName == "run") | |||||
{ | |||||
run(); | |||||
return nullptr; | |||||
} | |||||
// invoke base class | |||||
return AndroidInterfaceImplementer::invoke (proxy, method, args); | |||||
} | |||||
}; | |||||
struct Handler | |||||
{ | |||||
juce_DeclareSingleton (Handler, false) | |||||
Handler() : nativeHandler (getEnv()->NewObject (JNIHandler, JNIHandler.constructor)) {} | |||||
bool post (Runnable* runnable) | |||||
{ | |||||
return (getEnv()->CallBooleanMethod (nativeHandler.get(), JNIHandler.post, | |||||
CreateJavaInterface (runnable, "java/lang/Runnable").get()) != 0); | |||||
} | |||||
GlobalRef nativeHandler; | |||||
}; | |||||
juce_ImplementSingleton (Handler); | |||||
} | |||||
//============================================================================== | |||||
void MessageManager::doPlatformSpecificInitialisation() { Android::Handler::getInstance(); } | |||||
void MessageManager::doPlatformSpecificShutdown() {} | |||||
//============================================================================== | //============================================================================== | ||||
bool MessageManager::dispatchNextMessageOnSystemQueue (const bool) | bool MessageManager::dispatchNextMessageOnSystemQueue (const bool) | ||||
@@ -33,26 +86,37 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool) | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
struct AndroidMessageCallback : public Android::Runnable | |||||
{ | { | ||||
message->incReferenceCount(); | |||||
android.activity.callVoidMethod (JuceAppActivity.postMessage, (jlong) (pointer_sized_uint) message); | |||||
return true; | |||||
} | |||||
AndroidMessageCallback (const MessageManager::MessageBase::Ptr& messageToDeliver) | |||||
: message (messageToDeliver) | |||||
{} | |||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, deliverMessage, void, (JNIEnv* env, jobject, jlong value)) | |||||
{ | |||||
setEnv (env); | |||||
AndroidMessageCallback (MessageManager::MessageBase::Ptr && messageToDeliver) | |||||
: message (static_cast<MessageManager::MessageBase::Ptr&&> (messageToDeliver)) | |||||
{} | |||||
JUCE_TRY | |||||
void run() override | |||||
{ | { | ||||
MessageManager::MessageBase* const message = (MessageManager::MessageBase*) (pointer_sized_uint) value; | |||||
message->messageCallback(); | |||||
message->decReferenceCount(); | |||||
JUCE_TRY | |||||
{ | |||||
message->messageCallback(); | |||||
// delete the message already here as Java will only run the | |||||
// destructor of this runnable the next time the garbage | |||||
// collector kicks in. | |||||
message = nullptr; | |||||
} | |||||
JUCE_CATCH_EXCEPTION | |||||
} | } | ||||
JUCE_CATCH_EXCEPTION | |||||
} | |||||
MessageManager::MessageBase::Ptr message; | |||||
}; | |||||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||||
{ | |||||
return Android::Handler::getInstance()->post (new AndroidMessageCallback (message)); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void MessageManager::broadcastMessage (const String&) | void MessageManager::broadcastMessage (const String&) | ||||
{ | { | ||||
@@ -38,7 +38,7 @@ namespace | |||||
&& (int) h >= 0 && (int) h <= maxVal); | && (int) h >= 0 && (int) h <= maxVal); | ||||
#endif | #endif | ||||
return Rectangle<Type> (x, y, w, h); | |||||
return { x, y, w, h }; | |||||
} | } | ||||
} | } | ||||
@@ -49,15 +49,13 @@ LowLevelGraphicsContext::~LowLevelGraphicsContext() {} | |||||
//============================================================================== | //============================================================================== | ||||
Graphics::Graphics (const Image& imageToDrawOnto) | Graphics::Graphics (const Image& imageToDrawOnto) | ||||
: context (*imageToDrawOnto.createLowLevelContext()), | : context (*imageToDrawOnto.createLowLevelContext()), | ||||
contextToDelete (&context), | |||||
saveStatePending (false) | |||||
contextToDelete (&context) | |||||
{ | { | ||||
jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | ||||
} | } | ||||
Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept | Graphics::Graphics (LowLevelGraphicsContext& internalContext) noexcept | ||||
: context (internalContext), | |||||
saveStatePending (false) | |||||
: context (internalContext) | |||||
{ | { | ||||
} | } | ||||
@@ -85,7 +83,7 @@ bool Graphics::reduceClipRegion (Rectangle<int> area) | |||||
return context.clipToRectangle (area); | return context.clipToRectangle (area); | ||||
} | } | ||||
bool Graphics::reduceClipRegion (const int x, const int y, const int w, const int h) | |||||
bool Graphics::reduceClipRegion (int x, int y, int w, int h) | |||||
{ | { | ||||
return reduceClipRegion (coordsToRectangle (x, y, w, h)); | return reduceClipRegion (coordsToRectangle (x, y, w, h)); | ||||
} | } | ||||
@@ -157,7 +155,7 @@ void Graphics::setOrigin (Point<int> newOrigin) | |||||
void Graphics::setOrigin (int x, int y) | void Graphics::setOrigin (int x, int y) | ||||
{ | { | ||||
setOrigin (Point<int> (x, y)); | |||||
setOrigin ({ x, y }); | |||||
} | } | ||||
void Graphics::addTransform (const AffineTransform& transform) | void Graphics::addTransform (const AffineTransform& transform) | ||||
@@ -239,23 +237,20 @@ void Graphics::drawSingleLineText (const String& text, const int startX, const i | |||||
// Don't pass any vertical placement flags to this method - they'll be ignored. | // Don't pass any vertical placement flags to this method - they'll be ignored. | ||||
jassert (justification.getOnlyVerticalFlags() == 0); | jassert (justification.getOnlyVerticalFlags() == 0); | ||||
const int flags = justification.getOnlyHorizontalFlags(); | |||||
auto flags = justification.getOnlyHorizontalFlags(); | |||||
if (flags == Justification::right) | |||||
{ | |||||
if (startX < context.getClipBounds().getX()) | |||||
return; | |||||
} | |||||
else if (flags == Justification::left) | |||||
if (startX > context.getClipBounds().getRight()) | |||||
return; | |||||
if (flags == Justification::right && startX < context.getClipBounds().getX()) | |||||
return; | |||||
if (flags == Justification::left && startX > context.getClipBounds().getRight()) | |||||
return; | |||||
GlyphArrangement arr; | GlyphArrangement arr; | ||||
arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); | arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); | ||||
if (flags != Justification::left) | if (flags != Justification::left) | ||||
{ | { | ||||
float w = arr.getBoundingBox (0, -1, true).getWidth(); | |||||
auto w = arr.getBoundingBox (0, -1, true).getWidth(); | |||||
if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) | if ((flags & (Justification::horizontallyCentred | Justification::horizontallyJustified)) != 0) | ||||
w /= 2.0f; | w /= 2.0f; | ||||
@@ -371,11 +366,6 @@ void Graphics::fillRectList (const RectangleList<int>& rects) const | |||||
context.fillRect (r, false); | context.fillRect (r, false); | ||||
} | } | ||||
void Graphics::setPixel (int x, int y) const | |||||
{ | |||||
context.fillRect (coordsToRectangle (x, y, 1, 1), false); | |||||
} | |||||
void Graphics::fillAll() const | void Graphics::fillAll() const | ||||
{ | { | ||||
fillRect (context.getClipBounds()); | fillRect (context.getClipBounds()); | ||||
@@ -385,7 +375,7 @@ void Graphics::fillAll (Colour colourToUse) const | |||||
{ | { | ||||
if (! colourToUse.isTransparent()) | if (! colourToUse.isTransparent()) | ||||
{ | { | ||||
const Rectangle<int> clip (context.getClipBounds()); | |||||
auto clip = context.getClipBounds(); | |||||
context.saveState(); | context.saveState(); | ||||
context.setFill (colourToUse); | context.setFill (colourToUse); | ||||
@@ -507,7 +497,7 @@ void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float | |||||
strokePath (p, PathStrokeType (lineThickness)); | strokePath (p, PathStrokeType (lineThickness)); | ||||
} | } | ||||
void Graphics::drawArrow (const Line<float>& line, float lineThickness, float arrowheadWidth, float arrowheadLength) const | |||||
void Graphics::drawArrow (Line<float> line, float lineThickness, float arrowheadWidth, float arrowheadLength) const | |||||
{ | { | ||||
Path p; | Path p; | ||||
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | ||||
@@ -531,7 +521,7 @@ void Graphics::fillCheckerBoard (Rectangle<int> area, | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
const Rectangle<int> clipped (context.getClipBounds().getIntersection (area)); | |||||
auto clipped = context.getClipBounds().getIntersection (area); | |||||
if (! clipped.isEmpty()) | if (! clipped.isEmpty()) | ||||
{ | { | ||||
@@ -573,7 +563,7 @@ void Graphics::drawHorizontalLine (const int y, float left, float right) const | |||||
context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f)); | context.fillRect (Rectangle<float> (left, (float) y, right - left, 1.0f)); | ||||
} | } | ||||
void Graphics::drawLine (const Line<float>& line) const | |||||
void Graphics::drawLine (Line<float> line) const | |||||
{ | { | ||||
context.drawLine (line); | context.drawLine (line); | ||||
} | } | ||||
@@ -588,15 +578,15 @@ void Graphics::drawLine (float x1, float y1, float x2, float y2, float lineThick | |||||
drawLine (Line<float> (x1, y1, x2, y2), lineThickness); | drawLine (Line<float> (x1, y1, x2, y2), lineThickness); | ||||
} | } | ||||
void Graphics::drawLine (const Line<float>& line, const float lineThickness) const | |||||
void Graphics::drawLine (Line<float> line, const float lineThickness) const | |||||
{ | { | ||||
Path p; | Path p; | ||||
p.addLineSegment (line, lineThickness); | p.addLineSegment (line, lineThickness); | ||||
fillPath (p); | fillPath (p); | ||||
} | } | ||||
void Graphics::drawDashedLine (const Line<float>& line, const float* const dashLengths, | |||||
const int numDashLengths, const float lineThickness, int n) const | |||||
void Graphics::drawDashedLine (Line<float> line, const float* dashLengths, | |||||
int numDashLengths, float lineThickness, int n) const | |||||
{ | { | ||||
jassert (n >= 0 && n < numDashLengths); // your start index must be valid! | jassert (n >= 0 && n < numDashLengths); // your start index must be valid! | ||||
@@ -338,12 +338,6 @@ public: | |||||
void drawRoundedRectangle (Rectangle<float> rectangle, | void drawRoundedRectangle (Rectangle<float> rectangle, | ||||
float cornerSize, float lineThickness) const; | float cornerSize, float lineThickness) const; | ||||
/** Fills a 1x1 pixel using the current colour or brush. | |||||
Note that because the context may be transformed, this is effectively the same as | |||||
calling fillRect (x, y, 1, 1), and the actual result may involve multiple pixels. | |||||
*/ | |||||
void setPixel (int x, int y) const; | |||||
//============================================================================== | //============================================================================== | ||||
/** Fills an ellipse with the current colour or brush. | /** Fills an ellipse with the current colour or brush. | ||||
The ellipse is drawn to fit inside the given rectangle. | The ellipse is drawn to fit inside the given rectangle. | ||||
@@ -388,14 +382,14 @@ public: | |||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | TIP: If you're trying to draw horizontal or vertical lines, don't use this - | ||||
it's better to use fillRect() instead unless you really need an angled line. | it's better to use fillRect() instead unless you really need an angled line. | ||||
*/ | */ | ||||
void drawLine (const Line<float>& line) const; | |||||
void drawLine (Line<float> line) const; | |||||
/** Draws a line between two points with a given thickness. | /** Draws a line between two points with a given thickness. | ||||
@see Path::addLineSegment | @see Path::addLineSegment | ||||
TIP: If you're trying to draw horizontal or vertical lines, don't use this - | TIP: If you're trying to draw horizontal or vertical lines, don't use this - | ||||
it's better to use fillRect() instead unless you really need an angled line. | it's better to use fillRect() instead unless you really need an angled line. | ||||
*/ | */ | ||||
void drawLine (const Line<float>& line, float lineThickness) const; | |||||
void drawLine (Line<float> line, float lineThickness) const; | |||||
/** Draws a dashed line using a custom set of dash-lengths. | /** Draws a dashed line using a custom set of dash-lengths. | ||||
@@ -408,7 +402,7 @@ public: | |||||
@param dashIndexToStartFrom the index in the dash-length array to use for the first segment | @param dashIndexToStartFrom the index in the dash-length array to use for the first segment | ||||
@see PathStrokeType::createDashedStroke | @see PathStrokeType::createDashedStroke | ||||
*/ | */ | ||||
void drawDashedLine (const Line<float>& line, | |||||
void drawDashedLine (Line<float> line, | |||||
const float* dashLengths, int numDashLengths, | const float* dashLengths, int numDashLengths, | ||||
float lineThickness = 1.0f, | float lineThickness = 1.0f, | ||||
int dashIndexToStartFrom = 0) const; | int dashIndexToStartFrom = 0) const; | ||||
@@ -441,7 +435,7 @@ public: | |||||
/** Draws a path's outline using the currently selected colour or brush. */ | /** Draws a path's outline using the currently selected colour or brush. */ | ||||
void strokePath (const Path& path, | void strokePath (const Path& path, | ||||
const PathStrokeType& strokeType, | const PathStrokeType& strokeType, | ||||
const AffineTransform& transform = AffineTransform()) const; | |||||
const AffineTransform& transform = {}) const; | |||||
/** Draws a line with an arrowhead at its end. | /** Draws a line with an arrowhead at its end. | ||||
@@ -450,7 +444,7 @@ public: | |||||
@param arrowheadWidth the width of the arrow head (perpendicular to the line) | @param arrowheadWidth the width of the arrow head (perpendicular to the line) | ||||
@param arrowheadLength the length of the arrow head (along the length of the line) | @param arrowheadLength the length of the arrow head (along the length of the line) | ||||
*/ | */ | ||||
void drawArrow (const Line<float>& line, | |||||
void drawArrow (Line<float> line, | |||||
float lineThickness, | float lineThickness, | ||||
float arrowheadWidth, | float arrowheadWidth, | ||||
float arrowheadLength) const; | float arrowheadLength) const; | ||||
@@ -743,7 +737,7 @@ private: | |||||
LowLevelGraphicsContext& context; | LowLevelGraphicsContext& context; | ||||
ScopedPointer<LowLevelGraphicsContext> contextToDelete; | ScopedPointer<LowLevelGraphicsContext> contextToDelete; | ||||
bool saveStatePending; | |||||
bool saveStatePending = false; | |||||
void saveStateIfPending(); | void saveStateIfPending(); | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Graphics) | ||||
@@ -280,7 +280,7 @@ void Path::startNewSubPath (const float x, const float y) | |||||
data.elements[numElements++] = y; | data.elements[numElements++] = y; | ||||
} | } | ||||
void Path::startNewSubPath (const Point<float> start) | |||||
void Path::startNewSubPath (Point<float> start) | |||||
{ | { | ||||
startNewSubPath (start.x, start.y); | startNewSubPath (start.x, start.y); | ||||
} | } | ||||
@@ -301,7 +301,7 @@ void Path::lineTo (const float x, const float y) | |||||
bounds.extend (x, y); | bounds.extend (x, y); | ||||
} | } | ||||
void Path::lineTo (const Point<float> end) | |||||
void Path::lineTo (Point<float> end) | |||||
{ | { | ||||
lineTo (end.x, end.y); | lineTo (end.x, end.y); | ||||
} | } | ||||
@@ -326,8 +326,7 @@ void Path::quadraticTo (const float x1, const float y1, | |||||
bounds.extend (x1, y1, x2, y2); | bounds.extend (x1, y1, x2, y2); | ||||
} | } | ||||
void Path::quadraticTo (const Point<float> controlPoint, | |||||
const Point<float> endPoint) | |||||
void Path::quadraticTo (Point<float> controlPoint, Point<float> endPoint) | |||||
{ | { | ||||
quadraticTo (controlPoint.x, controlPoint.y, | quadraticTo (controlPoint.x, controlPoint.y, | ||||
endPoint.x, endPoint.y); | endPoint.x, endPoint.y); | ||||
@@ -358,9 +357,9 @@ void Path::cubicTo (const float x1, const float y1, | |||||
bounds.extend (x3, y3); | bounds.extend (x3, y3); | ||||
} | } | ||||
void Path::cubicTo (const Point<float> controlPoint1, | |||||
const Point<float> controlPoint2, | |||||
const Point<float> endPoint) | |||||
void Path::cubicTo (Point<float> controlPoint1, | |||||
Point<float> controlPoint2, | |||||
Point<float> endPoint) | |||||
{ | { | ||||
cubicTo (controlPoint1.x, controlPoint1.y, | cubicTo (controlPoint1.x, controlPoint1.y, | ||||
controlPoint2.x, controlPoint2.y, | controlPoint2.x, controlPoint2.y, | ||||
@@ -675,7 +674,7 @@ void Path::addPieSegment (Rectangle<float> segmentBounds, | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void Path::addLineSegment (const Line<float>& line, float lineThickness) | |||||
void Path::addLineSegment (Line<float> line, float lineThickness) | |||||
{ | { | ||||
auto reversed = line.reversed(); | auto reversed = line.reversed(); | ||||
lineThickness *= 0.5f; | lineThickness *= 0.5f; | ||||
@@ -687,7 +686,7 @@ void Path::addLineSegment (const Line<float>& line, float lineThickness) | |||||
closeSubPath(); | closeSubPath(); | ||||
} | } | ||||
void Path::addArrow (const Line<float>& line, float lineThickness, | |||||
void Path::addArrow (Line<float> line, float lineThickness, | |||||
float arrowheadWidth, float arrowheadLength) | float arrowheadWidth, float arrowheadLength) | ||||
{ | { | ||||
auto reversed = line.reversed(); | auto reversed = line.reversed(); | ||||
@@ -705,8 +704,8 @@ void Path::addArrow (const Line<float>& line, float lineThickness, | |||||
closeSubPath(); | closeSubPath(); | ||||
} | } | ||||
void Path::addPolygon (const Point<float> centre, const int numberOfSides, | |||||
const float radius, const float startAngle) | |||||
void Path::addPolygon (Point<float> centre, int numberOfSides, | |||||
float radius, float startAngle) | |||||
{ | { | ||||
jassert (numberOfSides > 1); // this would be silly. | jassert (numberOfSides > 1); // this would be silly. | ||||
@@ -729,8 +728,8 @@ void Path::addPolygon (const Point<float> centre, const int numberOfSides, | |||||
} | } | ||||
} | } | ||||
void Path::addStar (const Point<float> centre, const int numberOfPoints, | |||||
const float innerRadius, const float outerRadius, const float startAngle) | |||||
void Path::addStar (Point<float> centre, int numberOfPoints, float innerRadius, | |||||
float outerRadius, float startAngle) | |||||
{ | { | ||||
jassert (numberOfPoints > 1); // this would be silly. | jassert (numberOfPoints > 1); // this would be silly. | ||||
@@ -755,9 +754,9 @@ void Path::addStar (const Point<float> centre, const int numberOfPoints, | |||||
} | } | ||||
} | } | ||||
void Path::addBubble (const Rectangle<float>& bodyArea, | |||||
const Rectangle<float>& maximumArea, | |||||
const Point<float> arrowTip, | |||||
void Path::addBubble (Rectangle<float> bodyArea, | |||||
Rectangle<float> maximumArea, | |||||
Point<float> arrowTip, | |||||
const float cornerSize, | const float cornerSize, | ||||
const float arrowBaseWidth) | const float arrowBaseWidth) | ||||
{ | { | ||||
@@ -968,8 +967,8 @@ void Path::applyTransform (const AffineTransform& transform) noexcept | |||||
//============================================================================== | //============================================================================== | ||||
AffineTransform Path::getTransformToScaleToFit (const Rectangle<float>& area, | |||||
bool preserveProportions, Justification justification) const | |||||
AffineTransform Path::getTransformToScaleToFit (Rectangle<float> area, bool preserveProportions, | |||||
Justification justification) const | |||||
{ | { | ||||
return getTransformToScaleToFit (area.getX(), area.getY(), area.getWidth(), area.getHeight(), | return getTransformToScaleToFit (area.getX(), area.getY(), area.getWidth(), area.getHeight(), | ||||
preserveProportions, justification); | preserveProportions, justification); | ||||
@@ -1059,7 +1058,7 @@ bool Path::contains (const float x, const float y, const float tolerance) const | |||||
: ((negativeCrossings + positiveCrossings) & 1) != 0; | : ((negativeCrossings + positiveCrossings) & 1) != 0; | ||||
} | } | ||||
bool Path::contains (const Point<float> point, const float tolerance) const | |||||
bool Path::contains (Point<float> point, const float tolerance) const | |||||
{ | { | ||||
return contains (point.x, point.y, tolerance); | return contains (point.x, point.y, tolerance); | ||||
} | } | ||||
@@ -1138,7 +1137,7 @@ Point<float> Path::getPointAlongPath (float distanceFromStart, | |||||
return { i.x2, i.y2 }; | return { i.x2, i.y2 }; | ||||
} | } | ||||
float Path::getNearestPoint (const Point<float> targetPoint, Point<float>& pointOnPath, | |||||
float Path::getNearestPoint (Point<float> targetPoint, Point<float>& pointOnPath, | |||||
const AffineTransform& transform, | const AffineTransform& transform, | ||||
float tolerance) const | float tolerance) const | ||||
{ | { | ||||
@@ -129,7 +129,7 @@ public: | |||||
@see closeSubPath, setUsingNonZeroWinding | @see closeSubPath, setUsingNonZeroWinding | ||||
*/ | */ | ||||
bool contains (const Point<float> point, | |||||
bool contains (Point<float> point, | |||||
float tolerance = defaultToleranceForTesting) const; | float tolerance = defaultToleranceForTesting) const; | ||||
/** Checks whether a line crosses the path. | /** Checks whether a line crosses the path. | ||||
@@ -211,7 +211,7 @@ public: | |||||
@see lineTo, quadraticTo, cubicTo, closeSubPath | @see lineTo, quadraticTo, cubicTo, closeSubPath | ||||
*/ | */ | ||||
void startNewSubPath (const Point<float> start); | |||||
void startNewSubPath (Point<float> start); | |||||
/** Closes a the current sub-path with a line back to its start-point. | /** Closes a the current sub-path with a line back to its start-point. | ||||
@@ -247,7 +247,7 @@ public: | |||||
@see startNewSubPath, quadraticTo, cubicTo, closeSubPath | @see startNewSubPath, quadraticTo, cubicTo, closeSubPath | ||||
*/ | */ | ||||
void lineTo (const Point<float> end); | |||||
void lineTo (Point<float> end); | |||||
/** Adds a quadratic bezier curve from the shape's last position to a new position. | /** Adds a quadratic bezier curve from the shape's last position to a new position. | ||||
@@ -272,8 +272,8 @@ public: | |||||
@see startNewSubPath, lineTo, cubicTo, closeSubPath | @see startNewSubPath, lineTo, cubicTo, closeSubPath | ||||
*/ | */ | ||||
void quadraticTo (const Point<float> controlPoint, | |||||
const Point<float> endPoint); | |||||
void quadraticTo (Point<float> controlPoint, | |||||
Point<float> endPoint); | |||||
/** Adds a cubic bezier curve from the shape's last position to a new position. | /** Adds a cubic bezier curve from the shape's last position to a new position. | ||||
@@ -300,9 +300,9 @@ public: | |||||
@see startNewSubPath, lineTo, quadraticTo, closeSubPath | @see startNewSubPath, lineTo, quadraticTo, closeSubPath | ||||
*/ | */ | ||||
void cubicTo (const Point<float> controlPoint1, | |||||
const Point<float> controlPoint2, | |||||
const Point<float> endPoint); | |||||
void cubicTo (Point<float> controlPoint1, | |||||
Point<float> controlPoint2, | |||||
Point<float> endPoint); | |||||
/** Returns the last point that was added to the path by one of the drawing methods. | /** Returns the last point that was added to the path by one of the drawing methods. | ||||
*/ | */ | ||||
@@ -320,7 +320,7 @@ public: | |||||
@see addRoundedRectangle, addTriangle | @see addRoundedRectangle, addTriangle | ||||
*/ | */ | ||||
template <typename ValueType> | template <typename ValueType> | ||||
void addRectangle (const Rectangle<ValueType>& rectangle) | |||||
void addRectangle (Rectangle<ValueType> rectangle) | |||||
{ | { | ||||
addRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()), | addRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()), | ||||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight())); | static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight())); | ||||
@@ -355,7 +355,7 @@ public: | |||||
@see addRectangle, addTriangle | @see addRectangle, addTriangle | ||||
*/ | */ | ||||
template <typename ValueType> | template <typename ValueType> | ||||
void addRoundedRectangle (const Rectangle<ValueType>& rectangle, float cornerSizeX, float cornerSizeY) | |||||
void addRoundedRectangle (Rectangle<ValueType> rectangle, float cornerSizeX, float cornerSizeY) | |||||
{ | { | ||||
addRoundedRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()), | addRoundedRectangle (static_cast<float> (rectangle.getX()), static_cast<float> (rectangle.getY()), | ||||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight()), | static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight()), | ||||
@@ -367,7 +367,7 @@ public: | |||||
@see addRectangle, addTriangle | @see addRectangle, addTriangle | ||||
*/ | */ | ||||
template <typename ValueType> | template <typename ValueType> | ||||
void addRoundedRectangle (const Rectangle<ValueType>& rectangle, float cornerSize) | |||||
void addRoundedRectangle (Rectangle<ValueType> rectangle, float cornerSize) | |||||
{ | { | ||||
addRoundedRectangle (rectangle, cornerSize, cornerSize); | addRoundedRectangle (rectangle, cornerSize, cornerSize); | ||||
} | } | ||||
@@ -534,13 +534,13 @@ public: | |||||
@see addArrow | @see addArrow | ||||
*/ | */ | ||||
void addLineSegment (const Line<float>& line, float lineThickness); | |||||
void addLineSegment (Line<float> line, float lineThickness); | |||||
/** Adds a line with an arrowhead on the end. | /** Adds a line with an arrowhead on the end. | ||||
The arrow is added as a new closed sub-path. (Any currently open paths will be left open). | The arrow is added as a new closed sub-path. (Any currently open paths will be left open). | ||||
@see PathStrokeType::createStrokeWithArrowheads | @see PathStrokeType::createStrokeWithArrowheads | ||||
*/ | */ | ||||
void addArrow (const Line<float>& line, | |||||
void addArrow (Line<float> line, | |||||
float lineThickness, | float lineThickness, | ||||
float arrowheadWidth, | float arrowheadWidth, | ||||
float arrowheadLength); | float arrowheadLength); | ||||
@@ -548,7 +548,7 @@ public: | |||||
/** Adds a polygon shape to the path. | /** Adds a polygon shape to the path. | ||||
@see addStar | @see addStar | ||||
*/ | */ | ||||
void addPolygon (const Point<float> centre, | |||||
void addPolygon (Point<float> centre, | |||||
int numberOfSides, | int numberOfSides, | ||||
float radius, | float radius, | ||||
float startAngle = 0.0f); | float startAngle = 0.0f); | ||||
@@ -556,7 +556,7 @@ public: | |||||
/** Adds a star shape to the path. | /** Adds a star shape to the path. | ||||
@see addPolygon | @see addPolygon | ||||
*/ | */ | ||||
void addStar (const Point<float> centre, | |||||
void addStar (Point<float> centre, | |||||
int numberOfPoints, | int numberOfPoints, | ||||
float innerRadius, | float innerRadius, | ||||
float outerRadius, | float outerRadius, | ||||
@@ -572,8 +572,8 @@ public: | |||||
@param cornerSize the size of the rounded corners | @param cornerSize the size of the rounded corners | ||||
@param arrowBaseWidth the width of the base of the arrow where it joins the main rectangle | @param arrowBaseWidth the width of the base of the arrow where it joins the main rectangle | ||||
*/ | */ | ||||
void addBubble (const Rectangle<float>& bodyArea, | |||||
const Rectangle<float>& maximumArea, | |||||
void addBubble (Rectangle<float> bodyArea, | |||||
Rectangle<float> maximumArea, | |||||
const Point<float> arrowTipPosition, | const Point<float> arrowTipPosition, | ||||
const float cornerSize, | const float cornerSize, | ||||
const float arrowBaseWidth); | const float arrowBaseWidth); | ||||
@@ -677,7 +677,7 @@ public: | |||||
@see applyTransform, scaleToFit | @see applyTransform, scaleToFit | ||||
*/ | */ | ||||
AffineTransform getTransformToScaleToFit (const Rectangle<float>& area, | |||||
AffineTransform getTransformToScaleToFit (Rectangle<float> area, | |||||
bool preserveProportions, | bool preserveProportions, | ||||
Justification justificationType = Justification::centred) const; | Justification justificationType = Justification::centred) const; | ||||
@@ -41,101 +41,101 @@ class Point | |||||
{ | { | ||||
public: | public: | ||||
/** Creates a point at the origin */ | /** Creates a point at the origin */ | ||||
Point() noexcept : x(), y() {} | |||||
JUCE_CONSTEXPR Point() noexcept : x(), y() {} | |||||
/** Creates a copy of another point. */ | /** Creates a copy of another point. */ | ||||
Point (const Point& other) noexcept : x (other.x), y (other.y) {} | |||||
JUCE_CONSTEXPR Point (const Point& other) noexcept : x (other.x), y (other.y) {} | |||||
/** Creates a point from an (x, y) position. */ | /** Creates a point from an (x, y) position. */ | ||||
Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {} | |||||
JUCE_CONSTEXPR Point (ValueType initialX, ValueType initialY) noexcept : x (initialX), y (initialY) {} | |||||
//============================================================================== | //============================================================================== | ||||
/** Copies this point from another one. */ | /** Copies this point from another one. */ | ||||
Point& operator= (const Point& other) noexcept { x = other.x; y = other.y; return *this; } | |||||
Point& operator= (const Point& other) noexcept { x = other.x; y = other.y; return *this; } | |||||
inline bool operator== (Point other) const noexcept { return x == other.x && y == other.y; } | |||||
inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; } | |||||
JUCE_CONSTEXPR inline bool operator== (Point other) const noexcept { return x == other.x && y == other.y; } | |||||
JUCE_CONSTEXPR inline bool operator!= (Point other) const noexcept { return x != other.x || y != other.y; } | |||||
/** Returns true if the point is (0, 0). */ | /** Returns true if the point is (0, 0). */ | ||||
bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); } | |||||
JUCE_CONSTEXPR bool isOrigin() const noexcept { return x == ValueType() && y == ValueType(); } | |||||
/** Returns true if the coordinates are finite values. */ | /** Returns true if the coordinates are finite values. */ | ||||
inline bool isFinite() const noexcept { return juce_isfinite(x) && juce_isfinite(y); } | |||||
JUCE_CONSTEXPR inline bool isFinite() const noexcept { return juce_isfinite(x) && juce_isfinite(y); } | |||||
/** Returns the point's x coordinate. */ | /** Returns the point's x coordinate. */ | ||||
inline ValueType getX() const noexcept { return x; } | |||||
JUCE_CONSTEXPR inline ValueType getX() const noexcept { return x; } | |||||
/** Returns the point's y coordinate. */ | /** Returns the point's y coordinate. */ | ||||
inline ValueType getY() const noexcept { return y; } | |||||
JUCE_CONSTEXPR inline ValueType getY() const noexcept { return y; } | |||||
/** Sets the point's x coordinate. */ | /** Sets the point's x coordinate. */ | ||||
inline void setX (ValueType newX) noexcept { x = newX; } | |||||
inline void setX (ValueType newX) noexcept { x = newX; } | |||||
/** Sets the point's y coordinate. */ | /** Sets the point's y coordinate. */ | ||||
inline void setY (ValueType newY) noexcept { y = newY; } | |||||
inline void setY (ValueType newY) noexcept { y = newY; } | |||||
/** Returns a point which has the same Y position as this one, but a new X. */ | /** Returns a point which has the same Y position as this one, but a new X. */ | ||||
Point withX (ValueType newX) const noexcept { return Point (newX, y); } | |||||
JUCE_CONSTEXPR Point withX (ValueType newX) const noexcept { return Point (newX, y); } | |||||
/** Returns a point which has the same X position as this one, but a new Y. */ | /** Returns a point which has the same X position as this one, but a new Y. */ | ||||
Point withY (ValueType newY) const noexcept { return Point (x, newY); } | |||||
JUCE_CONSTEXPR Point withY (ValueType newY) const noexcept { return Point (x, newY); } | |||||
/** Changes the point's x and y coordinates. */ | /** Changes the point's x and y coordinates. */ | ||||
void setXY (ValueType newX, ValueType newY) noexcept { x = newX; y = newY; } | |||||
void setXY (ValueType newX, ValueType newY) noexcept { x = newX; y = newY; } | |||||
/** Adds a pair of coordinates to this value. */ | /** Adds a pair of coordinates to this value. */ | ||||
void addXY (ValueType xToAdd, ValueType yToAdd) noexcept { x += xToAdd; y += yToAdd; } | |||||
void addXY (ValueType xToAdd, ValueType yToAdd) noexcept { x += xToAdd; y += yToAdd; } | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns a point with a given offset from this one. */ | /** Returns a point with a given offset from this one. */ | ||||
Point translated (ValueType deltaX, ValueType deltaY) const noexcept { return Point (x + deltaX, y + deltaY); } | |||||
JUCE_CONSTEXPR Point translated (ValueType deltaX, ValueType deltaY) const noexcept { return Point (x + deltaX, y + deltaY); } | |||||
/** Adds two points together */ | /** Adds two points together */ | ||||
Point operator+ (Point other) const noexcept { return Point (x + other.x, y + other.y); } | |||||
JUCE_CONSTEXPR Point operator+ (Point other) const noexcept { return Point (x + other.x, y + other.y); } | |||||
/** Adds another point's coordinates to this one */ | /** Adds another point's coordinates to this one */ | ||||
Point& operator+= (Point other) noexcept { x += other.x; y += other.y; return *this; } | |||||
Point& operator+= (Point other) noexcept { x += other.x; y += other.y; return *this; } | |||||
/** Subtracts one points from another */ | /** Subtracts one points from another */ | ||||
Point operator- (Point other) const noexcept { return Point (x - other.x, y - other.y); } | |||||
JUCE_CONSTEXPR Point operator- (Point other) const noexcept { return Point (x - other.x, y - other.y); } | |||||
/** Subtracts another point's coordinates to this one */ | /** Subtracts another point's coordinates to this one */ | ||||
Point& operator-= (Point other) noexcept { x -= other.x; y -= other.y; return *this; } | |||||
Point& operator-= (Point other) noexcept { x -= other.x; y -= other.y; return *this; } | |||||
/** Multiplies two points together */ | /** Multiplies two points together */ | ||||
template <typename OtherType> | template <typename OtherType> | ||||
Point operator* (Point<OtherType> other) const noexcept { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); } | |||||
JUCE_CONSTEXPR Point operator* (Point<OtherType> other) const noexcept { return Point ((ValueType) (x * other.x), (ValueType) (y * other.y)); } | |||||
/** Multiplies another point's coordinates to this one */ | /** Multiplies another point's coordinates to this one */ | ||||
template <typename OtherType> | template <typename OtherType> | ||||
Point& operator*= (Point<OtherType> other) noexcept { *this = *this * other; return *this; } | |||||
Point& operator*= (Point<OtherType> other) noexcept { *this = *this * other; return *this; } | |||||
/** Divides one point by another */ | /** Divides one point by another */ | ||||
template <typename OtherType> | template <typename OtherType> | ||||
Point operator/ (Point<OtherType> other) const noexcept { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); } | |||||
JUCE_CONSTEXPR Point operator/ (Point<OtherType> other) const noexcept { return Point ((ValueType) (x / other.x), (ValueType) (y / other.y)); } | |||||
/** Divides this point's coordinates by another */ | /** Divides this point's coordinates by another */ | ||||
template <typename OtherType> | template <typename OtherType> | ||||
Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; } | |||||
Point& operator/= (Point<OtherType> other) noexcept { *this = *this / other; return *this; } | |||||
/** Returns a point whose coordinates are multiplied by a given scalar value. */ | /** Returns a point whose coordinates are multiplied by a given scalar value. */ | ||||
template <typename FloatType> | template <typename FloatType> | ||||
Point operator* (FloatType multiplier) const noexcept { return Point ((ValueType) (x * multiplier), (ValueType) (y * multiplier)); } | |||||
JUCE_CONSTEXPR Point operator* (FloatType multiplier) const noexcept { return Point ((ValueType) (x * multiplier), (ValueType) (y * multiplier)); } | |||||
/** Returns a point whose coordinates are divided by a given scalar value. */ | /** Returns a point whose coordinates are divided by a given scalar value. */ | ||||
template <typename FloatType> | template <typename FloatType> | ||||
Point operator/ (FloatType divisor) const noexcept { return Point ((ValueType) (x / divisor), (ValueType) (y / divisor)); } | |||||
JUCE_CONSTEXPR Point operator/ (FloatType divisor) const noexcept { return Point ((ValueType) (x / divisor), (ValueType) (y / divisor)); } | |||||
/** Multiplies the point's coordinates by a scalar value. */ | /** Multiplies the point's coordinates by a scalar value. */ | ||||
template <typename FloatType> | template <typename FloatType> | ||||
Point& operator*= (FloatType multiplier) noexcept { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; } | |||||
Point& operator*= (FloatType multiplier) noexcept { x = (ValueType) (x * multiplier); y = (ValueType) (y * multiplier); return *this; } | |||||
/** Divides the point's coordinates by a scalar value. */ | /** Divides the point's coordinates by a scalar value. */ | ||||
template <typename FloatType> | template <typename FloatType> | ||||
Point& operator/= (FloatType divisor) noexcept { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; } | |||||
Point& operator/= (FloatType divisor) noexcept { x = (ValueType) (x / divisor); y = (ValueType) (y / divisor); return *this; } | |||||
/** Returns the inverse of this point. */ | /** Returns the inverse of this point. */ | ||||
Point operator-() const noexcept { return Point (-x, -y); } | |||||
JUCE_CONSTEXPR Point operator-() const noexcept { return Point (-x, -y); } | |||||
//============================================================================== | //============================================================================== | ||||
/** This type will be double if the Point's type is double, otherwise it will be float. */ | /** This type will be double if the Point's type is double, otherwise it will be float. */ | ||||
@@ -443,9 +443,12 @@ void Image::BitmapData::setPixelColour (const int x, const int y, Colour colour) | |||||
//============================================================================== | //============================================================================== | ||||
void Image::clear (const Rectangle<int>& area, Colour colourToClearTo) | void Image::clear (const Rectangle<int>& area, Colour colourToClearTo) | ||||
{ | { | ||||
const ScopedPointer<LowLevelGraphicsContext> g (image->createLowLevelContext()); | |||||
g->setFill (colourToClearTo); | |||||
g->fillRect (area, true); | |||||
if (image != nullptr) | |||||
{ | |||||
const ScopedPointer<LowLevelGraphicsContext> g (image->createLowLevelContext()); | |||||
g->setFill (colourToClearTo); | |||||
g->fillRect (area, true); | |||||
} | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -2295,11 +2295,11 @@ public: | |||||
} | } | ||||
} | } | ||||
void drawLine (const Line<float>& line) | |||||
void drawLine (Line<float> line) | |||||
{ | { | ||||
Path p; | Path p; | ||||
p.addLineSegment (line, 1.0f); | p.addLineSegment (line, 1.0f); | ||||
fillPath (p, AffineTransform()); | |||||
fillPath (p, {}); | |||||
} | } | ||||
void drawImage (const Image& sourceImage, const AffineTransform& trans) | void drawImage (const Image& sourceImage, const AffineTransform& trans) | ||||
@@ -2319,7 +2319,7 @@ public: | |||||
void renderImage (const Image& sourceImage, const AffineTransform& trans, | void renderImage (const Image& sourceImage, const AffineTransform& trans, | ||||
const BaseRegionType* const tiledFillClipRegion) | const BaseRegionType* const tiledFillClipRegion) | ||||
{ | { | ||||
const AffineTransform t (transform.getTransformWith (trans)); | |||||
auto t = transform.getTransformWith (trans); | |||||
const int alpha = fillType.colour.getAlpha(); | const int alpha = fillType.colour.getAlpha(); | ||||
@@ -2356,18 +2356,17 @@ public: | |||||
{ | { | ||||
if (tiledFillClipRegion != nullptr) | if (tiledFillClipRegion != nullptr) | ||||
{ | { | ||||
tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, true); | |||||
tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, | |||||
t, interpolationQuality, true); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
Path p; | Path p; | ||||
p.addRectangle (sourceImage.getBounds()); | p.addRectangle (sourceImage.getBounds()); | ||||
typename BaseRegionType::Ptr c (clip->clone()); | |||||
c = c->clipToPath (p, t); | |||||
if (c != nullptr) | |||||
c->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, false); | |||||
if (auto c = clip->clone()->clipToPath (p, t)) | |||||
c->renderImageTransformed (getThis(), sourceImage, alpha, | |||||
t, interpolationQuality, false); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -2386,7 +2385,7 @@ public: | |||||
ColourGradient g2 (*(fillType.gradient)); | ColourGradient g2 (*(fillType.gradient)); | ||||
g2.multiplyOpacity (fillType.getOpacity()); | g2.multiplyOpacity (fillType.getOpacity()); | ||||
AffineTransform t (transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f)); | |||||
auto t = transform.getTransformWith (fillType.transform).translated (-0.5f, -0.5f); | |||||
const bool isIdentity = t.isOnlyTranslation(); | const bool isIdentity = t.isOnlyTranslation(); | ||||
@@ -26,7 +26,7 @@ | |||||
class Button::CallbackHelper : public Timer, | class Button::CallbackHelper : public Timer, | ||||
public ApplicationCommandManagerListener, | public ApplicationCommandManagerListener, | ||||
public ValueListener, | |||||
public Value::Listener, | |||||
public KeyListener | public KeyListener | ||||
{ | { | ||||
public: | public: | ||||
@@ -394,15 +394,8 @@ void Button::handleCommandMessage (int commandId) | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void Button::addListener (ButtonListener* const newListener) | |||||
{ | |||||
buttonListeners.add (newListener); | |||||
} | |||||
void Button::removeListener (ButtonListener* const listener) | |||||
{ | |||||
buttonListeners.remove (listener); | |||||
} | |||||
void Button::addListener (Listener* l) { buttonListeners.add (l); } | |||||
void Button::removeListener (Listener* l) { buttonListeners.remove (l); } | |||||
void Button::sendClickMessage (const ModifierKeys& modifiers) | void Button::sendClickMessage (const ModifierKeys& modifiers) | ||||
{ | { | ||||
@@ -420,7 +413,7 @@ void Button::sendClickMessage (const ModifierKeys& modifiers) | |||||
clicked (modifiers); | clicked (modifiers); | ||||
if (! checker.shouldBailOut()) | if (! checker.shouldBailOut()) | ||||
buttonListeners.callChecked (checker, &ButtonListener::buttonClicked, this); // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
buttonListeners.callChecked (checker, &Button::Listener::buttonClicked, this); | |||||
} | } | ||||
void Button::sendStateMessage() | void Button::sendStateMessage() | ||||
@@ -430,7 +423,7 @@ void Button::sendStateMessage() | |||||
buttonStateChanged(); | buttonStateChanged(); | ||||
if (! checker.shouldBailOut()) | if (! checker.shouldBailOut()) | ||||
buttonListeners.callChecked (checker, &ButtonListener::buttonStateChanged, this); | |||||
buttonListeners.callChecked (checker, &Button::Listener::buttonStateChanged, this); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -397,7 +397,7 @@ protected: | |||||
Subclasses can override this to perform whatever they actions they need | Subclasses can override this to perform whatever they actions they need | ||||
to do. | to do. | ||||
Alternatively, a ButtonListener can be added to the button, and these listeners | |||||
Alternatively, a Button::Listener can be added to the button, and these listeners | |||||
will be called when the click occurs. | will be called when the click occurs. | ||||
@see triggerClick | @see triggerClick | ||||
@@ -25,10 +25,7 @@ | |||||
*/ | */ | ||||
DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle) | DrawableButton::DrawableButton (const String& name, const DrawableButton::ButtonStyle buttonStyle) | ||||
: Button (name), | |||||
style (buttonStyle), | |||||
currentImage (nullptr), | |||||
edgeIndent (3) | |||||
: Button (name), style (buttonStyle) | |||||
{ | { | ||||
} | } | ||||
@@ -85,12 +82,12 @@ void DrawableButton::setEdgeIndent (const int numPixelsIndent) | |||||
Rectangle<float> DrawableButton::getImageBounds() const | Rectangle<float> DrawableButton::getImageBounds() const | ||||
{ | { | ||||
Rectangle<int> r (getLocalBounds()); | |||||
auto r = getLocalBounds(); | |||||
if (style != ImageStretched) | if (style != ImageStretched) | ||||
{ | { | ||||
int indentX = jmin (edgeIndent, proportionOfWidth (0.3f)); | |||||
int indentY = jmin (edgeIndent, proportionOfHeight (0.3f)); | |||||
auto indentX = jmin (edgeIndent, proportionOfWidth (0.3f)); | |||||
auto indentY = jmin (edgeIndent, proportionOfHeight (0.3f)); | |||||
if (style == ImageOnButtonBackground) | if (style == ImageOnButtonBackground) | ||||
{ | { | ||||
@@ -178,7 +175,7 @@ void DrawableButton::paintButton (Graphics& g, | |||||
const bool isMouseOverButton, | const bool isMouseOverButton, | ||||
const bool isButtonDown) | const bool isButtonDown) | ||||
{ | { | ||||
LookAndFeel& lf = getLookAndFeel(); | |||||
auto& lf = getLookAndFeel(); | |||||
if (style == ImageOnButtonBackground) | if (style == ImageOnButtonBackground) | ||||
lf.drawButtonBackground (g, *this, | lf.drawButtonBackground (g, *this, | ||||
@@ -217,7 +214,7 @@ Drawable* DrawableButton::getOverImage() const noexcept | |||||
Drawable* DrawableButton::getDownImage() const noexcept | Drawable* DrawableButton::getDownImage() const noexcept | ||||
{ | { | ||||
if (Drawable* const d = getToggleState() ? downImageOn : downImage) | |||||
if (auto* d = getToggleState() ? downImageOn.get() : downImage.get()) | |||||
return d; | return d; | ||||
return getOverImage(); | return getOverImage(); | ||||
@@ -122,6 +122,9 @@ public: | |||||
*/ | */ | ||||
void setEdgeIndent (int numPixelsIndent); | void setEdgeIndent (int numPixelsIndent); | ||||
/** Returns the current edge indent size. */ | |||||
int getEdgeIndent() const noexcept { return edgeIndent; } | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the image that the button is currently displaying. */ | /** Returns the image that the button is currently displaying. */ | ||||
Drawable* getCurrentImage() const noexcept; | Drawable* getCurrentImage() const noexcept; | ||||
@@ -179,8 +182,8 @@ private: | |||||
ButtonStyle style; | ButtonStyle style; | ||||
ScopedPointer<Drawable> normalImage, overImage, downImage, disabledImage, | ScopedPointer<Drawable> normalImage, overImage, downImage, disabledImage, | ||||
normalImageOn, overImageOn, downImageOn, disabledImageOn; | normalImageOn, overImageOn, downImageOn, disabledImageOn; | ||||
Drawable* currentImage; | |||||
int edgeIndent; | |||||
Drawable* currentImage = nullptr; | |||||
int edgeIndent = 3; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton) | ||||
}; | }; |
@@ -31,10 +31,7 @@ Component* Component::currentlyFocusedComponent = nullptr; | |||||
class Component::MouseListenerList | class Component::MouseListenerList | ||||
{ | { | ||||
public: | public: | ||||
MouseListenerList() noexcept | |||||
: numDeepMouseListeners (0) | |||||
{ | |||||
} | |||||
MouseListenerList() noexcept {} | |||||
void addListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) | void addListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) | ||||
{ | { | ||||
@@ -54,7 +51,7 @@ public: | |||||
void removeListener (MouseListener* const listenerToRemove) | void removeListener (MouseListener* const listenerToRemove) | ||||
{ | { | ||||
const int index = listeners.indexOf (listenerToRemove); | |||||
auto index = listeners.indexOf (listenerToRemove); | |||||
if (index >= 0) | if (index >= 0) | ||||
{ | { | ||||
@@ -86,20 +83,21 @@ public: | |||||
for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) | for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) | ||||
{ | { | ||||
auto* list = p->mouseListeners.get(); | |||||
if (list != nullptr && list->numDeepMouseListeners > 0) | |||||
if (auto* list = p->mouseListeners.get()) | |||||
{ | { | ||||
BailOutChecker2 checker2 (checker, p); | |||||
for (int i = list->numDeepMouseListeners; --i >= 0;) | |||||
if (list->numDeepMouseListeners > 0) | |||||
{ | { | ||||
(list->listeners.getUnchecked(i)->*eventMethod) (e); | |||||
BailOutChecker2 checker2 (checker, p); | |||||
if (checker2.shouldBailOut()) | |||||
return; | |||||
for (int i = list->numDeepMouseListeners; --i >= 0;) | |||||
{ | |||||
(list->listeners.getUnchecked(i)->*eventMethod) (e); | |||||
if (checker2.shouldBailOut()) | |||||
return; | |||||
i = jmin (i, list->numDeepMouseListeners); | |||||
i = jmin (i, list->numDeepMouseListeners); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -123,20 +121,21 @@ public: | |||||
for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) | for (Component* p = comp.parentComponent; p != nullptr; p = p->parentComponent) | ||||
{ | { | ||||
MouseListenerList* const list = p->mouseListeners; | |||||
if (list != nullptr && list->numDeepMouseListeners > 0) | |||||
if (auto* list = p->mouseListeners.get()) | |||||
{ | { | ||||
BailOutChecker2 checker2 (checker, p); | |||||
for (int i = list->numDeepMouseListeners; --i >= 0;) | |||||
if (list->numDeepMouseListeners > 0) | |||||
{ | { | ||||
list->listeners.getUnchecked(i)->mouseWheelMove (e, wheel); | |||||
BailOutChecker2 checker2 (checker, p); | |||||
if (checker2.shouldBailOut()) | |||||
return; | |||||
for (int i = list->numDeepMouseListeners; --i >= 0;) | |||||
{ | |||||
list->listeners.getUnchecked(i)->mouseWheelMove (e, wheel); | |||||
if (checker2.shouldBailOut()) | |||||
return; | |||||
i = jmin (i, list->numDeepMouseListeners); | |||||
i = jmin (i, list->numDeepMouseListeners); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -144,7 +143,7 @@ public: | |||||
private: | private: | ||||
Array<MouseListener*> listeners; | Array<MouseListener*> listeners; | ||||
int numDeepMouseListeners; | |||||
int numDeepMouseListeners = 0; | |||||
struct BailOutChecker2 | struct BailOutChecker2 | ||||
{ | { | ||||
@@ -175,11 +174,10 @@ struct FocusRestorer | |||||
~FocusRestorer() | ~FocusRestorer() | ||||
{ | { | ||||
if (lastFocus != nullptr && ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) | |||||
{ | |||||
if (lastFocus != nullptr && lastFocus->isShowing()) | |||||
lastFocus->grabKeyboardFocus(); | |||||
} | |||||
if (lastFocus != nullptr | |||||
&& lastFocus->isShowing() | |||||
&& ! lastFocus->isCurrentlyBlockedByAnotherModalComponent()) | |||||
lastFocus->grabKeyboardFocus(); | |||||
} | } | ||||
WeakReference<Component> lastFocus; | WeakReference<Component> lastFocus; | ||||
@@ -326,7 +324,7 @@ struct Component::ComponentHelpers | |||||
if (comp.isOnDesktop()) | if (comp.isOnDesktop()) | ||||
{ | { | ||||
if (ComponentPeer* peer = comp.getPeer()) | |||||
if (auto* peer = comp.getPeer()) | |||||
pointInParentSpace = ScalingHelpers::unscaledScreenPosToScaled | pointInParentSpace = ScalingHelpers::unscaledScreenPosToScaled | ||||
(comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (pointInParentSpace))); | (comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (pointInParentSpace))); | ||||
else | else | ||||
@@ -345,7 +343,7 @@ struct Component::ComponentHelpers | |||||
{ | { | ||||
if (comp.isOnDesktop()) | if (comp.isOnDesktop()) | ||||
{ | { | ||||
if (ComponentPeer* peer = comp.getPeer()) | |||||
if (auto* peer = comp.getPeer()) | |||||
pointInLocalSpace = ScalingHelpers::unscaledScreenPosToScaled | pointInLocalSpace = ScalingHelpers::unscaledScreenPosToScaled | ||||
(peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); | (peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); | ||||
else | else | ||||
@@ -608,7 +606,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||||
// don't use getPeer(), so that we only get the peer that's specifically | // don't use getPeer(), so that we only get the peer that's specifically | ||||
// for this comp, and not for one of its parents. | // for this comp, and not for one of its parents. | ||||
ComponentPeer* peer = ComponentPeer::getPeerFor (this); | |||||
auto* peer = ComponentPeer::getPeerFor (this); | |||||
if (peer == nullptr || styleWanted != peer->getStyleFlags()) | if (peer == nullptr || styleWanted != peer->getStyleFlags()) | ||||
{ | { | ||||
@@ -622,7 +620,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||||
jmax (1, getHeight())); | jmax (1, getHeight())); | ||||
#endif | #endif | ||||
const Point<int> topLeft (getScreenPosition()); | |||||
auto topLeft = getScreenPosition(); | |||||
bool wasFullscreen = false; | bool wasFullscreen = false; | ||||
bool wasMinimised = false; | bool wasMinimised = false; | ||||
@@ -703,7 +701,7 @@ void Component::removeFromDesktop() | |||||
if (flags.hasHeavyweightPeerFlag) | if (flags.hasHeavyweightPeerFlag) | ||||
{ | { | ||||
ComponentPeer* const peer = ComponentPeer::getPeerFor (this); | |||||
auto* peer = ComponentPeer::getPeerFor (this); | |||||
jassert (peer != nullptr); | jassert (peer != nullptr); | ||||
flags.hasHeavyweightPeerFlag = false; | flags.hasHeavyweightPeerFlag = false; | ||||
@@ -753,7 +751,7 @@ void Component::setOpaque (const bool shouldBeOpaque) | |||||
flags.opaqueFlag = shouldBeOpaque; | flags.opaqueFlag = shouldBeOpaque; | ||||
if (flags.hasHeavyweightPeerFlag) | if (flags.hasHeavyweightPeerFlag) | ||||
if (const ComponentPeer* const peer = ComponentPeer::getPeerFor (this)) | |||||
if (auto* peer = ComponentPeer::getPeerFor (this)) | |||||
addToDesktop (peer->getStyleFlags()); // recreates the heavyweight window | addToDesktop (peer->getStyleFlags()); // recreates the heavyweight window | ||||
repaint(); | repaint(); | ||||
@@ -955,8 +953,8 @@ void Component::toBehind (Component* const other) | |||||
{ | { | ||||
auto* us = getPeer(); | auto* us = getPeer(); | ||||
auto* them = other->getPeer(); | auto* them = other->getPeer(); | ||||
jassert (us != nullptr && them != nullptr); | jassert (us != nullptr && them != nullptr); | ||||
if (us != nullptr && them != nullptr) | if (us != nullptr && them != nullptr) | ||||
us->toBehind (them); | us->toBehind (them); | ||||
} | } | ||||
@@ -1008,7 +1006,7 @@ void Component::setAlwaysOnTop (const bool shouldStayOnTop) | |||||
{ | { | ||||
// some kinds of peer can't change their always-on-top status, so | // some kinds of peer can't change their always-on-top status, so | ||||
// for these, we'll need to create a new window | // for these, we'll need to create a new window | ||||
const int oldFlags = peer->getStyleFlags(); | |||||
auto oldFlags = peer->getStyleFlags(); | |||||
removeFromDesktop(); | removeFromDesktop(); | ||||
addToDesktop (oldFlags); | addToDesktop (oldFlags); | ||||
} | } | ||||
@@ -1248,8 +1246,8 @@ void Component::setBounds (const String& newBoundsExpression) | |||||
void Component::setBoundsRelative (const float x, const float y, | void Component::setBoundsRelative (const float x, const float y, | ||||
const float w, const float h) | const float w, const float h) | ||||
{ | { | ||||
const int pw = getParentWidth(); | |||||
const int ph = getParentHeight(); | |||||
auto pw = getParentWidth(); | |||||
auto ph = getParentHeight(); | |||||
setBounds (roundToInt (x * pw), | setBounds (roundToInt (x * pw), | ||||
roundToInt (y * ph), | roundToInt (y * ph), | ||||
@@ -1448,7 +1446,7 @@ Component* Component::getComponentAt (Point<int> position) | |||||
Component* Component::getComponentAt (const int x, const int y) | Component* Component::getComponentAt (const int x, const int y) | ||||
{ | { | ||||
return getComponentAt (Point<int> (x, y)); | |||||
return getComponentAt ({ x, y }); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -1873,7 +1871,7 @@ void Component::repaint() | |||||
void Component::repaint (const int x, const int y, const int w, const int h) | void Component::repaint (const int x, const int y, const int w, const int h) | ||||
{ | { | ||||
internalRepaint (Rectangle<int> (x, y, w, h)); | |||||
internalRepaint ({ x, y, w, h }); | |||||
} | } | ||||
void Component::repaint (Rectangle<int> area) | void Component::repaint (Rectangle<int> area) | ||||
@@ -1964,7 +1962,7 @@ void Component::paintComponentAndChildren (Graphics& g) | |||||
{ | { | ||||
g.saveState(); | g.saveState(); | ||||
if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point<int>()) && g.isClipEmpty())) | |||||
if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty())) | |||||
paint (g); | paint (g); | ||||
g.restoreState(); | g.restoreState(); | ||||
@@ -2168,7 +2166,7 @@ void Component::sendLookAndFeelChange() | |||||
Colour Component::findColour (const int colourId, const bool inheritFromParent) const | Colour Component::findColour (const int colourId, const bool inheritFromParent) const | ||||
{ | { | ||||
if (const var* const v = properties.getVarPointer (ComponentHelpers::getColourPropertyId (colourId))) | |||||
if (auto* v = properties.getVarPointer (ComponentHelpers::getColourPropertyId (colourId))) | |||||
return Colour ((uint32) static_cast<int> (*v)); | return Colour ((uint32) static_cast<int> (*v)); | ||||
if (inheritFromParent && parentComponent != nullptr | if (inheritFromParent && parentComponent != nullptr | ||||
@@ -2201,7 +2199,7 @@ void Component::copyAllExplicitColoursTo (Component& target) const | |||||
for (int i = properties.size(); --i >= 0;) | for (int i = properties.size(); --i >= 0;) | ||||
{ | { | ||||
const Identifier name (properties.getName(i)); | |||||
auto name = properties.getName(i); | |||||
if (name.toString().startsWith ("jcclr_")) | if (name.toString().startsWith ("jcclr_")) | ||||
if (target.properties.set (name, properties [name])) | if (target.properties.set (name, properties [name])) | ||||
@@ -2418,7 +2416,7 @@ void Component::internalMouseExit (MouseInputSource source, Point<float> relativ | |||||
void Component::internalMouseDown (MouseInputSource source, Point<float> relativePos, Time time, | void Component::internalMouseDown (MouseInputSource source, Point<float> relativePos, Time time, | ||||
float pressure, float orientation, float rotation, float tiltX, float tiltY) | float pressure, float orientation, float rotation, float tiltX, float tiltY) | ||||
{ | { | ||||
Desktop& desktop = Desktop::getInstance(); | |||||
auto& desktop = Desktop::getInstance(); | |||||
BailOutChecker checker (this); | BailOutChecker checker (this); | ||||
if (isCurrentlyBlockedByAnotherModalComponent()) | if (isCurrentlyBlockedByAnotherModalComponent()) | ||||
@@ -2445,7 +2443,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||||
flags.mouseDownWasBlocked = false; | flags.mouseDownWasBlocked = false; | ||||
for (Component* c = this; c != nullptr; c = c->parentComponent) | |||||
for (auto* c = this; c != nullptr; c = c->parentComponent) | |||||
{ | { | ||||
if (c->isBroughtToFrontOnMouseClick()) | if (c->isBroughtToFrontOnMouseClick()) | ||||
{ | { | ||||
@@ -2502,7 +2500,7 @@ void Component::internalMouseUp (MouseInputSource source, Point<float> relativeP | |||||
if (checker.shouldBailOut()) | if (checker.shouldBailOut()) | ||||
return; | return; | ||||
Desktop& desktop = Desktop::getInstance(); | |||||
auto& desktop = Desktop::getInstance(); | |||||
desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseUp, me); | desktop.getMouseListeners().callChecked (checker, &MouseListener::mouseUp, me); | ||||
MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseUp, me); | MouseListenerList::sendMouseEvent (*this, checker, &MouseListener::mouseUp, me); | ||||
@@ -2549,7 +2547,7 @@ void Component::internalMouseDrag (MouseInputSource source, Point<float> relativ | |||||
void Component::internalMouseMove (MouseInputSource source, Point<float> relativePos, Time time) | void Component::internalMouseMove (MouseInputSource source, Point<float> relativePos, Time time) | ||||
{ | { | ||||
Desktop& desktop = Desktop::getInstance(); | |||||
auto& desktop = Desktop::getInstance(); | |||||
if (isCurrentlyBlockedByAnotherModalComponent()) | if (isCurrentlyBlockedByAnotherModalComponent()) | ||||
{ | { | ||||
@@ -2578,7 +2576,7 @@ void Component::internalMouseMove (MouseInputSource source, Point<float> relativ | |||||
void Component::internalMouseWheel (MouseInputSource source, Point<float> relativePos, | void Component::internalMouseWheel (MouseInputSource source, Point<float> relativePos, | ||||
Time time, const MouseWheelDetails& wheel) | Time time, const MouseWheelDetails& wheel) | ||||
{ | { | ||||
Desktop& desktop = Desktop::getInstance(); | |||||
auto& desktop = Desktop::getInstance(); | |||||
BailOutChecker checker (this); | BailOutChecker checker (this); | ||||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | ||||
@@ -2854,9 +2852,7 @@ void Component::moveKeyboardFocusToSibling (const bool moveToNext) | |||||
if (parentComponent != nullptr) | if (parentComponent != nullptr) | ||||
{ | { | ||||
ScopedPointer<KeyboardFocusTraverser> traverser (createFocusTraverser()); | |||||
if (traverser != nullptr) | |||||
if (ScopedPointer<KeyboardFocusTraverser> traverser = createFocusTraverser()) | |||||
{ | { | ||||
auto* nextComp = moveToNext ? traverser->getNextComponent (this) | auto* nextComp = moveToNext ? traverser->getNextComponent (this) | ||||
: traverser->getPreviousComponent (this); | : traverser->getPreviousComponent (this); | ||||
@@ -762,8 +762,8 @@ public: | |||||
template <class TargetClass> | template <class TargetClass> | ||||
TargetClass* findParentComponentOfClass() const | TargetClass* findParentComponentOfClass() const | ||||
{ | { | ||||
for (Component* p = parentComponent; p != nullptr; p = p->parentComponent) | |||||
if (TargetClass* const target = dynamic_cast<TargetClass*> (p)) | |||||
for (auto* p = parentComponent; p != nullptr; p = p->parentComponent) | |||||
if (auto* target = dynamic_cast<TargetClass*> (p)) | |||||
return target; | return target; | ||||
return nullptr; | return nullptr; | ||||
@@ -289,13 +289,13 @@ public: | |||||
void beginDragAutoRepeat (int millisecondsBetweenCallbacks); | void beginDragAutoRepeat (int millisecondsBetweenCallbacks); | ||||
//============================================================================== | //============================================================================== | ||||
/** In a tablet device which can be turned around, this is used to inidicate the orientation. */ | |||||
/** In a tablet/mobile device which can be turned around, this is used to indicate the orientation. */ | |||||
enum DisplayOrientation | enum DisplayOrientation | ||||
{ | { | ||||
upright = 1, /**< Indicates that the display is the normal way up. */ | |||||
upsideDown = 2, /**< Indicates that the display is upside-down. */ | |||||
rotatedClockwise = 4, /**< Indicates that the display is turned 90 degrees clockwise from its upright position. */ | |||||
rotatedAntiClockwise = 8, /**< Indicates that the display is turned 90 degrees anti-clockwise from its upright position. */ | |||||
upright = 1, /**< Indicates that the device is the normal way up. */ | |||||
upsideDown = 2, /**< Indicates that the device is upside-down. */ | |||||
rotatedClockwise = 4, /**< Indicates that the device is turned 90 degrees clockwise from its upright position. */ | |||||
rotatedAntiClockwise = 8, /**< Indicates that the device is turned 90 degrees anti-clockwise from its upright position. */ | |||||
allOrientations = 1 + 2 + 4 + 8 /**< A combination of all the orientation values */ | allOrientations = 1 + 2 + 4 + 8 /**< A combination of all the orientation values */ | ||||
}; | }; | ||||
@@ -75,20 +75,6 @@ public: | |||||
} | } | ||||
}; | }; | ||||
struct UseShapeOp | |||||
{ | |||||
const SVGState* state; | |||||
Path* sourcePath; | |||||
AffineTransform* transform; | |||||
Drawable* target; | |||||
bool operator() (const XmlPath& xmlPath) | |||||
{ | |||||
target = state->parseShape (xmlPath, *sourcePath, true, transform); | |||||
return target != nullptr; | |||||
} | |||||
}; | |||||
struct UseTextOp | struct UseTextOp | ||||
{ | { | ||||
const SVGState* state; | const SVGState* state; | ||||
@@ -690,22 +676,6 @@ private: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
Drawable* useShape (const XmlPath& xml, Path& path) const | |||||
{ | |||||
auto translation = AffineTransform::translation ((float) xml->getDoubleAttribute ("x", 0.0), | |||||
(float) xml->getDoubleAttribute ("y", 0.0)); | |||||
UseShapeOp op = { this, &path, &translation, nullptr }; | |||||
auto linkedID = getLinkedID (xml); | |||||
if (linkedID.isNotEmpty()) | |||||
topLevelXml.applyOperationToChildWithID (linkedID, op); | |||||
return op.target; | |||||
} | |||||
Drawable* parseShape (const XmlPath& xml, Path& path, | Drawable* parseShape (const XmlPath& xml, Path& path, | ||||
const bool shouldParseTransform = true, | const bool shouldParseTransform = true, | ||||
AffineTransform* additonalTransform = nullptr) const | AffineTransform* additonalTransform = nullptr) const | ||||
@@ -718,9 +688,6 @@ private: | |||||
return newState.parseShape (xml, path, false, additonalTransform); | return newState.parseShape (xml, path, false, additonalTransform); | ||||
} | } | ||||
if (xml->hasTagName ("use")) | |||||
return useShape (xml, path); | |||||
auto dp = new DrawablePath(); | auto dp = new DrawablePath(); | ||||
setCommonAttributes (*dp, xml); | setCommonAttributes (*dp, xml); | ||||
dp->setFill (Colours::transparentBlack); | dp->setFill (Colours::transparentBlack); | ||||
@@ -1159,7 +1126,7 @@ private: | |||||
if (getStyleAttribute (xml, "font-weight").containsIgnoreCase ("bold")) | if (getStyleAttribute (xml, "font-weight").containsIgnoreCase ("bold")) | ||||
f.setBold (true); | f.setBold (true); | ||||
return f.withPointHeight (getCoordLength (getStyleAttribute (xml, "font-size"), 1.0f)); | |||||
return f.withPointHeight (getCoordLength (getStyleAttribute (xml, "font-size", "15"), 1.0f)); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -39,9 +39,9 @@ | |||||
*/ | */ | ||||
class JUCE_API FileBrowserComponent : public Component, | class JUCE_API FileBrowserComponent : public Component, | ||||
private FileBrowserListener, | private FileBrowserListener, | ||||
private TextEditorListener, | |||||
private ButtonListener, | |||||
private ComboBoxListener, // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||||
private TextEditor::Listener, | |||||
private Button::Listener, | |||||
private ComboBox::Listener, | |||||
private FileFilter, | private FileFilter, | ||||
private Timer | private Timer | ||||
{ | { | ||||
@@ -63,7 +63,7 @@ | |||||
@see FileChooser | @see FileChooser | ||||
*/ | */ | ||||
class JUCE_API FileChooserDialogBox : public ResizableWindow, | class JUCE_API FileChooserDialogBox : public ResizableWindow, | ||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
private Button::Listener, | |||||
private FileBrowserListener | private FileBrowserListener | ||||
{ | { | ||||
public: | public: | ||||
@@ -37,7 +37,7 @@ | |||||
class JUCE_API FileSearchPathListComponent : public Component, | class JUCE_API FileSearchPathListComponent : public Component, | ||||
public SettableTooltipClient, | public SettableTooltipClient, | ||||
public FileDragAndDropTarget, | public FileDragAndDropTarget, | ||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
private Button::Listener, | |||||
private ListBoxModel | private ListBoxModel | ||||
{ | { | ||||
public: | public: | ||||
@@ -65,8 +65,8 @@ class JUCE_API FilenameComponent : public Component, | |||||
public SettableTooltipClient, | public SettableTooltipClient, | ||||
public FileDragAndDropTarget, | public FileDragAndDropTarget, | ||||
private AsyncUpdater, | private AsyncUpdater, | ||||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
private ComboBoxListener | |||||
private Button::Listener, | |||||
private ComboBox::Listener | |||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -129,6 +129,9 @@ | |||||
#undef KeyPress | #undef KeyPress | ||||
#endif | #endif | ||||
#include <map> | |||||
#include <set> | |||||
//============================================================================== | //============================================================================== | ||||
namespace juce | namespace juce | ||||
{ | { | ||||
@@ -256,6 +259,11 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); | |||||
// these classes are C++11-only | // these classes are C++11-only | ||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | ||||
#include "layout/juce_FlexBox.cpp" | #include "layout/juce_FlexBox.cpp" | ||||
#include "layout/juce_GridItem.cpp" | |||||
#include "layout/juce_Grid.cpp" | |||||
#if JUCE_UNIT_TESTS | |||||
#include "layout/juce_GridUnitTests.cpp" | |||||
#endif | |||||
#endif | #endif | ||||
#if JUCE_IOS || JUCE_WINDOWS | #if JUCE_IOS || JUCE_WINDOWS | ||||
@@ -157,6 +157,7 @@ class KeyPressMappingSet; | |||||
class ApplicationCommandManagerListener; | class ApplicationCommandManagerListener; | ||||
class DrawableButton; | class DrawableButton; | ||||
class FlexBox; | class FlexBox; | ||||
class Grid; | |||||
#include "mouse/juce_MouseCursor.h" | #include "mouse/juce_MouseCursor.h" | ||||
#include "mouse/juce_MouseListener.h" | #include "mouse/juce_MouseListener.h" | ||||
@@ -294,6 +295,14 @@ class FlexBox; | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | ||||
#include "layout/juce_FlexItem.h" | #include "layout/juce_FlexItem.h" | ||||
#include "layout/juce_FlexBox.h" | #include "layout/juce_FlexBox.h" | ||||
#include "layout/juce_GridItem.h" | |||||
#include "layout/juce_Grid.h" | |||||
constexpr Grid::Px operator"" _px (long double px) { return Grid::Px { px }; } | |||||
constexpr Grid::Px operator"" _px (unsigned long long px) { return Grid::Px { px }; } | |||||
constexpr Grid::Fr operator"" _fr (unsigned long long fr) { return Grid::Fr { fr }; } | |||||
#endif | #endif | ||||
} | } |
@@ -0,0 +1,174 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
/** | |||||
Container that handles geometry for grid layouts (fixed columns and rows) using a set of declarative rules. | |||||
Implemented from the `CSS Grid Layout` specification as described at: | |||||
https://css-tricks.com/snippets/css/complete-guide-grid/ | |||||
@see GridItem | |||||
*/ | |||||
class JUCE_API Grid | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** A size in pixels */ | |||||
struct Px | |||||
{ | |||||
explicit Px (float p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ } | |||||
explicit Px (int p) : pixels (static_cast<long double>(p)) { /*sta (p >= 0.0f);*/ } | |||||
explicit constexpr Px (long double p) : pixels (p) {} | |||||
explicit constexpr Px (unsigned long long p) : pixels (static_cast<long double>(p)) {} | |||||
long double pixels; | |||||
}; | |||||
/** A fractional ratio integer */ | |||||
struct Fr | |||||
{ | |||||
explicit Fr (int f) : fraction (static_cast<unsigned long long> (f)) {} | |||||
explicit constexpr Fr (unsigned long long p) : fraction (p) {} | |||||
unsigned long long fraction; | |||||
}; | |||||
//============================================================================== | |||||
/** */ | |||||
struct TrackInfo | |||||
{ | |||||
/** Creates a track with auto dimension. */ | |||||
TrackInfo() noexcept; | |||||
/** */ | |||||
TrackInfo (Px sizeInPixels) noexcept; | |||||
/** */ | |||||
TrackInfo (Fr fractionOfFreeSpace) noexcept; | |||||
/** */ | |||||
TrackInfo (Px sizeInPixels, const juce::String& endLineNameToUse) noexcept; | |||||
/** */ | |||||
TrackInfo (Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept; | |||||
/** */ | |||||
TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels) noexcept; | |||||
/** */ | |||||
TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace) noexcept; | |||||
/** */ | |||||
TrackInfo (const juce::String& startLineNameToUse, Px sizeInPixels, const juce::String& endLineNameToUse) noexcept; | |||||
/** */ | |||||
TrackInfo (const juce::String& startLineNameToUse, Fr fractionOfFreeSpace, const juce::String& endLineNameToUse) noexcept; | |||||
private: | |||||
friend class Grid; | |||||
friend class GridItem; | |||||
float size = 0; // Either a fraction or an absolute size in pixels | |||||
bool isFraction = false; | |||||
bool hasKeyword = false; | |||||
juce::String startLineName, endLineName; | |||||
}; | |||||
//============================================================================== | |||||
/** */ | |||||
enum class JustifyItems : int { start = 0, end, center, stretch }; | |||||
/** */ | |||||
enum class AlignItems : int { start = 0, end, center, stretch }; | |||||
/** */ | |||||
enum class JustifyContent { start, end, center, stretch, spaceAround, spaceBetween, spaceEvenly }; | |||||
/** */ | |||||
enum class AlignContent { start, end, center, stretch, spaceAround, spaceBetween, spaceEvenly }; | |||||
/** */ | |||||
enum class AutoFlow { row, column, rowDense, columnDense }; | |||||
//============================================================================== | |||||
/** */ | |||||
Grid() noexcept; | |||||
/** Destructor */ | |||||
~Grid() noexcept; | |||||
//============================================================================== | |||||
/** */ | |||||
JustifyItems justifyItems = JustifyItems::stretch; | |||||
/** */ | |||||
AlignItems alignItems = AlignItems::stretch; | |||||
/** */ | |||||
JustifyContent justifyContent = JustifyContent::stretch; | |||||
/** */ | |||||
AlignContent alignContent = AlignContent::stretch; | |||||
/** */ | |||||
AutoFlow autoFlow = AutoFlow::row; | |||||
//============================================================================== | |||||
/** */ | |||||
juce::Array<TrackInfo> templateColumns; | |||||
/** */ | |||||
juce::Array<TrackInfo> templateRows; | |||||
/** Template areas */ | |||||
juce::StringArray templateAreas; | |||||
/** */ | |||||
TrackInfo autoRows; | |||||
/** */ | |||||
TrackInfo autoColumns; | |||||
/** */ | |||||
Px columnGap { 0 }; | |||||
/** */ | |||||
Px rowGap { 0 }; | |||||
/** */ | |||||
void setGap (Px sizeInPixels) noexcept { rowGap = columnGap = sizeInPixels; } | |||||
//============================================================================== | |||||
/** */ | |||||
juce::Array<GridItem> items; | |||||
//============================================================================== | |||||
/** */ | |||||
void performLayout (juce::Rectangle<int>); | |||||
//============================================================================== | |||||
/** */ | |||||
int getNumberOfColumns() const noexcept { return templateColumns.size(); } | |||||
/** */ | |||||
int getNumberOfRows() const noexcept { return templateRows.size(); } | |||||
private: | |||||
//============================================================================== | |||||
struct SizeCalculation; | |||||
struct PlacementHelpers; | |||||
struct AutoPlacement; | |||||
struct BoxAlignment; | |||||
}; |
@@ -0,0 +1,182 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
GridItem::Property::Property() noexcept : isAuto (true) | |||||
{ | |||||
} | |||||
GridItem::Property::Property (GridItem::Keyword keyword) noexcept : isAuto (keyword == GridItem::Keyword::autoValue) | |||||
{ | |||||
jassert (keyword == GridItem::Keyword::autoValue); | |||||
} | |||||
GridItem::Property::Property (const char* lineNameToUse) noexcept : GridItem::Property (juce::String (lineNameToUse)) | |||||
{ | |||||
} | |||||
GridItem::Property::Property (const juce::String& lineNameToUse) noexcept : name (lineNameToUse), number (1) | |||||
{ | |||||
} | |||||
GridItem::Property::Property (int numberToUse) noexcept : number (numberToUse) | |||||
{ | |||||
} | |||||
GridItem::Property::Property (int numberToUse, const juce::String& lineNameToUse) noexcept | |||||
: name (lineNameToUse), number (numberToUse) | |||||
{ | |||||
} | |||||
GridItem::Property::Property (Span spanToUse) noexcept | |||||
: name (spanToUse.name), number (spanToUse.number), isSpan (true) | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
GridItem::Margin::Margin() noexcept : left(), right(), top(), bottom() | |||||
{} | |||||
GridItem::Margin::Margin (int v) noexcept : GridItem::Margin::Margin (static_cast<float> (v)) | |||||
{} | |||||
GridItem::Margin::Margin (float v) noexcept : left (v), right (v), top (v), bottom (v) | |||||
{} | |||||
GridItem::Margin::Margin (float t, float r, float b, float l) noexcept : left (l), right (r), top (t), bottom (b) | |||||
{} | |||||
//============================================================================== | |||||
GridItem::GridItem() noexcept {} | |||||
GridItem::~GridItem() noexcept {} | |||||
GridItem::GridItem (juce::Component& componentToUse) noexcept : associatedComponent (&componentToUse) {} | |||||
GridItem::GridItem (juce::Component* componentToUse) noexcept : associatedComponent (componentToUse) {} | |||||
void GridItem::setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) | |||||
{ | |||||
column.start = columnStart; | |||||
column.end = columnEnd; | |||||
row.start = rowStart; | |||||
row.end = rowEnd; | |||||
} | |||||
void GridItem::setArea (Property rowStart, Property columnStart) | |||||
{ | |||||
column.start = columnStart; | |||||
row.start = rowStart; | |||||
} | |||||
void GridItem::setArea (const juce::String& areaName) | |||||
{ | |||||
area = areaName; | |||||
} | |||||
GridItem GridItem::withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.setArea (rowStart, columnStart, rowEnd, columnEnd); | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withArea (Property rowStart, Property columnStart) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.setArea (rowStart, columnStart); | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withArea (const juce::String& areaName) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.setArea (areaName); | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withRow (StartAndEndProperty newRow) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.row = newRow; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withColumn (StartAndEndProperty newColumn) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.column = newColumn; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withAlignSelf (AlignSelf newAlignSelf) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.alignSelf = newAlignSelf; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withJustifySelf (JustifySelf newJustifySelf) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.justifySelf = newJustifySelf; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withWidth (float newWidth) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.width = newWidth; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withHeight (float newHeight) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.height = newHeight; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withSize (float newWidth, float newHeight) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.width = newWidth; | |||||
gi.height = newHeight; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withMargin (Margin newHeight) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.margin = newHeight; | |||||
return gi; | |||||
} | |||||
GridItem GridItem::withOrder (int newOrder) const noexcept | |||||
{ | |||||
auto gi = *this; | |||||
gi.order = newOrder; | |||||
return gi; | |||||
} |
@@ -0,0 +1,223 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
/** | |||||
Defines an item in a Grid | |||||
@see Grid | |||||
*/ | |||||
class JUCE_API GridItem | |||||
{ | |||||
public: | |||||
enum class Keyword { autoValue }; | |||||
//============================================================================== | |||||
/** */ | |||||
struct Span | |||||
{ | |||||
explicit Span (int numberToUse) noexcept : number (numberToUse) | |||||
{ | |||||
/* Span must be at least one and positive */ | |||||
jassert (numberToUse > 0); | |||||
} | |||||
explicit Span (int numberToUse, const juce::String& nameToUse) : Span (numberToUse) | |||||
{ | |||||
/* Name must not be empty */ | |||||
jassert (nameToUse.isNotEmpty()); | |||||
name = nameToUse; | |||||
} | |||||
explicit Span (const juce::String& nameToUse) : name (nameToUse) | |||||
{ | |||||
/* Name must not be empty */ | |||||
jassert (nameToUse.isNotEmpty()); | |||||
} | |||||
int number = 1; | |||||
juce::String name; | |||||
}; | |||||
//============================================================================== | |||||
/** */ | |||||
struct Property | |||||
{ | |||||
/** */ | |||||
Property() noexcept; | |||||
/** */ | |||||
Property (Keyword keyword) noexcept; | |||||
/** */ | |||||
Property (const char* lineNameToUse) noexcept; | |||||
/** */ | |||||
Property (const juce::String& lineNameToUse) noexcept; | |||||
/** */ | |||||
Property (int numberToUse) noexcept; | |||||
/** */ | |||||
Property (int numberToUse, const juce::String& lineNameToUse) noexcept; | |||||
/** */ | |||||
Property (Span spanToUse) noexcept; | |||||
private: | |||||
bool hasSpan() const noexcept { return isSpan && ! isAuto; } | |||||
bool hasAbsolute() const noexcept { return ! (isSpan || isAuto); } | |||||
bool hasAuto() const noexcept { return isAuto; } | |||||
bool hasName() const noexcept { return name.isNotEmpty(); } | |||||
friend class Grid; | |||||
juce::String name; | |||||
int number = 1; /** Either an absolute line number or number of lines to span across. */ | |||||
bool isSpan = false; | |||||
bool isAuto = false; | |||||
}; | |||||
//============================================================================== | |||||
/** */ | |||||
struct StartAndEndProperty { Property start, end; }; | |||||
//============================================================================== | |||||
/** */ | |||||
enum class JustifySelf : int { start = 0, end, center, stretch, autoValue }; | |||||
/** */ | |||||
enum class AlignSelf : int { start = 0, end, center, stretch, autoValue }; | |||||
/** */ | |||||
GridItem() noexcept; | |||||
/** */ | |||||
GridItem (juce::Component& componentToUse) noexcept; | |||||
/** */ | |||||
GridItem (juce::Component* componentToUse) noexcept; | |||||
/** Destructor. */ | |||||
~GridItem() noexcept; | |||||
//============================================================================== | |||||
/** */ | |||||
juce::Component* associatedComponent = nullptr; | |||||
//============================================================================== | |||||
/** */ | |||||
int order = 0; | |||||
/** */ | |||||
JustifySelf justifySelf = JustifySelf::autoValue; | |||||
/** */ | |||||
AlignSelf alignSelf = AlignSelf::autoValue; | |||||
/** */ | |||||
StartAndEndProperty column = { Keyword::autoValue, Keyword::autoValue }; | |||||
/** */ | |||||
StartAndEndProperty row = { Keyword::autoValue, Keyword::autoValue }; | |||||
/** */ | |||||
juce::String area; | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
useDefaultValue = -2, /* TODO: useDefaultValue should be named useAuto */ | |||||
notAssigned = -1 | |||||
}; | |||||
/* TODO: move all of this into a common class that is shared with the FlexItem */ | |||||
float width = notAssigned; | |||||
float minWidth = 0; | |||||
float maxWidth = notAssigned; | |||||
float height = notAssigned; | |||||
float minHeight = 0; | |||||
float maxHeight = notAssigned; | |||||
struct Margin | |||||
{ | |||||
Margin() noexcept; | |||||
Margin (int size) noexcept; | |||||
Margin (float size) noexcept; | |||||
Margin (float top, float right, float bottom, float left) noexcept; /**< Creates a margin with these sizes. */ | |||||
float left; | |||||
float right; | |||||
float top; | |||||
float bottom; | |||||
}; | |||||
/** */ | |||||
Margin margin; | |||||
/** */ | |||||
juce::Rectangle<float> currentBounds; | |||||
/** Short-hand */ | |||||
void setArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd); | |||||
/** Short-hand, span of 1 by default */ | |||||
void setArea (Property rowStart, Property columnStart); | |||||
/** Short-hand */ | |||||
void setArea (const juce::String& areaName); | |||||
/** Short-hand */ | |||||
GridItem withArea (Property rowStart, Property columnStart, Property rowEnd, Property columnEnd) const noexcept; | |||||
/** Short-hand, span of 1 by default */ | |||||
GridItem withArea (Property rowStart, Property columnStart) const noexcept; | |||||
/** Short-hand */ | |||||
GridItem withArea (const juce::String& areaName) const noexcept; | |||||
/** */ | |||||
GridItem withRow (StartAndEndProperty row) const noexcept; | |||||
/** */ | |||||
GridItem withColumn (StartAndEndProperty column) const noexcept; | |||||
/** */ | |||||
GridItem withAlignSelf (AlignSelf newAlignSelf) const noexcept; | |||||
/** */ | |||||
GridItem withJustifySelf (JustifySelf newJustifySelf) const noexcept; | |||||
/** */ | |||||
GridItem withWidth (float newWidth) const noexcept; | |||||
/** */ | |||||
GridItem withHeight (float newHeight) const noexcept; | |||||
/** */ | |||||
GridItem withSize (float newWidth, float newHeight) const noexcept; | |||||
/** */ | |||||
GridItem withMargin (Margin newMargin) const noexcept; | |||||
/** Returns a copy of this object with a new order. */ | |||||
GridItem withOrder (int newOrder) const noexcept; | |||||
}; |
@@ -0,0 +1,257 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2017 - ROLI Ltd. | |||||
JUCE is an open source library subject to commercial or open-source | |||||
licensing. | |||||
By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||||
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||||
27th April 2017). | |||||
End User License Agreement: www.juce.com/juce-5-licence | |||||
Privacy Policy: www.juce.com/juce-5-privacy-policy | |||||
Or: You may also use this code under the terms of the GPL v3 (see | |||||
www.gnu.org/licenses). | |||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
DISCLAIMED. | |||||
============================================================================== | |||||
*/ | |||||
struct GridTests : public juce::UnitTest | |||||
{ | |||||
GridTests() : juce::UnitTest ("Grid class") {} | |||||
void runTest() override | |||||
{ | |||||
using Fr = juce::Grid::Fr; | |||||
using Tr = juce::Grid::TrackInfo; | |||||
using Rect = juce::Rectangle<float>; | |||||
using Grid = juce::Grid; | |||||
{ | |||||
Grid grid; | |||||
grid.templateColumns.add (Tr (1_fr)); | |||||
grid.templateRows.addArray ({ Tr (20_px), Tr (1_fr) }); | |||||
grid.items.addArray ({ GridItem().withArea (1, 1), | |||||
GridItem().withArea (2, 1) }); | |||||
grid.performLayout (juce::Rectangle<int> (200, 400)); | |||||
beginTest ("Layout calculation test: 1 column x 2 rows: no gap"); | |||||
expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 200.f, 20.0f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 200.f, 380.0f)); | |||||
grid.templateColumns.add (Tr (50_px)); | |||||
grid.templateRows.add (Tr (2_fr)); | |||||
grid.items.addArray ( { GridItem().withArea (1, 2), | |||||
GridItem().withArea (2, 2), | |||||
GridItem().withArea (3, 1), | |||||
GridItem().withArea (3, 2) }); | |||||
grid.performLayout (juce::Rectangle<int> (150, 170)); | |||||
beginTest ("Layout calculation test: 2 columns x 3 rows: no gap"); | |||||
expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 100.0f, 20.0f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.0f, 20.0f, 100.0f, 50.0f)); | |||||
expect (grid.items[2].currentBounds == Rect (100.0f, 0.0f, 50.0f, 20.0f)); | |||||
expect (grid.items[3].currentBounds == Rect (100.0f, 20.0f, 50.0f, 50.0f)); | |||||
expect (grid.items[4].currentBounds == Rect (0.0f, 70.0f, 100.0f, 100.0f)); | |||||
expect (grid.items[5].currentBounds == Rect (100.0f, 70.0f, 50.0f, 100.0f)); | |||||
grid.columnGap = 20_px; | |||||
grid.rowGap = 10_px; | |||||
grid.performLayout (juce::Rectangle<int> (200, 310)); | |||||
beginTest ("Layout calculation test: 2 columns x 3 rows: rowGap of 10 and columnGap of 20"); | |||||
expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 130.0f, 20.0f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.0f, 30.0f, 130.0f, 90.0f)); | |||||
expect (grid.items[2].currentBounds == Rect (150.0f, 0.0f, 50.0f, 20.0f)); | |||||
expect (grid.items[3].currentBounds == Rect (150.0f, 30.0f, 50.0f, 90.0f)); | |||||
expect (grid.items[4].currentBounds == Rect (0.0f, 130.0f, 130.0f, 180.0f)); | |||||
expect (grid.items[5].currentBounds == Rect (150.0f, 130.0f, 50.0f, 180.0f)); | |||||
} | |||||
{ | |||||
Grid grid; | |||||
grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") }); | |||||
grid.templateRows.addArray ({ Tr (1_fr), | |||||
Tr (20_px)}); | |||||
{ | |||||
beginTest ("Grid items placement tests: integer and custom ident, counting forward"); | |||||
GridItem i1, i2, i3, i4, i5; | |||||
i1.column = { 1, 4 }; | |||||
i1.row = { 1, 2 }; | |||||
i2.column = { 1, 3 }; | |||||
i2.row = { 1, 3 }; | |||||
i3.column = { "first", "in" }; | |||||
i3.row = { 2, 3 }; | |||||
i4.column = { "first", { 2, "in" } }; | |||||
i4.row = { 1, 2 }; | |||||
i5.column = { "first", "last" }; | |||||
i5.row = { 1, 2 }; | |||||
grid.items.addArray ({ i1, i2, i3, i4, i5 }); | |||||
grid.performLayout ({ 140, 100 }); | |||||
expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f)); | |||||
expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f)); | |||||
expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f)); | |||||
expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); | |||||
} | |||||
} | |||||
{ | |||||
Grid grid; | |||||
grid.templateColumns.addArray ({ Tr ("first", 20_px, "in"), Tr ("in", 1_fr, "in"), Tr (20_px, "last") }); | |||||
grid.templateRows.addArray ({ Tr (1_fr), | |||||
Tr (20_px)}); | |||||
beginTest ("Grid items placement tests: integer and custom ident, counting forward, reversed end and start"); | |||||
GridItem i1, i2, i3, i4, i5; | |||||
i1.column = { 4, 1 }; | |||||
i1.row = { 2, 1 }; | |||||
i2.column = { 3, 1 }; | |||||
i2.row = { 3, 1 }; | |||||
i3.column = { "in", "first" }; | |||||
i3.row = { 3, 2 }; | |||||
i4.column = { "first", { 2, "in" } }; | |||||
i4.row = { 1, 2 }; | |||||
i5.column = { "last", "first" }; | |||||
i5.row = { 1, 2 }; | |||||
grid.items.addArray ({ i1, i2, i3, i4, i5 }); | |||||
grid.performLayout ({ 140, 100 }); | |||||
expect (grid.items[0].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.0f, 0.0f, 120.0f, 100.0f)); | |||||
expect (grid.items[2].currentBounds == Rect (0.0f, 80.0f, 20.0f, 20.0f)); | |||||
expect (grid.items[3].currentBounds == Rect (0.0f, 0.0f, 120.0f, 80.0f)); | |||||
expect (grid.items[4].currentBounds == Rect (0.0f, 0.0f, 140.0f, 80.0f)); | |||||
} | |||||
{ | |||||
beginTest ("Grid items placement tests: areas"); | |||||
Grid grid; | |||||
grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (Fr (1_fr)), Tr (50_px) }; | |||||
grid.templateRows = { Tr (50_px), | |||||
Tr (1_fr), | |||||
Tr (50_px) }; | |||||
grid.templateAreas = { "header header header header", | |||||
"main main . sidebar", | |||||
"footer footer footer footer" }; | |||||
grid.items.addArray ({ GridItem().withArea ("header"), | |||||
GridItem().withArea ("main"), | |||||
GridItem().withArea ("sidebar"), | |||||
GridItem().withArea ("footer"), | |||||
}); | |||||
grid.performLayout ({ 300, 150 }); | |||||
expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 300.f, 50.f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); | |||||
expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 50.f, 50.f)); | |||||
expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 300.f, 50.f)); | |||||
} | |||||
{ | |||||
beginTest ("Grid implicit rows and columns: triggered by areas"); | |||||
Grid grid; | |||||
grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) }; | |||||
grid.templateRows = { Tr (50_px), | |||||
Tr (1_fr), | |||||
Tr (50_px) }; | |||||
grid.autoRows = Tr (30_px); | |||||
grid.autoColumns = Tr (30_px); | |||||
grid.templateAreas = { "header header header header header", | |||||
"main main . sidebar sidebar", | |||||
"footer footer footer footer footer", | |||||
"sub sub sub sub sub"}; | |||||
grid.items.addArray ({ GridItem().withArea ("header"), | |||||
GridItem().withArea ("main"), | |||||
GridItem().withArea ("sidebar"), | |||||
GridItem().withArea ("footer"), | |||||
GridItem().withArea ("sub"), | |||||
}); | |||||
grid.performLayout ({ 330, 180 }); | |||||
expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 330.f, 50.f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); | |||||
expect (grid.items[2].currentBounds == Rect (250.f, 50.f, 80.f, 50.f)); | |||||
expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 330.f, 50.f)); | |||||
expect (grid.items[4].currentBounds == Rect (0.f, 150.f, 330.f, 30.f)); | |||||
} | |||||
{ | |||||
beginTest ("Grid implicit rows and columns: triggered by areas"); | |||||
Grid grid; | |||||
grid.templateColumns = { Tr (50_px), Tr (100_px), Tr (1_fr), Tr (50_px) }; | |||||
grid.templateRows = { Tr (50_px), | |||||
Tr (1_fr), | |||||
Tr (50_px) }; | |||||
grid.autoRows = Tr (1_fr); | |||||
grid.autoColumns = Tr (1_fr); | |||||
grid.templateAreas = { "header header header header", | |||||
"main main . sidebar", | |||||
"footer footer footer footer" }; | |||||
grid.items.addArray ({ GridItem().withArea ("header"), | |||||
GridItem().withArea ("main"), | |||||
GridItem().withArea ("sidebar"), | |||||
GridItem().withArea ("footer"), | |||||
GridItem().withArea (4, 5, 6, 7) | |||||
}); | |||||
grid.performLayout ({ 350, 250 }); | |||||
expect (grid.items[0].currentBounds == Rect (0.f, 0.f, 250.f, 50.f)); | |||||
expect (grid.items[1].currentBounds == Rect (0.f, 50.f, 150.f, 50.f)); | |||||
expect (grid.items[2].currentBounds == Rect (200.f, 50.f, 50.f, 50.f)); | |||||
expect (grid.items[3].currentBounds == Rect (0.f, 100.f, 250.f, 50.f)); | |||||
expect (grid.items[4].currentBounds == Rect (250.f, 150.f, 100.f, 100.f)); | |||||
} | |||||
} | |||||
}; | |||||
static GridTests gridUnitTests; |
@@ -169,7 +169,7 @@ void TabBarButton::resized() | |||||
//============================================================================== | //============================================================================== | ||||
class TabbedButtonBar::BehindFrontTabComp : public Component, | class TabbedButtonBar::BehindFrontTabComp : public Component, | ||||
public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
public Button::Listener | |||||
{ | { | ||||
public: | public: | ||||
BehindFrontTabComp (TabbedButtonBar& tb) : owner (tb) | BehindFrontTabComp (TabbedButtonBar& tb) : owner (tb) | ||||
@@ -311,6 +311,7 @@ public: | |||||
virtual Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) = 0; | virtual Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) = 0; | ||||
virtual void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0; | virtual void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0; | ||||
virtual Font getTabButtonFont (TabBarButton&, float height) = 0; | |||||
virtual void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0; | virtual void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) = 0; | ||||
virtual void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) = 0; | virtual void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) = 0; | ||||
virtual void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) = 0; | virtual void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) = 0; | ||||
@@ -173,6 +173,11 @@ LookAndFeel_V2::LookAndFeel_V2() | |||||
BubbleComponent::backgroundColourId, 0xeeeeeebb, | BubbleComponent::backgroundColourId, 0xeeeeeebb, | ||||
BubbleComponent::outlineColourId, 0x77000000, | BubbleComponent::outlineColourId, 0x77000000, | ||||
TableHeaderComponent::textColourId, 0xff000000, | |||||
TableHeaderComponent::backgroundColourId, 0xffe8ebf9, | |||||
TableHeaderComponent::outlineColourId, 0x33000000, | |||||
TableHeaderComponent::highlightColourId, 0x8899aadd, | |||||
DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour, | DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour, | ||||
DirectoryContentsDisplayComponent::textColourId, 0xff000000, | DirectoryContentsDisplayComponent::textColourId, 0xff000000, | ||||
@@ -336,11 +341,9 @@ void LookAndFeel_V2::drawToggleButton (Graphics& g, ToggleButton& button, | |||||
if (! button.isEnabled()) | if (! button.isEnabled()) | ||||
g.setOpacity (0.5f); | g.setOpacity (0.5f); | ||||
const int textX = (int) tickWidth + 5; | |||||
g.drawFittedText (button.getButtonText(), | g.drawFittedText (button.getButtonText(), | ||||
textX, 0, | |||||
button.getWidth() - textX - 2, button.getHeight(), | |||||
button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 5) | |||||
.withTrimmedRight (2), | |||||
Justification::centredLeft, 10); | Justification::centredLeft, 10); | ||||
} | } | ||||
@@ -830,22 +833,22 @@ void LookAndFeel_V2::drawTreeviewPlusMinusBox (Graphics& g, const Rectangle<floa | |||||
const int x = ((int) area.getWidth() - boxSize) / 2 + (int) area.getX(); | const int x = ((int) area.getWidth() - boxSize) / 2 + (int) area.getX(); | ||||
const int y = ((int) area.getHeight() - boxSize) / 2 + (int) area.getY(); | const int y = ((int) area.getHeight() - boxSize) / 2 + (int) area.getY(); | ||||
const int w = boxSize; | |||||
const int h = boxSize; | |||||
Rectangle<float> boxArea ((float) x, (float) y, (float) boxSize, (float) boxSize); | |||||
g.setColour (Colour (0xe5ffffff)); | g.setColour (Colour (0xe5ffffff)); | ||||
g.fillRect (x, y, w, h); | |||||
g.fillRect (boxArea); | |||||
g.setColour (Colour (0x80000000)); | g.setColour (Colour (0x80000000)); | ||||
g.drawRect (x, y, w, h); | |||||
g.drawRect (boxArea); | |||||
const float size = boxSize / 2 + 1.0f; | const float size = boxSize / 2 + 1.0f; | ||||
const float centre = (float) (boxSize / 2); | const float centre = (float) (boxSize / 2); | ||||
g.fillRect (x + (w - size) * 0.5f, y + centre, size, 1.0f); | |||||
g.fillRect (x + (boxSize - size) * 0.5f, y + centre, size, 1.0f); | |||||
if (! isOpen) | if (! isOpen) | ||||
g.fillRect (x + centre, y + (h - size) * 0.5f, 1.0f, size); | |||||
g.fillRect (x + centre, y + (boxSize - size) * 0.5f, 1.0f, size); | |||||
} | } | ||||
bool LookAndFeel_V2::areLinesDrawnForTreeView (TreeView&) | bool LookAndFeel_V2::areLinesDrawnForTreeView (TreeView&) | ||||
@@ -1106,6 +1109,8 @@ void LookAndFeel_V2::preparePopupMenuWindow (Component&) {} | |||||
bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options&) { return true; } | bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options&) { return true; } | ||||
int LookAndFeel_V2::getPopupMenuBorderSize() { return 2; } | |||||
//============================================================================== | //============================================================================== | ||||
void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) | void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) | ||||
{ | { | ||||
@@ -2166,6 +2171,11 @@ void LookAndFeel_V2::fillTabButtonShape (TabBarButton& button, Graphics& g, cons | |||||
g.strokePath (path, PathStrokeType (isFrontTab ? 1.0f : 0.5f)); | g.strokePath (path, PathStrokeType (isFrontTab ? 1.0f : 0.5f)); | ||||
} | } | ||||
Font LookAndFeel_V2::getTabButtonFont (TabBarButton&, float height) | |||||
{ | |||||
return { height * 0.6f }; | |||||
} | |||||
void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) | void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) | ||||
{ | { | ||||
const Rectangle<float> area (button.getTextArea().toFloat()); | const Rectangle<float> area (button.getTextArea().toFloat()); | ||||
@@ -2176,7 +2186,7 @@ void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool | |||||
if (button.getTabbedButtonBar().isVertical()) | if (button.getTabbedButtonBar().isVertical()) | ||||
std::swap (length, depth); | std::swap (length, depth); | ||||
Font font (depth * 0.6f); | |||||
Font font (getTabButtonFont (button, depth)); | |||||
font.setUnderline (button.hasKeyboardFocus (false)); | font.setUnderline (button.hasKeyboardFocus (false)); | ||||
AffineTransform t; | AffineTransform t; | ||||
@@ -2323,26 +2333,33 @@ void LookAndFeel_V2::drawTableHeaderBackground (Graphics& g, TableHeaderComponen | |||||
Rectangle<int> area (header.getLocalBounds()); | Rectangle<int> area (header.getLocalBounds()); | ||||
area.removeFromTop (area.getHeight() / 2); | area.removeFromTop (area.getHeight() / 2); | ||||
g.setGradientFill (ColourGradient (Colour (0xffe8ebf9), 0.0f, (float) area.getY(), | |||||
Colour (0xfff6f8f9), 0.0f, (float) area.getBottom(), | |||||
auto backgroundColour = header.findColour (TableHeaderComponent::backgroundColourId); | |||||
g.setGradientFill (ColourGradient (backgroundColour, | |||||
0.0f, (float) area.getY(), | |||||
backgroundColour.withMultipliedSaturation (.5f), | |||||
0.0f, (float) area.getBottom(), | |||||
false)); | false)); | ||||
g.fillRect (area); | g.fillRect (area); | ||||
g.setColour (Colour (0x33000000)); | |||||
g.setColour (header.findColour (TableHeaderComponent::outlineColourId)); | |||||
g.fillRect (area.removeFromBottom (1)); | g.fillRect (area.removeFromBottom (1)); | ||||
for (int i = header.getNumColumns (true); --i >= 0;) | for (int i = header.getNumColumns (true); --i >= 0;) | ||||
g.fillRect (header.getColumnPosition (i).removeFromRight (1)); | g.fillRect (header.getColumnPosition (i).removeFromRight (1)); | ||||
} | } | ||||
void LookAndFeel_V2::drawTableHeaderColumn (Graphics& g, const String& columnName, int /*columnId*/, | |||||
void LookAndFeel_V2::drawTableHeaderColumn (Graphics& g, TableHeaderComponent& header, | |||||
const String& columnName, int /*columnId*/, | |||||
int width, int height, bool isMouseOver, bool isMouseDown, | int width, int height, bool isMouseOver, bool isMouseDown, | ||||
int columnFlags) | int columnFlags) | ||||
{ | { | ||||
auto highlightColour = header.findColour (TableHeaderComponent::highlightColourId); | |||||
if (isMouseDown) | if (isMouseDown) | ||||
g.fillAll (Colour (0x8899aadd)); | |||||
g.fillAll (highlightColour); | |||||
else if (isMouseOver) | else if (isMouseOver) | ||||
g.fillAll (Colour (0x5599aadd)); | |||||
g.fillAll (highlightColour.withMultipliedAlpha (0.625f)); | |||||
Rectangle<int> area (width, height); | Rectangle<int> area (width, height); | ||||
area.reduce (4, 0); | area.reduce (4, 0); | ||||
@@ -2358,7 +2375,7 @@ void LookAndFeel_V2::drawTableHeaderColumn (Graphics& g, const String& columnNam | |||||
g.fillPath (sortArrow, sortArrow.getTransformToScaleToFit (area.removeFromRight (height / 2).reduced (2).toFloat(), true)); | g.fillPath (sortArrow, sortArrow.getTransformToScaleToFit (area.removeFromRight (height / 2).reduced (2).toFloat(), true)); | ||||
} | } | ||||
g.setColour (Colours::black); | |||||
g.setColour (header.findColour (TableHeaderComponent::textColourId)); | |||||
g.setFont (Font (height * 0.5f, Font::bold)); | g.setFont (Font (height * 0.5f, Font::bold)); | ||||
g.drawFittedText (columnName, area, Justification::centredLeft, 1); | g.drawFittedText (columnName, area, Justification::centredLeft, 1); | ||||
} | } | ||||
@@ -182,6 +182,8 @@ public: | |||||
bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) override; | bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) override; | ||||
int getPopupMenuBorderSize() override; | |||||
//============================================================================== | //============================================================================== | ||||
void drawComboBox (Graphics&, int width, int height, bool isButtonDown, | void drawComboBox (Graphics&, int width, int height, bool isButtonDown, | ||||
int buttonX, int buttonY, int buttonW, int buttonH, | int buttonX, int buttonY, int buttonW, int buttonH, | ||||
@@ -272,6 +274,7 @@ public: | |||||
Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) override; | Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) override; | ||||
void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) override; | void drawTabButton (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) override; | ||||
Font getTabButtonFont (TabBarButton&, float height) override; | |||||
void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) override; | void drawTabButtonText (TabBarButton&, Graphics&, bool isMouseOver, bool isMouseDown) override; | ||||
void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) override; | void drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) override; | ||||
void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) override; | void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) override; | ||||
@@ -289,9 +292,9 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
void drawTableHeaderBackground (Graphics&, TableHeaderComponent&) override; | void drawTableHeaderBackground (Graphics&, TableHeaderComponent&) override; | ||||
void drawTableHeaderColumn (Graphics&, const String& columnName, int columnId, | |||||
int width, int height, bool isMouseOver, bool isMouseDown, | |||||
int columnFlags) override; | |||||
void drawTableHeaderColumn (Graphics&, TableHeaderComponent&, const String& columnName, | |||||
int columnId, int width, int height, bool isMouseOver, | |||||
bool isMouseDown, int columnFlags) override; | |||||
//============================================================================== | //============================================================================== | ||||
void paintToolbarBackground (Graphics&, int width, int height, Toolbar&) override; | void paintToolbarBackground (Graphics&, int width, int height, Toolbar&) override; | ||||
@@ -39,6 +39,8 @@ LookAndFeel_V3::LookAndFeel_V3() | |||||
setColour (Slider::thumbColourId, Colour (0xffddddff)); | setColour (Slider::thumbColourId, Colour (0xffddddff)); | ||||
setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); | setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); | ||||
setColour (ScrollBar::thumbColourId, Colour::greyLevel (0.8f).contrasting().withAlpha (0.13f)); | setColour (ScrollBar::thumbColourId, Colour::greyLevel (0.8f).contrasting().withAlpha (0.13f)); | ||||
setColour (TableHeaderComponent::backgroundColourId, Colours::white.withAlpha (0.6f)); | |||||
setColour (TableHeaderComponent::outlineColourId, Colours::black.withAlpha (0.5f)); | |||||
} | } | ||||
LookAndFeel_V3::~LookAndFeel_V3() {} | LookAndFeel_V3::~LookAndFeel_V3() {} | ||||
@@ -153,14 +155,15 @@ void LookAndFeel_V3::drawButtonBackground (Graphics& g, Button& button, const Co | |||||
void LookAndFeel_V3::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header) | void LookAndFeel_V3::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header) | ||||
{ | { | ||||
Rectangle<int> r (header.getLocalBounds()); | Rectangle<int> r (header.getLocalBounds()); | ||||
auto outlineColour = header.findColour (TableHeaderComponent::outlineColourId); | |||||
g.setColour (Colours::black.withAlpha (0.5f)); | |||||
g.setColour (outlineColour); | |||||
g.fillRect (r.removeFromBottom (1)); | g.fillRect (r.removeFromBottom (1)); | ||||
g.setColour (Colours::white.withAlpha (0.6f)); | |||||
g.setColour (header.findColour (TableHeaderComponent::backgroundColourId)); | |||||
g.fillRect (r); | g.fillRect (r); | ||||
g.setColour (Colours::black.withAlpha (0.5f)); | |||||
g.setColour (outlineColour); | |||||
for (int i = header.getNumColumns (true); --i >= 0;) | for (int i = header.getNumColumns (true); --i >= 0;) | ||||
g.fillRect (header.getColumnPosition (i).removeFromRight (1)); | g.fillRect (header.getColumnPosition (i).removeFromRight (1)); | ||||
@@ -333,11 +333,9 @@ void LookAndFeel_V4::drawToggleButton (Graphics& g, ToggleButton& button, | |||||
if (! button.isEnabled()) | if (! button.isEnabled()) | ||||
g.setOpacity (0.5f); | g.setOpacity (0.5f); | ||||
const auto textX = roundToInt (tickWidth) + 10; | |||||
g.drawFittedText (button.getButtonText(), | g.drawFittedText (button.getButtonText(), | ||||
textX, 0, | |||||
button.getWidth() - textX - 2, button.getHeight(), | |||||
button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 10) | |||||
.withTrimmedRight (2), | |||||
Justification::centredLeft, 10); | Justification::centredLeft, 10); | ||||
} | } | ||||
@@ -27,7 +27,6 @@ | |||||
namespace PopupMenuSettings | namespace PopupMenuSettings | ||||
{ | { | ||||
const int scrollZone = 24; | const int scrollZone = 24; | ||||
const int borderSize = 2; | |||||
const int dismissCommandId = 0x6287345f; | const int dismissCommandId = 0x6287345f; | ||||
static bool menuWasHiddenBecauseOfAppChange = false; | static bool menuWasHiddenBecauseOfAppChange = false; | ||||
@@ -88,7 +87,7 @@ struct ItemComponent : public Component | |||||
int itemW = 80; | int itemW = 80; | ||||
int itemH = 16; | int itemH = 16; | ||||
getIdealSize (itemW, itemH, standardItemHeight); | getIdealSize (itemW, itemH, standardItemHeight); | ||||
setSize (itemW, jlimit (2, 600, itemH)); | |||||
setSize (itemW, jlimit (1, 600, itemH)); | |||||
addMouseListener (&parent, false); | addMouseListener (&parent, false); | ||||
} | } | ||||
@@ -290,7 +289,8 @@ public: | |||||
auto& lf = getLookAndFeel(); | auto& lf = getLookAndFeel(); | ||||
if (parentComponent != nullptr) | if (parentComponent != nullptr) | ||||
lf.drawResizableFrame (g, getWidth(), getHeight(), BorderSize<int> (PopupMenuSettings::borderSize)); | |||||
lf.drawResizableFrame (g, getWidth(), getHeight(), | |||||
BorderSize<int> (getLookAndFeel().getPopupMenuBorderSize())); | |||||
if (canScroll()) | if (canScroll()) | ||||
{ | { | ||||
@@ -598,7 +598,7 @@ public: | |||||
return parentComponent->getLocalArea (nullptr, | return parentComponent->getLocalArea (nullptr, | ||||
parentComponent->getScreenBounds() | parentComponent->getScreenBounds() | ||||
.reduced (PopupMenuSettings::borderSize) | |||||
.reduced (getLookAndFeel().getPopupMenuBorderSize()) | |||||
.getIntersection (parentArea)); | .getIntersection (parentArea)); | ||||
} | } | ||||
@@ -666,6 +666,9 @@ public: | |||||
else | else | ||||
x = jmax (parentArea.getX() + 4, target.getX() - widthToUse); | x = jmax (parentArea.getX() + 4, target.getX() - widthToUse); | ||||
if (getLookAndFeel().getPopupMenuBorderSize() == 0) // workaround for dismissing the window on mouse up when border size is 0 | |||||
x += tendTowardsRight ? 1 : -1; | |||||
y = target.getY(); | y = target.getY(); | ||||
if (target.getCentreY() > parentArea.getCentreY()) | if (target.getCentreY() > parentArea.getCentreY()) | ||||
y = jmax (parentArea.getY(), target.getBottom() - heightToUse); | y = jmax (parentArea.getY(), target.getBottom() - heightToUse); | ||||
@@ -711,7 +714,7 @@ public: | |||||
needsToScroll = contentHeight > actualH; | needsToScroll = contentHeight > actualH; | ||||
width = updateYPositions(); | width = updateYPositions(); | ||||
height = actualH + PopupMenuSettings::borderSize * 2; | |||||
height = actualH + getLookAndFeel().getPopupMenuBorderSize() * 2; | |||||
} | } | ||||
int workOutBestSize (const int maxMenuW) | int workOutBestSize (const int maxMenuW) | ||||
@@ -733,7 +736,7 @@ public: | |||||
colH += items.getUnchecked (childNum + i)->getHeight(); | colH += items.getUnchecked (childNum + i)->getHeight(); | ||||
} | } | ||||
colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + PopupMenuSettings::borderSize * 2); | |||||
colW = jmin (maxMenuW / jmax (1, numColumns - 2), colW + getLookAndFeel().getPopupMenuBorderSize() * 2); | |||||
columnWidths.set (col, colW); | columnWidths.set (col, colW); | ||||
totalW += colW; | totalW += colW; | ||||
@@ -832,7 +835,7 @@ public: | |||||
childYOffset = jmax (childYOffset, 0); | childYOffset = jmax (childYOffset, 0); | ||||
else if (delta > 0) | else if (delta > 0) | ||||
childYOffset = jmin (childYOffset, | childYOffset = jmin (childYOffset, | ||||
contentHeight - windowPos.getHeight() + PopupMenuSettings::borderSize); | |||||
contentHeight - windowPos.getHeight() + getLookAndFeel().getPopupMenuBorderSize()); | |||||
updateYPositions(); | updateYPositions(); | ||||
} | } | ||||
@@ -857,7 +860,7 @@ public: | |||||
const int colW = columnWidths [col]; | const int colW = columnWidths [col]; | ||||
int y = PopupMenuSettings::borderSize - (childYOffset + (getY() - windowPos.getY())); | |||||
int y = getLookAndFeel().getPopupMenuBorderSize() - (childYOffset + (getY() - windowPos.getY())); | |||||
for (int i = 0; i < numChildren; ++i) | for (int i = 0; i < numChildren; ++i) | ||||
{ | { | ||||
@@ -280,6 +280,8 @@ public: | |||||
This will add a user-defined component to use as a menu item. The component | This will add a user-defined component to use as a menu item. The component | ||||
passed in will be deleted by this menu when it's no longer needed. | passed in will be deleted by this menu when it's no longer needed. | ||||
Note that native macOS menus do not support custom components. | |||||
@see CustomComponent | @see CustomComponent | ||||
*/ | */ | ||||
void addCustomItem (int itemResultID, | void addCustomItem (int itemResultID, | ||||
@@ -296,6 +298,8 @@ public: | |||||
detection of a mouse-click on your component, and use that to trigger the | detection of a mouse-click on your component, and use that to trigger the | ||||
menu ID specified in itemResultID. If this is false, the menu item can't | menu ID specified in itemResultID. If this is false, the menu item can't | ||||
be triggered, so itemResultID is not used. | be triggered, so itemResultID is not used. | ||||
Note that native macOS menus do support custom components. | |||||
*/ | */ | ||||
void addCustomItem (int itemResultID, | void addCustomItem (int itemResultID, | ||||
Component* customComponent, | Component* customComponent, | ||||
@@ -723,6 +727,8 @@ public: | |||||
/** Return true if you want your popup menus to scale with the target component's AffineTransform | /** Return true if you want your popup menus to scale with the target component's AffineTransform | ||||
or scale factor */ | or scale factor */ | ||||
virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0; | virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0; | ||||
virtual int getPopupMenuBorderSize() = 0; | |||||
}; | }; | ||||
private: | private: | ||||
@@ -84,13 +84,17 @@ public: | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
Point<float> getScreenPosition() const | |||||
Point<float> getScreenPosition() const noexcept | |||||
{ | { | ||||
// This needs to return the live position if possible, but it mustn't update the lastScreenPos | // This needs to return the live position if possible, but it mustn't update the lastScreenPos | ||||
// value, because that can cause continuity problems. | // value, because that can cause continuity problems. | ||||
return ScalingHelpers::unscaledScreenPosToScaled | |||||
(unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition() | |||||
: lastScreenPos)); | |||||
return ScalingHelpers::unscaledScreenPosToScaled (getRawScreenPosition()); | |||||
} | |||||
Point<float> getRawScreenPosition() const noexcept | |||||
{ | |||||
return unboundedMouseOffset + (inputType != MouseInputSource::InputSourceType::touch ? MouseInputSource::getCurrentRawMousePosition() | |||||
: lastScreenPos); | |||||
} | } | ||||
void setScreenPosition (Point<float> p) | void setScreenPosition (Point<float> p) | ||||
@@ -578,6 +582,7 @@ bool MouseInputSource::hasMouseWheel() const noexcept | |||||
int MouseInputSource::getIndex() const noexcept { return pimpl->index; } | int MouseInputSource::getIndex() const noexcept { return pimpl->index; } | ||||
bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } | bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } | ||||
Point<float> MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); } | Point<float> MouseInputSource::getScreenPosition() const noexcept { return pimpl->getScreenPosition(); } | ||||
Point<float> MouseInputSource::getRawScreenPosition() const noexcept { return pimpl->getRawScreenPosition(); } | |||||
ModifierKeys MouseInputSource::getCurrentModifiers() const noexcept { return pimpl->getCurrentModifiers(); } | ModifierKeys MouseInputSource::getCurrentModifiers() const noexcept { return pimpl->getCurrentModifiers(); } | ||||
float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } | float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } | ||||
bool MouseInputSource::isPressureValid() const noexcept { return pimpl->isPressureValid(); } | bool MouseInputSource::isPressureValid() const noexcept { return pimpl->isPressureValid(); } | ||||
@@ -730,7 +735,7 @@ struct MouseInputSource::SourceList : public Timer | |||||
// because on some OSes the queue can get overloaded with messages so that mouse-events don't get through.. | // because on some OSes the queue can get overloaded with messages so that mouse-events don't get through.. | ||||
if (s->isDragging() && ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) | if (s->isDragging() && ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) | ||||
{ | { | ||||
s->lastScreenPos = s->getScreenPosition(); | |||||
s->lastScreenPos = s->getRawScreenPosition(); | |||||
s->triggerFakeMove(); | s->triggerFakeMove(); | ||||
anyDragging = true; | anyDragging = true; | ||||
} | } | ||||
@@ -105,6 +105,9 @@ public: | |||||
/** Returns the last-known screen position of this source. */ | /** Returns the last-known screen position of this source. */ | ||||
Point<float> getScreenPosition() const noexcept; | Point<float> getScreenPosition() const noexcept; | ||||
/** Returns the last-known screen position of this source without any scaling applied. */ | |||||
Point<float> getRawScreenPosition() const noexcept; | |||||
/** Returns a set of modifiers that indicate which buttons are currently | /** Returns a set of modifiers that indicate which buttons are currently | ||||
held down on this device. | held down on this device. | ||||
*/ | */ | ||||
@@ -31,6 +31,11 @@ extern juce::JUCEApplicationBase* juce_CreateApplication(); // (from START_JUCE_ | |||||
namespace juce | namespace juce | ||||
{ | { | ||||
//============================================================================== | |||||
#if JUCE_MODULE_AVAILABLE_juce_product_unlocking | |||||
extern void juce_inAppPurchaseCompleted (void*); | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, | JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, launchApp, void, (JNIEnv* env, jobject activity, | ||||
jstring appFile, jstring appDataDir)) | jstring appFile, jstring appDataDir)) | ||||
@@ -83,6 +88,18 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* env, | |||||
android.shutdown (env); | android.shutdown (env); | ||||
} | } | ||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, appActivityResult, void, (JNIEnv* env, jobject, jint requestCode, jint /*resultCode*/, jobject intentData)) | |||||
{ | |||||
setEnv (env); | |||||
#if JUCE_MODULE_AVAILABLE_juce_product_unlocking | |||||
if (requestCode == 1001) | |||||
juce_inAppPurchaseCompleted (intentData); | |||||
#else | |||||
ignoreUnused (intentData, requestCode); | |||||
#endif | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | ||||
METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \ | METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \ | ||||
@@ -248,20 +265,20 @@ public: | |||||
setFullScreen (true); | setFullScreen (true); | ||||
} | } | ||||
Point<int> getScreenPosition() const | |||||
Point<float> getScreenPosition() const | |||||
{ | { | ||||
return Point<int> (view.callIntMethod (ComponentPeerView.getLeft), | |||||
view.callIntMethod (ComponentPeerView.getTop)) / scale; | |||||
return Point<float> (view.callIntMethod (ComponentPeerView.getLeft), | |||||
view.callIntMethod (ComponentPeerView.getTop)) / scale; | |||||
} | } | ||||
Point<float> localToGlobal (Point<float> relativePosition) override | Point<float> localToGlobal (Point<float> relativePosition) override | ||||
{ | { | ||||
return relativePosition + getScreenPosition().toFloat(); | |||||
return relativePosition + getScreenPosition(); | |||||
} | } | ||||
Point<float> globalToLocal (Point<float> screenPosition) override | Point<float> globalToLocal (Point<float> screenPosition) override | ||||
{ | { | ||||
return screenPosition - getScreenPosition().toFloat(); | |||||
return screenPosition - getScreenPosition(); | |||||
} | } | ||||
void setMinimised (bool /*shouldBeMinimised*/) override | void setMinimised (bool /*shouldBeMinimised*/) override | ||||
@@ -390,7 +407,7 @@ public: | |||||
void handleMouseDownCallback (int index, Point<float> sysPos, int64 time) | void handleMouseDownCallback (int index, Point<float> sysPos, int64 time) | ||||
{ | { | ||||
Point<float> pos = sysPos / scale; | Point<float> pos = sysPos / scale; | ||||
lastMousePos = pos; | |||||
lastMousePos = localToGlobal (pos); | |||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | // this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before. | ||||
handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons(), | handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons(), | ||||
@@ -403,7 +420,7 @@ public: | |||||
void handleMouseDragCallback (int index, Point<float> pos, int64 time) | void handleMouseDragCallback (int index, Point<float> pos, int64 time) | ||||
{ | { | ||||
pos /= scale; | pos /= scale; | ||||
lastMousePos = pos; | |||||
lastMousePos = localToGlobal (pos); | |||||
jassert (index < 64); | jassert (index < 64); | ||||
touchesDown = (touchesDown | (1 << (index & 63))); | touchesDown = (touchesDown | (1 << (index & 63))); | ||||
@@ -415,7 +432,7 @@ public: | |||||
void handleMouseUpCallback (int index, Point<float> pos, int64 time) | void handleMouseUpCallback (int index, Point<float> pos, int64 time) | ||||
{ | { | ||||
pos /= scale; | pos /= scale; | ||||
lastMousePos = pos; | |||||
lastMousePos = localToGlobal (pos); | |||||
jassert (index < 64); | jassert (index < 64); | ||||
touchesDown = (touchesDown & ~(1 << (index & 63))); | touchesDown = (touchesDown & ~(1 << (index & 63))); | ||||
@@ -678,6 +695,18 @@ ComponentPeer* Component::createNewPeer (int styleFlags, void*) | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (getRotation, "getRotation", "()I") | |||||
DECLARE_JNI_CLASS (Display, "android/view/Display"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) \ | |||||
METHOD (getDefaultDisplay, "getDefaultDisplay", "()Landroid/view/Display;") | |||||
DECLARE_JNI_CLASS (WindowManager, "android/view/WindowManager"); | |||||
#undef JNI_CLASS_MEMBERS | |||||
bool Desktop::canUseSemiTransparentWindows() noexcept | bool Desktop::canUseSemiTransparentWindows() noexcept | ||||
{ | { | ||||
return true; | return true; | ||||
@@ -690,7 +719,37 @@ double Desktop::getDefaultMasterScale() | |||||
Desktop::DisplayOrientation Desktop::getCurrentOrientation() const | Desktop::DisplayOrientation Desktop::getCurrentOrientation() const | ||||
{ | { | ||||
// TODO | |||||
enum | |||||
{ | |||||
ROTATION_0 = 0, | |||||
ROTATION_90 = 1, | |||||
ROTATION_180 = 2, | |||||
ROTATION_270 = 3 | |||||
}; | |||||
JNIEnv* env = getEnv(); | |||||
LocalRef<jstring> windowServiceString (javaString ("window")); | |||||
LocalRef<jobject> windowManager = LocalRef<jobject> (env->CallObjectMethod (android.activity, JuceAppActivity.getSystemService, windowServiceString.get())); | |||||
if (windowManager.get() != 0) | |||||
{ | |||||
LocalRef<jobject> display = LocalRef<jobject> (env->CallObjectMethod (windowManager, WindowManager.getDefaultDisplay)); | |||||
if (display.get() != 0) | |||||
{ | |||||
int rotation = env->CallIntMethod (display, Display.getRotation); | |||||
switch (rotation) | |||||
{ | |||||
case ROTATION_0: return upright; | |||||
case ROTATION_90: return rotatedAntiClockwise; | |||||
case ROTATION_180: return upsideDown; | |||||
case ROTATION_270: return rotatedClockwise; | |||||
} | |||||
} | |||||
} | |||||
jassertfalse; | |||||
return upright; | return upright; | ||||
} | } | ||||
@@ -100,9 +100,9 @@ public: | |||||
{ | { | ||||
NSMenu* superMenu = [menu supermenu]; | NSMenu* superMenu = [menu supermenu]; | ||||
auto menuNames = currentModel->getMenuBarNames(); | auto menuNames = currentModel->getMenuBarNames(); | ||||
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu]; | |||||
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1; | |||||
[menu removeAllItems]; | [menu removeAllItems]; | ||||
auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu - 1, menuNames[indexOfMenu - 1]); | |||||
auto updatedPopup = currentModel->getMenuForIndex (indexOfMenu, menuNames[indexOfMenu]); | |||||
for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();) | for (PopupMenu::MenuItemIterator iter (updatedPopup); iter.next();) | ||||
addMenuItem (iter, menu, 1, indexOfMenu); | addMenuItem (iter, menu, 1, indexOfMenu); | ||||
@@ -57,6 +57,46 @@ static bool shouldDeactivateTitleBar = true; | |||||
extern void* getUser32Function (const char*); | extern void* getUser32Function (const char*); | ||||
//============================================================================== | //============================================================================== | ||||
#ifndef WM_TOUCH | |||||
enum | |||||
{ | |||||
WM_TOUCH = 0x0240, | |||||
TOUCHEVENTF_MOVE = 0x0001, | |||||
TOUCHEVENTF_DOWN = 0x0002, | |||||
TOUCHEVENTF_UP = 0x0004 | |||||
}; | |||||
typedef HANDLE HTOUCHINPUT; | |||||
typedef HANDLE HGESTUREINFO; | |||||
struct TOUCHINPUT | |||||
{ | |||||
LONG x; | |||||
LONG y; | |||||
HANDLE hSource; | |||||
DWORD dwID; | |||||
DWORD dwFlags; | |||||
DWORD dwMask; | |||||
DWORD dwTime; | |||||
ULONG_PTR dwExtraInfo; | |||||
DWORD cxContact; | |||||
DWORD cyContact; | |||||
}; | |||||
struct GESTUREINFO | |||||
{ | |||||
UINT cbSize; | |||||
DWORD dwFlags; | |||||
DWORD dwID; | |||||
HWND hwndTarget; | |||||
POINTS ptsLocation; | |||||
DWORD dwInstanceID; | |||||
DWORD dwSequenceID; | |||||
ULONGLONG ullArguments; | |||||
UINT cbExtraArgs; | |||||
}; | |||||
#endif | |||||
#ifndef WM_NCPOINTERUPDATE | #ifndef WM_NCPOINTERUPDATE | ||||
enum | enum | ||||
{ | { | ||||
@@ -37,7 +37,7 @@ | |||||
@see PropertyComponent | @see PropertyComponent | ||||
*/ | */ | ||||
class JUCE_API BooleanPropertyComponent : public PropertyComponent, | class JUCE_API BooleanPropertyComponent : public PropertyComponent, | ||||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
private Button::Listener | |||||
{ | { | ||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||