@@ -25,7 +25,7 @@ | |||
#if JUCE_INTEL | |||
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; | |||
#else | |||
#define JUCE_SNAP_TO_ZERO(n) | |||
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) | |||
#endif | |||
//============================================================================== | |||
@@ -36,6 +36,15 @@ struct CoreAudioLayouts | |||
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. | |||
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. */ | |||
static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout) | |||
{ | |||
switch (layout.mChannelLayoutTag) | |||
switch (layout.mChannelLayoutTag & 0xffff0000) | |||
{ | |||
case kAudioChannelLayoutTag_UseChannelBitmap: | |||
return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes(); | |||
@@ -94,30 +103,12 @@ struct CoreAudioLayouts | |||
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) | |||
{ | |||
// 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; | |||
for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) | |||
@@ -139,6 +130,14 @@ private: | |||
return speakers; | |||
} | |||
private: | |||
//============================================================================== | |||
struct LayoutTagSpeakerList | |||
{ | |||
AudioChannelLayoutTag tag; | |||
AudioChannelSet::ChannelType channelTypes[16]; | |||
}; | |||
static Array<AudioChannelLayoutTag> createKnownCoreAudioTags() | |||
{ | |||
Array<AudioChannelLayoutTag> tags; | |||
@@ -24,20 +24,8 @@ 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 | |||
{ | |||
@@ -83,11 +71,6 @@ void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer, | |||
//============================================================================== | |||
Synthesiser::Synthesiser() | |||
: sampleRate (0), | |||
lastNoteOnCounter (0), | |||
minimumSubBlockSize (32), | |||
subBlockSubdivisionIsStrict (false), | |||
shouldStealNotes (true) | |||
{ | |||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||
lastPitchWheelValues[i] = 0x2000; | |||
@@ -159,13 +142,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||
if (sampleRate != newRate) | |||
{ | |||
const ScopedLock sl (lock); | |||
allNotesOff (0, false); | |||
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 | |||
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) | |||
{ | |||
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) | |||
{ | |||
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) | |||
@@ -298,23 +273,15 @@ void Synthesiser::noteOn (const int midiChannel, | |||
{ | |||
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 | |||
// 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); | |||
} | |||
startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | |||
sound, midiChannel, midiNoteNumber, velocity); | |||
@@ -337,7 +304,7 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
voice->currentPlayingMidiChannel = midiChannel; | |||
voice->noteOnTime = ++lastNoteOnCounter; | |||
voice->currentlyPlayingSound = sound; | |||
voice->keyIsDown = true; | |||
voice->setKeyDown (true); | |||
voice->sostenutoPedalDown = false; | |||
voice->sustainPedalDown = sustainPedalsDown[midiChannel]; | |||
@@ -363,10 +330,8 @@ void Synthesiser::noteOff (const int midiChannel, | |||
{ | |||
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 | |||
&& voice->isPlayingChannel (midiChannel)) | |||
{ | |||
@@ -377,7 +342,7 @@ void Synthesiser::noteOff (const int midiChannel, | |||
{ | |||
jassert (! voice->keyIsDown || voice->sustainPedalDown == sustainPedalsDown [midiChannel]); | |||
voice->keyIsDown = false; | |||
voice->setKeyDown (false); | |||
if (! (voice->sustainPedalDown || voice->sostenutoPedalDown)) | |||
stopVoice (voice, velocity, allowTailOff); | |||
@@ -391,13 +356,9 @@ void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||
{ | |||
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)) | |||
voice->stopNote (1.0f, allowTailOff); | |||
} | |||
sustainPedalsDown.clear(); | |||
} | |||
@@ -406,13 +367,9 @@ void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) | |||
{ | |||
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)) | |||
voice->pitchWheelMoved (wheelValue); | |||
} | |||
} | |||
void Synthesiser::handleController (const int midiChannel, | |||
@@ -429,40 +386,28 @@ void Synthesiser::handleController (const int midiChannel, | |||
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)) | |||
voice->controllerMoved (controllerNumber, controllerValue); | |||
} | |||
} | |||
void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) | |||
{ | |||
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 | |||
&& (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) | |||
voice->aftertouchChanged (aftertouchValue); | |||
} | |||
} | |||
void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) | |||
{ | |||
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)) | |||
voice->channelPressureChanged (channelPressureValue); | |||
} | |||
} | |||
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
@@ -474,25 +419,19 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
{ | |||
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()) | |||
voice->sustainPedalDown = true; | |||
} | |||
} | |||
else | |||
{ | |||
for (int i = voices.size(); --i >= 0;) | |||
for (auto* voice : voices) | |||
{ | |||
SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (voice->isPlayingChannel (midiChannel)) | |||
{ | |||
voice->sustainPedalDown = false; | |||
if (! voice->isKeyDown()) | |||
if (! (voice->isKeyDown() || voice->isSostenutoPedalDown())) | |||
stopVoice (voice, 1.0f, true); | |||
} | |||
} | |||
@@ -506,10 +445,8 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
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 (isDown) | |||
@@ -539,13 +476,9 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
{ | |||
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)) | |||
return voice; | |||
} | |||
if (stealIfNoneAvailable) | |||
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. | |||
// 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) | |||
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; | |||
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)) | |||
{ | |||
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 | |||
{ | |||
const int note = voice->getCurrentlyPlayingNote(); | |||
auto note = voice->getCurrentlyPlayingNote(); | |||
if (low == nullptr || note < low->getCurrentlyPlayingNote()) | |||
low = voice; | |||
@@ -607,43 +538,25 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
if (top == low) | |||
top = nullptr; | |||
const int numUsableVoices = usableVoices.size(); | |||
// 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) | |||
return voice; | |||
} | |||
// 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()) | |||
return voice; | |||
} | |||
// 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()) | |||
return voice; | |||
} | |||
// 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) | |||
return voice; | |||
} | |||
// We've only got "protected" voices now: lowest note takes priority | |||
jassert (low != nullptr); | |||
@@ -182,6 +182,8 @@ public: | |||
virtual void renderNextBlock (AudioBuffer<float>& outputBuffer, | |||
int startSample, | |||
int numSamples) = 0; | |||
/** A double-precision version of renderNextBlock() */ | |||
virtual void renderNextBlock (AudioBuffer<double>& outputBuffer, | |||
int startSample, | |||
int numSamples); | |||
@@ -214,6 +216,11 @@ public: | |||
*/ | |||
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. */ | |||
bool isSustainPedalDown() const noexcept { return sustainPedalDown; } | |||
@@ -249,11 +256,11 @@ private: | |||
//============================================================================== | |||
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; | |||
bool keyIsDown, sustainPedalDown, sostenutoPedalDown; | |||
bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false; | |||
AudioBuffer<float> tempBuffer; | |||
@@ -615,11 +622,11 @@ private: | |||
int startSample, | |||
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; | |||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
@@ -651,11 +651,16 @@ public: | |||
{ | |||
OpenSLSession::stop(); | |||
while (! guard.compareAndSetBool (1, 0)) | |||
Thread::sleep (1); | |||
if (inputChannels > 0) | |||
recorder->setState (false); | |||
if (outputChannels > 0) | |||
player->setState (false); | |||
guard.set (0); | |||
} | |||
bool setAudioPreprocessingEnabled (bool shouldEnable) override | |||
@@ -576,7 +576,13 @@ struct iOSAudioIODevice::Pimpl : public AudioPlayHead, | |||
&hostUrl, | |||
&dataSize); | |||
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), | |||
bytesPerSample (0), | |||
bytesPerFrame (0), | |||
sampleRateHasChanged (false) | |||
sampleRateHasChanged (false), | |||
shouldClose (false) | |||
{ | |||
clientEvent = CreateEvent (nullptr, false, false, nullptr); | |||
@@ -468,6 +469,11 @@ public: | |||
sampleRateHasChanged = true; | |||
} | |||
void deviceBecameInactive() | |||
{ | |||
shouldClose = true; | |||
} | |||
//============================================================================== | |||
ComSmartPtr<IMMDevice> device; | |||
ComSmartPtr<IAudioClient> client; | |||
@@ -482,7 +488,7 @@ public: | |||
Array<int> channelMaps; | |||
UINT32 actualBufferSize; | |||
int bytesPerSample, bytesPerFrame; | |||
bool sampleRateHasChanged; | |||
bool sampleRateHasChanged, shouldClose; | |||
virtual void updateFormat (bool isFloat) = 0; | |||
@@ -498,10 +504,17 @@ private: | |||
JUCE_COMRESULT OnSimpleVolumeChanged (float, BOOL, LPCGUID) { return S_OK; } | |||
JUCE_COMRESULT OnChannelVolumeChanged (DWORD, float*, DWORD, 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) | |||
{ | |||
Logger::writeToLog("OnSessionDisconnected"); | |||
if (reason == DisconnectReasonFormatChanged) | |||
owner.deviceSampleRateChanged(); | |||
@@ -969,7 +982,8 @@ public: | |||
isStarted (false), | |||
currentBufferSizeSamples (0), | |||
currentSampleRate (0), | |||
callback (nullptr) | |||
callback (nullptr), | |||
deviceBecameInactive (false) | |||
{ | |||
} | |||
@@ -1107,6 +1121,8 @@ public: | |||
if (inputDevice != nullptr) ResetEvent (inputDevice->clientEvent); | |||
if (outputDevice != nullptr) ResetEvent (outputDevice->clientEvent); | |||
deviceBecameInactive = false; | |||
startThread (8); | |||
Thread::sleep (5); | |||
@@ -1226,8 +1242,14 @@ public: | |||
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 (WaitForSingleObject (inputDevice->clientEvent, 1000) == WAIT_TIMEOUT) | |||
@@ -1253,6 +1275,7 @@ public: | |||
} | |||
} | |||
if (! deviceBecameInactive) | |||
{ | |||
const ScopedTryLock sl (startStopLock); | |||
@@ -1263,7 +1286,7 @@ public: | |||
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 | |||
// the input reservoir is filled up correctly even when bufferSize > device actualBufferSize | |||
@@ -1276,7 +1299,7 @@ public: | |||
} | |||
} | |||
if (sampleRateHasChanged) | |||
if (sampleRateHasChanged || deviceBecameInactive) | |||
{ | |||
triggerAsyncUpdate(); | |||
break; // Quit the thread... will restart it later! | |||
@@ -1303,7 +1326,7 @@ private: | |||
bool isOpen_, isStarted; | |||
int currentBufferSizeSamples; | |||
double currentSampleRate; | |||
bool sampleRateChangedByOutput; | |||
bool sampleRateChangedByOutput, deviceBecameInactive; | |||
AudioIODeviceCallback* callback; | |||
CriticalSection startStopLock; | |||
@@ -1354,12 +1377,17 @@ private: | |||
outputDevice = 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 | |||
@@ -1481,7 +1509,7 @@ private: | |||
HRESULT STDMETHODCALLTYPE OnDeviceAdded (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 OnPropertyValueChanged (LPCWSTR, const PROPERTYKEY) { return notify(); } | |||
@@ -619,8 +619,7 @@ public: | |||
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"); | |||
} | |||
@@ -631,8 +630,7 @@ public: | |||
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"); | |||
} | |||
@@ -643,8 +641,7 @@ public: | |||
for (auto tagEntry : knownTags) | |||
{ | |||
AudioChannelLayout layout { tagEntry.tag }; | |||
auto labels = CoreAudioLayouts::getCoreAudioLayoutChannels (layout); | |||
auto labels = CoreAudioLayouts::getSpeakerLayoutForCoreAudioTag (tagEntry.tag); | |||
labels.sort(); | |||
for (int i = 0; i < (labels.size() - 1); ++i) | |||
@@ -657,13 +654,9 @@ public: | |||
beginTest ("CA speaker list and juce layouts are consistent"); | |||
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"); | |||
} | |||
} | |||
{ | |||
@@ -674,9 +667,7 @@ public: | |||
if (tagEntry.equivalentChannelSet.isDisabled()) | |||
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"); | |||
} | |||
} | |||
@@ -1065,6 +1065,19 @@ public: | |||
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 | |||
{ | |||
@@ -1128,6 +1141,7 @@ public: | |||
void refreshParameterList() override | |||
{ | |||
parameters.clear(); | |||
paramIDToIndex.clear(); | |||
if (audioUnit != nullptr) | |||
{ | |||
@@ -1158,6 +1172,7 @@ public: | |||
ParamInfo* const param = new ParamInfo(); | |||
parameters.add (param); | |||
param->paramID = ids[i]; | |||
paramIDToIndex[ids[i]] = i; | |||
param->minValue = info.minValue; | |||
param->maxValue = info.maxValue; | |||
param->automatable = (info.flags & kAudioUnitParameterFlag_NonRealTime) == 0; | |||
@@ -1250,6 +1265,7 @@ private: | |||
}; | |||
OwnedArray<ParamInfo> parameters; | |||
std::unordered_map<AudioUnitParameterID, int> paramIDToIndex; | |||
MidiDataConcatenator midiConcatenator; | |||
CriticalSection midiInLock; | |||
@@ -1335,13 +1351,10 @@ private: | |||
|| event.mEventType == kAudioUnitEvent_BeginParameterChangeGesture | |||
|| 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())) | |||
return; | |||
@@ -1351,7 +1364,7 @@ private: | |||
{ | |||
case kAudioUnitEvent_ParameterValueChange: | |||
{ | |||
const ParamInfo& p = *parameters.getUnchecked(paramIndex); | |||
auto& p = *parameters.getUnchecked (paramIndex); | |||
sendParamChangeMessageToListeners (paramIndex, (newValue - p.minValue) / (p.maxValue - p.minValue)); | |||
} | |||
break; | |||
@@ -68,6 +68,7 @@ | |||
#include <pluginterfaces/base/ipluginbase.h> | |||
#include <pluginterfaces/base/ustring.h> | |||
#include <pluginterfaces/gui/iplugview.h> | |||
#include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||
#include <pluginterfaces/vst/ivstattributes.h> | |||
#include <pluginterfaces/vst/ivstaudioprocessor.h> | |||
#include <pluginterfaces/vst/ivstcomponent.h> | |||
@@ -83,6 +84,7 @@ | |||
#include <pluginterfaces/vst/vsttypes.h> | |||
#include <pluginterfaces/vst/ivstunits.h> | |||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
#include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
#include <public.sdk/source/common/memorystream.h> | |||
#include <public.sdk/source/vst/vsteditcontroller.h> | |||
#else | |||
@@ -105,6 +107,7 @@ | |||
#include <pluginterfaces/gui/iplugview.h> | |||
#include <pluginterfaces/gui/iplugviewcontentscalesupport.h> | |||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
#include <pluginterfaces/vst/ivstchannelcontextinfo.h> | |||
#include <public.sdk/source/common/memorystream.cpp> | |||
#include <public.sdk/source/common/pluginview.cpp> | |||
#include <public.sdk/source/vst/vsteditcontroller.cpp> | |||
@@ -2101,6 +2101,67 @@ struct VST3PluginInstance : public AudioPluginInstance | |||
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 | |||
{ | |||
@@ -2450,6 +2511,7 @@ private: | |||
ComSmartPtr<Vst::IUnitData> unitData; | |||
ComSmartPtr<Vst::IProgramListData> programListData; | |||
ComSmartPtr<Vst::IConnectionPoint> componentConnection, editControllerConnection; | |||
ComSmartPtr<Vst::ChannelContext::IInfoListener> trackInfoListener; | |||
/** The number of IO buses MUST match that of the plugin, | |||
even if there aren't enough channels to process, | |||
@@ -2534,6 +2596,7 @@ private: | |||
editController2.loadFrom (holder->component); | |||
componentHandler.loadFrom (holder->component); | |||
componentHandler2.loadFrom (holder->component); | |||
trackInfoListener.loadFrom (holder->component); | |||
if (processor == nullptr) processor.loadFrom (editController); | |||
if (unitInfo == nullptr) unitInfo.loadFrom (editController); | |||
@@ -2542,6 +2605,7 @@ private: | |||
if (editController2 == nullptr) editController2.loadFrom (editController); | |||
if (componentHandler == nullptr) componentHandler.loadFrom (editController); | |||
if (componentHandler2 == nullptr) componentHandler2.loadFrom (editController); | |||
if (trackInfoListener == nullptr) trackInfoListener.loadFrom (editController); | |||
} | |||
void setStateForAllMidiBuses (bool newState) | |||
@@ -180,8 +180,8 @@ struct SpeakerMappings : private AudioChannelSet // (inheritance only to give e | |||
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); | |||
return storage.getData(); | |||
@@ -1029,6 +1029,9 @@ void AudioProcessor::setCurrentProgramStateInformation (const void* data, int si | |||
setStateInformation (data, sizeInBytes); | |||
} | |||
//============================================================================== | |||
void AudioProcessor::updateTrackProperties (const AudioProcessor::TrackProperties&) {} | |||
//============================================================================== | |||
// magic number to identify memory blocks that we've stored as XML | |||
const uint32 magicXmlNumber = 0x21324356; | |||
@@ -1320,6 +1320,32 @@ public: | |||
*/ | |||
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 | |||
/** Deprecated: use getTotalNumInputChannels instead. */ | |||
@@ -45,7 +45,10 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||
} | |||
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() | |||
{ | |||
@@ -85,6 +85,28 @@ public: | |||
*/ | |||
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 | |||
GUI scale. | |||
*/ | |||
@@ -1466,6 +1466,7 @@ void AudioProcessorGraph::processAudio (AudioBuffer<FloatType>& buffer, MidiBuff | |||
AudioBuffer<FloatType>& currentAudioOutputBuffer = audioBuffers->currentAudioOutputBuffer.get<FloatType>(); | |||
const int numSamples = buffer.getNumSamples(); | |||
jassert (numSamples <= getBlockSize()); | |||
currentAudioInputBuffer = &buffer; | |||
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); | |||
} | |||
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; } | |||
bool AudioProcessorGraph::acceptsMidi() 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) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
sliceAndProcess (buffer, midiMessages); | |||
} | |||
void AudioProcessorGraph::processBlock (AudioBuffer<double>& buffer, MidiBuffer& midiMessages) | |||
{ | |||
processAudio (buffer, midiMessages); | |||
sliceAndProcess (buffer, midiMessages); | |||
} | |||
// explicit template instantiation | |||
@@ -380,6 +380,9 @@ private: | |||
template <typename floatType> | |||
void processAudio (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
template <typename floatType> | |||
void sliceAndProcess (AudioBuffer<floatType>& buffer, MidiBuffer& midiMessages); | |||
//============================================================================== | |||
ReferenceCountedArray<Node> nodes; | |||
OwnedArray<Connection> connections; | |||
@@ -35,7 +35,7 @@ | |||
class JUCE_API PluginListComponent : public Component, | |||
public FileDragAndDropTarget, | |||
private ChangeListener, | |||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private Button::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -91,7 +91,7 @@ DynamicObject::Ptr DynamicObject::clone() | |||
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 << '{'; | |||
if (! allOnOneLine) | |||
@@ -107,7 +107,7 @@ void DynamicObject::writeAsJSON (OutputStream& out, const int indentLevel, const | |||
out << '"'; | |||
JSONFormatter::writeString (out, properties.getName (i)); | |||
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) | |||
{ | |||
@@ -113,14 +113,14 @@ public: | |||
never need to call it directly, but it's virtual so that custom object types | |||
can stringify themselves appropriately. | |||
*/ | |||
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine); | |||
virtual void writeAsJSON (OutputStream&, int indentLevel, bool allOnOneLine, int maximumDecimalPlaces); | |||
private: | |||
//============================================================================== | |||
NamedValueSet properties; | |||
#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) {} | |||
#endif | |||
@@ -31,16 +31,20 @@ | |||
*/ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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: | |||
//============================================================================== | |||
/** 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. | |||
@@ -234,7 +225,7 @@ public: | |||
if (elementToLookFor == data.getReference (s)) | |||
return s; | |||
const int halfway = (s + e) / 2; | |||
auto halfway = (s + e) / 2; | |||
if (halfway == s) | |||
return -1; | |||
@@ -277,15 +268,16 @@ public: | |||
while (s < e) | |||
{ | |||
ElementType& elem = data.getReference (s); | |||
auto& elem = data.getReference (s); | |||
if (newElement == elem) | |||
{ | |||
elem = newElement; // force an update in case operator== permits differences. | |||
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) | |||
{ | |||
@@ -335,25 +327,22 @@ public: | |||
int numElementsToAdd = -1) noexcept | |||
{ | |||
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(); | |||
} | |||
else if (otherSet.size() > 0) | |||
else if (! otherSet.isEmpty()) | |||
{ | |||
for (int i = data.size(); --i >= 0;) | |||
if (otherSet.contains (data.getReference (i))) | |||
@@ -423,7 +412,7 @@ public: | |||
if (this != &otherSet) | |||
{ | |||
if (otherSet.size() <= 0) | |||
if (otherSet.isEmpty()) | |||
{ | |||
clear(); | |||
} | |||
@@ -323,7 +323,8 @@ class JSONFormatter | |||
{ | |||
public: | |||
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()) | |||
{ | |||
@@ -343,14 +344,18 @@ public: | |||
{ | |||
out << (static_cast<bool> (v) ? "true" : "false"); | |||
} | |||
else if (v.isDouble()) | |||
{ | |||
out << String (static_cast<double> (v), maximumDecimalPlaces); | |||
} | |||
else if (v.isArray()) | |||
{ | |||
writeArray (out, *v.getArray(), indentLevel, allOnOneLine); | |||
writeArray (out, *v.getArray(), indentLevel, allOnOneLine, maximumDecimalPlaces); | |||
} | |||
else if (v.isObject()) | |||
{ | |||
if (DynamicObject* object = v.getDynamicObject()) | |||
object->writeAsJSON (out, indentLevel, allOnOneLine); | |||
object->writeAsJSON (out, indentLevel, allOnOneLine, maximumDecimalPlaces); | |||
else | |||
jassertfalse; // Only DynamicObjects can be converted to JSON! | |||
} | |||
@@ -420,7 +425,8 @@ public: | |||
} | |||
static void writeArray (OutputStream& out, const Array<var>& array, | |||
const int indentLevel, const bool allOnOneLine) | |||
const int indentLevel, const bool allOnOneLine, | |||
int maximumDecimalPlaces) | |||
{ | |||
out << '['; | |||
@@ -434,7 +440,7 @@ public: | |||
if (! allOnOneLine) | |||
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) | |||
{ | |||
@@ -493,16 +499,16 @@ Result JSON::parse (const String& text, var& 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); | |||
JSONFormatter::write (mo, data, 0, allOnOneLine); | |||
JSONFormatter::write (mo, data, 0, allOnOneLine, maximumDecimalPlaces); | |||
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) | |||
@@ -90,10 +90,12 @@ public: | |||
/** 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 | |||
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 | |||
*/ | |||
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. | |||
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. | |||
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. | |||
The maximumDecimalPlaces parameter determines the precision of floating point numbers. | |||
@see toString | |||
*/ | |||
static void writeToStream (OutputStream& output, | |||
const var& objectToFormat, | |||
bool allOnOneLine = false); | |||
bool allOnOneLine = false, | |||
int maximumDecimalPlaces = 20); | |||
/** Returns a version of a string with any extended characters escaped. */ | |||
static String escapeString (StringRef); | |||
@@ -827,7 +827,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
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; | |||
} | |||
@@ -149,6 +149,13 @@ public: | |||
*/ | |||
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. | |||
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. | |||
@@ -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.HttpURLConnection; | |||
import android.media.AudioManager; | |||
import android.media.MediaScannerConnection; | |||
import android.media.MediaScannerConnection.MediaScannerConnectionClient; | |||
import android.Manifest; | |||
import java.util.concurrent.CancellationException; | |||
import java.util.concurrent.Future; | |||
@@ -68,7 +66,7 @@ import java.util.concurrent.Callable; | |||
import java.util.concurrent.TimeoutException; | |||
import java.util.concurrent.locks.ReentrantLock; | |||
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 resumeApp(); | |||
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; | |||
@@ -882,6 +864,38 @@ public class JuceAppActivity extends Activity | |||
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 | |||
{ | |||
@@ -1185,36 +1199,13 @@ public class JuceAppActivity extends Activity | |||
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) | |||
{ | |||
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 | |||
{ | |||
return false; | |||
@@ -100,3 +140,49 @@ JUCE_API bool JUCE_CALLTYPE Process::openDocument (const String& fileName, 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 | |||
{ | |||
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() | |||
{ | |||
@@ -62,6 +63,14 @@ public: | |||
return *this; | |||
} | |||
inline GlobalRef& operator= (GlobalRef&& other) | |||
{ | |||
clear(); | |||
std::swap (obj, other.obj); | |||
return *this; | |||
} | |||
//============================================================================== | |||
inline operator jobject() const noexcept { return obj; } | |||
inline jobject get() const noexcept { return obj; } | |||
@@ -98,7 +107,7 @@ public: | |||
private: | |||
//============================================================================== | |||
jobject obj; | |||
jobject obj = 0; | |||
static inline jobject retain (jobject obj) | |||
{ | |||
@@ -111,14 +120,19 @@ template <typename JavaType> | |||
class LocalRef | |||
{ | |||
public: | |||
explicit inline LocalRef () noexcept : obj (0) {} | |||
explicit inline LocalRef (JavaType o) noexcept : obj (o) {} | |||
inline LocalRef (const LocalRef& other) noexcept : obj (retain (other.obj)) {} | |||
inline LocalRef (LocalRef&& other) noexcept : obj (0) { std::swap (obj, other.obj); } | |||
~LocalRef() { clear(); } | |||
void clear() | |||
{ | |||
if (obj != 0) | |||
{ | |||
getEnv()->DeleteLocalRef (obj); | |||
obj = 0; | |||
} | |||
} | |||
LocalRef& operator= (const LocalRef& other) | |||
@@ -129,6 +143,13 @@ public: | |||
return *this; | |||
} | |||
LocalRef& operator= (LocalRef&& other) | |||
{ | |||
clear(); | |||
std::swap (other.obj, obj); | |||
return *this; | |||
} | |||
inline operator JavaType() 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) \ | |||
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 (postMessage, "postMessage", "(J)V") \ | |||
METHOD (finish, "finish", "()V") \ | |||
METHOD (finish, "finish", "()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 (setScreenSaver, "setScreenSaver", "(Z)V") \ | |||
METHOD (getScreenSaver, "getScreenSaver", "()Z") \ | |||
@@ -288,11 +307,16 @@ extern AndroidSystem android; | |||
METHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$BluetoothManager;") \ | |||
METHOD (getAndroidSDKVersion, "getAndroidSDKVersion", "()I") \ | |||
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 (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | |||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \ | |||
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \ | |||
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); | |||
#undef JNI_CLASS_MEMBERS | |||
@@ -332,3 +356,76 @@ DECLARE_JNI_CLASS (Matrix, "android/graphics/Matrix"); | |||
DECLARE_JNI_CLASS (RectClass, "android/graphics/Rect"); | |||
#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) | |||
{ | |||
getClasses().add (this); | |||
@@ -91,6 +98,92 @@ jfieldID JNIClassBase::resolveStaticField (JNIEnv* env, const char* fieldName, c | |||
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; | |||
@@ -27,4 +27,9 @@ | |||
#include <CoreFoundation/CFAvailability.h> | |||
#undef CF_OPTIONS | |||
#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 |
@@ -401,7 +401,12 @@ bool JUCE_CALLTYPE Process::openDocument (const String& fileName, const String& | |||
if (SystemStats::isRunningInAppExtensionSandbox()) | |||
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]; | |||
#else | |||
[[UIApplication sharedApplication] openURL: filenameAsURL options: @{} completionHandler: nil]; | |||
return true; | |||
#endif | |||
#else | |||
NSWorkspace* workspace = [NSWorkspace sharedWorkspace]; | |||
@@ -137,6 +137,8 @@ public: | |||
[task release]; | |||
[request release]; | |||
[headers release]; | |||
[session finishTasksAndInvalidate]; | |||
[session release]; | |||
const ScopedLock sl (dataLock); | |||
@@ -59,6 +59,16 @@ namespace | |||
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 | |||
template <typename RectangleType> | |||
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; | |||
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; | |||
#endif | |||
} | |||
} | |||
@@ -554,23 +558,13 @@ ssize_t FileOutputStream::writeInternal (const void* const data, const size_t nu | |||
return result; | |||
} | |||
#ifndef JUCE_ANDROID | |||
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() | |||
{ | |||
@@ -227,7 +227,19 @@ String SystemStats::getOperatingSystemName() | |||
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() | |||
@@ -47,6 +47,10 @@ | |||
#define JUCE_COMPILER_SUPPORTS_THREAD_LOCAL 1 | |||
#endif | |||
#if __cpp_constexpr >= 201304 | |||
#define JUCE_HAS_CONSTEXPR 1 | |||
#endif | |||
#ifndef JUCE_EXCEPTIONS_DISABLED | |||
#if ! __EXCEPTIONS | |||
#define JUCE_EXCEPTIONS_DISABLED 1 | |||
@@ -86,6 +90,10 @@ | |||
#define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | |||
#endif | |||
#if __has_feature(cxx_relaxed_constexpr) | |||
#define JUCE_HAS_CONSTEXPR 1 | |||
#endif | |||
#ifndef JUCE_COMPILER_SUPPORTS_ARC | |||
#define JUCE_COMPILER_SUPPORTS_ARC 1 | |||
#endif | |||
@@ -118,6 +126,10 @@ | |||
#define JUCE_COMPILER_SUPPORTS_NOEXCEPT 1 | |||
#endif | |||
#if _MSC_VER >= 1910 | |||
#define JUCE_HAS_CONSTEXPR 1 | |||
#endif | |||
#ifndef JUCE_EXCEPTIONS_DISABLED | |||
#if ! _CPPUNWIND | |||
#define JUCE_EXCEPTIONS_DISABLED 1 | |||
@@ -137,6 +149,12 @@ | |||
#define JUCE_DELETED_FUNCTION | |||
#endif | |||
#if JUCE_HAS_CONSTEXPR | |||
#define JUCE_CONSTEXPR constexpr | |||
#else | |||
#define JUCE_CONSTEXPR | |||
#endif | |||
#if ! DOXYGEN | |||
#if ! JUCE_COMPILER_SUPPORTS_NOEXCEPT | |||
#ifdef noexcept | |||
@@ -563,7 +563,7 @@ struct HashGenerator | |||
template <typename CharPointer> | |||
static Type calculate (CharPointer t) noexcept | |||
{ | |||
Type result = Type(); | |||
Type result = {}; | |||
while (! t.isEmpty()) | |||
result = ((Type) multiplier) * result + (Type) t.getAndAdvance(); | |||
@@ -574,9 +574,9 @@ struct HashGenerator | |||
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; } | |||
@@ -1884,7 +1884,7 @@ String String::formattedRaw (const char* pf, ...) | |||
va_end (args); | |||
if (num > 0) | |||
return String (temp); | |||
return String (temp.get()); | |||
bufferSize += 256; | |||
@@ -987,6 +987,10 @@ public: | |||
*/ | |||
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). | |||
@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. */ | |||
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); | |||
/** 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); | |||
/** 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); | |||
/** 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); | |||
/** 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); | |||
/** 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); | |||
/** 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); | |||
/** 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); | |||
// 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. */ | |||
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) | |||
{ | |||
} | |||
Thread::~Thread() | |||
{ | |||
if (deleteOnThreadEnd) | |||
return; | |||
/* 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 | |||
probably a Bad Thing! | |||
@@ -97,6 +100,9 @@ void Thread::threadEntryPoint() | |||
currentThreadHolder->value.releaseCurrentThreadStorage(); | |||
closeThreadHandle(); | |||
if (deleteOnThreadEnd) | |||
delete this; | |||
} | |||
// used to wrap the incoming call from the platform-specific code | |||
@@ -162,7 +168,7 @@ void Thread::signalThreadShouldExit() | |||
bool Thread::currentThreadShouldExit() | |||
{ | |||
if (Thread* currentThread = getCurrentThread()) | |||
if (auto* currentThread = getCurrentThread()) | |||
return currentThread->threadShouldExit(); | |||
return false; | |||
@@ -273,6 +279,29 @@ void Thread::notify() const | |||
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 | |||
{ | |||
@@ -76,8 +76,6 @@ public: | |||
virtual void run() = 0; | |||
//============================================================================== | |||
// Thread control functions.. | |||
/** Starts the thread running. | |||
This will cause the thread's run() method to be called by a new thread. | |||
@@ -118,6 +116,18 @@ public: | |||
*/ | |||
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 */ | |||
bool isThreadRunning() const; | |||
@@ -154,8 +164,7 @@ public: | |||
static bool currentThreadShouldExit(); | |||
/** 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 | |||
is less than zero, it will wait forever. | |||
@@ -274,21 +283,17 @@ public: | |||
static Thread* JUCE_CALLTYPE getCurrentThread(); | |||
/** Returns the ID of this thread. | |||
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 | |||
thread's not actually running. | |||
@see getCurrentThreadId | |||
*/ | |||
ThreadID getThreadId() const noexcept { return threadId; } | |||
/** Returns the name of the thread. | |||
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. | |||
Different OSes may place different length or content limits on this name. | |||
@@ -306,6 +311,7 @@ private: | |||
int threadPriority = 5; | |||
size_t threadStackSize; | |||
uint32 affinityMask = 0; | |||
bool deleteOnThreadEnd = false; | |||
bool volatile shouldExit = false; | |||
#if JUCE_ANDROID | |||
@@ -23,8 +23,8 @@ | |||
class ThreadPool::ThreadPoolThread : public Thread | |||
{ | |||
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); | |||
} | |||
ThreadPoolJob* volatile currentJob; | |||
ThreadPoolJob* volatile currentJob = nullptr; | |||
ThreadPool& pool; | |||
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() | |||
{ | |||
if (ThreadPool::ThreadPoolThread* t = dynamic_cast<ThreadPool::ThreadPoolThread*> (Thread::getCurrentThread())) | |||
if (auto* t = dynamic_cast<ThreadPool::ThreadPoolThread*> (Thread::getCurrentThread())) | |||
return t->currentJob; | |||
return nullptr; | |||
@@ -102,17 +100,17 @@ void ThreadPool::createThreads (int numThreads, size_t threadStackSize) | |||
for (int i = jmax (1, numThreads); --i >= 0;) | |||
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() | |||
{ | |||
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) | |||
@@ -132,44 +130,83 @@ void ThreadPool::addJob (ThreadPoolJob* const job, const bool deleteJobWhenFinis | |||
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(); | |||
} | |||
int ThreadPool::getNumThreads() const | |||
int ThreadPool::getNumThreads() const noexcept | |||
{ | |||
return threads.size(); | |||
} | |||
ThreadPoolJob* ThreadPool::getJob (const int index) const | |||
ThreadPoolJob* ThreadPool::getJob (int index) const noexcept | |||
{ | |||
const ScopedLock sl (lock); | |||
return jobs [index]; | |||
} | |||
bool ThreadPool::contains (const ThreadPoolJob* const job) const | |||
bool ThreadPool::contains (const ThreadPoolJob* const job) const noexcept | |||
{ | |||
const ScopedLock sl (lock); | |||
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); | |||
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 | |||
{ | |||
if (job != nullptr) | |||
{ | |||
const uint32 start = Time::getMillisecondCounter(); | |||
auto start = Time::getMillisecondCounter(); | |||
while (contains (job)) | |||
{ | |||
@@ -217,7 +254,7 @@ bool ThreadPool::removeJob (ThreadPoolJob* const job, | |||
bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeOutMs, | |||
ThreadPool::JobSelector* const selectedJobsToRemove) | |||
{ | |||
Array <ThreadPoolJob*> jobsToWaitFor; | |||
Array<ThreadPoolJob*> jobsToWaitFor; | |||
{ | |||
OwnedArray<ThreadPoolJob> deletionList; | |||
@@ -227,7 +264,7 @@ bool ThreadPool::removeAllJobs (const bool interruptRunningJobs, const int timeO | |||
for (int i = jobs.size(); --i >= 0;) | |||
{ | |||
ThreadPoolJob* const job = jobs.getUnchecked(i); | |||
auto* job = jobs.getUnchecked(i); | |||
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 (int i = jobsToWaitFor.size(); --i >= 0;) | |||
{ | |||
ThreadPoolJob* const job = jobsToWaitFor.getUnchecked (i); | |||
auto* job = jobsToWaitFor.getUnchecked (i); | |||
if (! isJobRunning (job)) | |||
jobsToWaitFor.remove (i); | |||
@@ -277,12 +314,9 @@ StringArray ThreadPool::getNamesOfAllJobs (const bool onlyReturnActiveJobs) cons | |||
StringArray s; | |||
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) | |||
s.add (job->getJobName()); | |||
} | |||
return s; | |||
} | |||
@@ -291,8 +325,8 @@ bool ThreadPool::setThreadPriorities (const int newPriority) | |||
{ | |||
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; | |||
return ok; | |||
@@ -307,20 +341,21 @@ ThreadPoolJob* ThreadPool::pickNextJobToRun() | |||
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) | |||
{ | |||
if (ThreadPoolJob* const job = pickNextJobToRun()) | |||
if (auto* job = pickNextJobToRun()) | |||
{ | |||
ThreadPoolJob::JobStatus result = ThreadPoolJob::jobHasFinished; | |||
auto result = ThreadPoolJob::jobHasFinished; | |||
thread.currentJob = job; | |||
try | |||
@@ -123,8 +123,8 @@ private: | |||
friend class ThreadPool; | |||
friend class ThreadPoolThread; | |||
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) | |||
}; | |||
@@ -207,6 +207,16 @@ public: | |||
void addJob (ThreadPoolJob* job, | |||
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. | |||
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); | |||
/** 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. */ | |||
int getNumThreads() const; | |||
int getNumThreads() const noexcept; | |||
/** Returns one of the jobs in the queue. | |||
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. | |||
*/ | |||
ThreadPoolJob* getJob (int index) const; | |||
ThreadPoolJob* getJob (int index) const noexcept; | |||
/** Returns true if the given job is currently queued or running. | |||
@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. | |||
@@ -277,13 +286,17 @@ public: | |||
bool waitForJobToFinish (const ThreadPoolJob* job, | |||
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. | |||
If onlyReturnActiveJobs is true, only the ones currently running are returned. | |||
*/ | |||
StringArray getNamesOfAllJobs (bool onlyReturnActiveJobs) const; | |||
/** Changes the priority of all the threads. | |||
This will call Thread::setPriority() for each thread in the pool. | |||
May return false if for some reason the priority can't be changed. | |||
*/ | |||
@@ -292,7 +305,7 @@ public: | |||
private: | |||
//============================================================================== | |||
Array <ThreadPoolJob*> jobs; | |||
Array<ThreadPoolJob*> jobs; | |||
class ThreadPoolThread; | |||
friend class ThreadPoolJob; | |||
@@ -139,7 +139,7 @@ private: | |||
class JUCE_API ScopedTimeMeasurement | |||
{ | |||
public: | |||
ScopedTimeMeasurement (double& resultInSeconds) | |||
ScopedTimeMeasurement (double& resultInSeconds) noexcept | |||
: result (resultInSeconds) | |||
{ | |||
result = 0.0; | |||
@@ -154,4 +154,6 @@ public: | |||
private: | |||
int64 startTimeTicks = Time::getHighResolutionTicks(); | |||
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. | |||
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(); | |||
@@ -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) | |||
@@ -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&) | |||
{ | |||
@@ -38,7 +38,7 @@ namespace | |||
&& (int) h >= 0 && (int) h <= maxVal); | |||
#endif | |||
return Rectangle<Type> (x, y, w, h); | |||
return { x, y, w, h }; | |||
} | |||
} | |||
@@ -49,15 +49,13 @@ LowLevelGraphicsContext::~LowLevelGraphicsContext() {} | |||
//============================================================================== | |||
Graphics::Graphics (const Image& imageToDrawOnto) | |||
: context (*imageToDrawOnto.createLowLevelContext()), | |||
contextToDelete (&context), | |||
saveStatePending (false) | |||
contextToDelete (&context) | |||
{ | |||
jassert (imageToDrawOnto.isValid()); // Can't draw into a null image! | |||
} | |||
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); | |||
} | |||
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)); | |||
} | |||
@@ -157,7 +155,7 @@ void Graphics::setOrigin (Point<int> newOrigin) | |||
void Graphics::setOrigin (int x, int y) | |||
{ | |||
setOrigin (Point<int> (x, y)); | |||
setOrigin ({ x, y }); | |||
} | |||
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. | |||
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; | |||
arr.addLineOfText (context.getFont(), text, (float) startX, (float) baselineY); | |||
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) | |||
w /= 2.0f; | |||
@@ -371,11 +366,6 @@ void Graphics::fillRectList (const RectangleList<int>& rects) const | |||
context.fillRect (r, false); | |||
} | |||
void Graphics::setPixel (int x, int y) const | |||
{ | |||
context.fillRect (coordsToRectangle (x, y, 1, 1), false); | |||
} | |||
void Graphics::fillAll() const | |||
{ | |||
fillRect (context.getClipBounds()); | |||
@@ -385,7 +375,7 @@ void Graphics::fillAll (Colour colourToUse) const | |||
{ | |||
if (! colourToUse.isTransparent()) | |||
{ | |||
const Rectangle<int> clip (context.getClipBounds()); | |||
auto clip = context.getClipBounds(); | |||
context.saveState(); | |||
context.setFill (colourToUse); | |||
@@ -507,7 +497,7 @@ void Graphics::drawRoundedRectangle (Rectangle<float> r, float cornerSize, float | |||
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; | |||
p.addArrow (line, lineThickness, arrowheadWidth, arrowheadLength); | |||
@@ -531,7 +521,7 @@ void Graphics::fillCheckerBoard (Rectangle<int> area, | |||
} | |||
else | |||
{ | |||
const Rectangle<int> clipped (context.getClipBounds().getIntersection (area)); | |||
auto clipped = context.getClipBounds().getIntersection (area); | |||
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)); | |||
} | |||
void Graphics::drawLine (const Line<float>& line) const | |||
void Graphics::drawLine (Line<float> line) const | |||
{ | |||
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); | |||
} | |||
void Graphics::drawLine (const Line<float>& line, const float lineThickness) const | |||
void Graphics::drawLine (Line<float> line, const float lineThickness) const | |||
{ | |||
Path p; | |||
p.addLineSegment (line, lineThickness); | |||
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! | |||
@@ -338,12 +338,6 @@ public: | |||
void drawRoundedRectangle (Rectangle<float> rectangle, | |||
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. | |||
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 - | |||
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. | |||
@see Path::addLineSegment | |||
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. | |||
*/ | |||
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. | |||
@@ -408,7 +402,7 @@ public: | |||
@param dashIndexToStartFrom the index in the dash-length array to use for the first segment | |||
@see PathStrokeType::createDashedStroke | |||
*/ | |||
void drawDashedLine (const Line<float>& line, | |||
void drawDashedLine (Line<float> line, | |||
const float* dashLengths, int numDashLengths, | |||
float lineThickness = 1.0f, | |||
int dashIndexToStartFrom = 0) const; | |||
@@ -441,7 +435,7 @@ public: | |||
/** Draws a path's outline using the currently selected colour or brush. */ | |||
void strokePath (const Path& path, | |||
const PathStrokeType& strokeType, | |||
const AffineTransform& transform = AffineTransform()) const; | |||
const AffineTransform& transform = {}) const; | |||
/** 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 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 arrowheadWidth, | |||
float arrowheadLength) const; | |||
@@ -743,7 +737,7 @@ private: | |||
LowLevelGraphicsContext& context; | |||
ScopedPointer<LowLevelGraphicsContext> contextToDelete; | |||
bool saveStatePending; | |||
bool saveStatePending = false; | |||
void saveStateIfPending(); | |||
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; | |||
} | |||
void Path::startNewSubPath (const Point<float> start) | |||
void Path::startNewSubPath (Point<float> start) | |||
{ | |||
startNewSubPath (start.x, start.y); | |||
} | |||
@@ -301,7 +301,7 @@ void Path::lineTo (const float x, const float y) | |||
bounds.extend (x, y); | |||
} | |||
void Path::lineTo (const Point<float> end) | |||
void Path::lineTo (Point<float> end) | |||
{ | |||
lineTo (end.x, end.y); | |||
} | |||
@@ -326,8 +326,7 @@ void Path::quadraticTo (const float x1, const float y1, | |||
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, | |||
endPoint.x, endPoint.y); | |||
@@ -358,9 +357,9 @@ void Path::cubicTo (const float x1, const float y1, | |||
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, | |||
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(); | |||
lineThickness *= 0.5f; | |||
@@ -687,7 +686,7 @@ void Path::addLineSegment (const Line<float>& line, float lineThickness) | |||
closeSubPath(); | |||
} | |||
void Path::addArrow (const Line<float>& line, float lineThickness, | |||
void Path::addArrow (Line<float> line, float lineThickness, | |||
float arrowheadWidth, float arrowheadLength) | |||
{ | |||
auto reversed = line.reversed(); | |||
@@ -705,8 +704,8 @@ void Path::addArrow (const Line<float>& line, float lineThickness, | |||
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. | |||
@@ -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. | |||
@@ -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 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(), | |||
preserveProportions, justification); | |||
@@ -1059,7 +1058,7 @@ bool Path::contains (const float x, const float y, const float tolerance) const | |||
: ((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); | |||
} | |||
@@ -1138,7 +1137,7 @@ Point<float> Path::getPointAlongPath (float distanceFromStart, | |||
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, | |||
float tolerance) const | |||
{ | |||
@@ -129,7 +129,7 @@ public: | |||
@see closeSubPath, setUsingNonZeroWinding | |||
*/ | |||
bool contains (const Point<float> point, | |||
bool contains (Point<float> point, | |||
float tolerance = defaultToleranceForTesting) const; | |||
/** Checks whether a line crosses the path. | |||
@@ -211,7 +211,7 @@ public: | |||
@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. | |||
@@ -247,7 +247,7 @@ public: | |||
@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. | |||
@@ -272,8 +272,8 @@ public: | |||
@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. | |||
@@ -300,9 +300,9 @@ public: | |||
@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. | |||
*/ | |||
@@ -320,7 +320,7 @@ public: | |||
@see addRoundedRectangle, addTriangle | |||
*/ | |||
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()), | |||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight())); | |||
@@ -355,7 +355,7 @@ public: | |||
@see addRectangle, addTriangle | |||
*/ | |||
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()), | |||
static_cast<float> (rectangle.getWidth()), static_cast<float> (rectangle.getHeight()), | |||
@@ -367,7 +367,7 @@ public: | |||
@see addRectangle, addTriangle | |||
*/ | |||
template <typename ValueType> | |||
void addRoundedRectangle (const Rectangle<ValueType>& rectangle, float cornerSize) | |||
void addRoundedRectangle (Rectangle<ValueType> rectangle, float cornerSize) | |||
{ | |||
addRoundedRectangle (rectangle, cornerSize, cornerSize); | |||
} | |||
@@ -534,13 +534,13 @@ public: | |||
@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. | |||
The arrow is added as a new closed sub-path. (Any currently open paths will be left open). | |||
@see PathStrokeType::createStrokeWithArrowheads | |||
*/ | |||
void addArrow (const Line<float>& line, | |||
void addArrow (Line<float> line, | |||
float lineThickness, | |||
float arrowheadWidth, | |||
float arrowheadLength); | |||
@@ -548,7 +548,7 @@ public: | |||
/** Adds a polygon shape to the path. | |||
@see addStar | |||
*/ | |||
void addPolygon (const Point<float> centre, | |||
void addPolygon (Point<float> centre, | |||
int numberOfSides, | |||
float radius, | |||
float startAngle = 0.0f); | |||
@@ -556,7 +556,7 @@ public: | |||
/** Adds a star shape to the path. | |||
@see addPolygon | |||
*/ | |||
void addStar (const Point<float> centre, | |||
void addStar (Point<float> centre, | |||
int numberOfPoints, | |||
float innerRadius, | |||
float outerRadius, | |||
@@ -572,8 +572,8 @@ public: | |||
@param cornerSize the size of the rounded corners | |||
@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 float cornerSize, | |||
const float arrowBaseWidth); | |||
@@ -677,7 +677,7 @@ public: | |||
@see applyTransform, scaleToFit | |||
*/ | |||
AffineTransform getTransformToScaleToFit (const Rectangle<float>& area, | |||
AffineTransform getTransformToScaleToFit (Rectangle<float> area, | |||
bool preserveProportions, | |||
Justification justificationType = Justification::centred) const; | |||
@@ -41,101 +41,101 @@ class Point | |||
{ | |||
public: | |||
/** Creates a point at the origin */ | |||
Point() noexcept : x(), y() {} | |||
JUCE_CONSTEXPR Point() noexcept : x(), y() {} | |||
/** 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. */ | |||
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. */ | |||
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). */ | |||
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. */ | |||
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. */ | |||
inline ValueType getX() const noexcept { return x; } | |||
JUCE_CONSTEXPR inline ValueType getX() const noexcept { return x; } | |||
/** 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. */ | |||
inline void setX (ValueType newX) noexcept { x = newX; } | |||
inline void setX (ValueType newX) noexcept { x = newX; } | |||
/** 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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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 */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
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. */ | |||
@@ -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) | |||
{ | |||
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; | |||
p.addLineSegment (line, 1.0f); | |||
fillPath (p, AffineTransform()); | |||
fillPath (p, {}); | |||
} | |||
void drawImage (const Image& sourceImage, const AffineTransform& trans) | |||
@@ -2319,7 +2319,7 @@ public: | |||
void renderImage (const Image& sourceImage, const AffineTransform& trans, | |||
const BaseRegionType* const tiledFillClipRegion) | |||
{ | |||
const AffineTransform t (transform.getTransformWith (trans)); | |||
auto t = transform.getTransformWith (trans); | |||
const int alpha = fillType.colour.getAlpha(); | |||
@@ -2356,18 +2356,17 @@ public: | |||
{ | |||
if (tiledFillClipRegion != nullptr) | |||
{ | |||
tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, t, interpolationQuality, true); | |||
tiledFillClipRegion->renderImageTransformed (getThis(), sourceImage, alpha, | |||
t, interpolationQuality, true); | |||
} | |||
else | |||
{ | |||
Path p; | |||
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)); | |||
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(); | |||
@@ -26,7 +26,7 @@ | |||
class Button::CallbackHelper : public Timer, | |||
public ApplicationCommandManagerListener, | |||
public ValueListener, | |||
public Value::Listener, | |||
public KeyListener | |||
{ | |||
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) | |||
{ | |||
@@ -420,7 +413,7 @@ void Button::sendClickMessage (const ModifierKeys& modifiers) | |||
clicked (modifiers); | |||
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() | |||
@@ -430,7 +423,7 @@ void Button::sendStateMessage() | |||
buttonStateChanged(); | |||
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 | |||
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. | |||
@see triggerClick | |||
@@ -25,10 +25,7 @@ | |||
*/ | |||
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<int> r (getLocalBounds()); | |||
auto r = getLocalBounds(); | |||
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) | |||
{ | |||
@@ -178,7 +175,7 @@ void DrawableButton::paintButton (Graphics& g, | |||
const bool isMouseOverButton, | |||
const bool isButtonDown) | |||
{ | |||
LookAndFeel& lf = getLookAndFeel(); | |||
auto& lf = getLookAndFeel(); | |||
if (style == ImageOnButtonBackground) | |||
lf.drawButtonBackground (g, *this, | |||
@@ -217,7 +214,7 @@ Drawable* DrawableButton::getOverImage() 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 getOverImage(); | |||
@@ -122,6 +122,9 @@ public: | |||
*/ | |||
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. */ | |||
Drawable* getCurrentImage() const noexcept; | |||
@@ -179,8 +182,8 @@ private: | |||
ButtonStyle style; | |||
ScopedPointer<Drawable> normalImage, overImage, downImage, disabledImage, | |||
normalImageOn, overImageOn, downImageOn, disabledImageOn; | |||
Drawable* currentImage; | |||
int edgeIndent; | |||
Drawable* currentImage = nullptr; | |||
int edgeIndent = 3; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DrawableButton) | |||
}; |
@@ -31,10 +31,7 @@ Component* Component::currentlyFocusedComponent = nullptr; | |||
class Component::MouseListenerList | |||
{ | |||
public: | |||
MouseListenerList() noexcept | |||
: numDeepMouseListeners (0) | |||
{ | |||
} | |||
MouseListenerList() noexcept {} | |||
void addListener (MouseListener* const newListener, const bool wantsEventsForAllNestedChildComponents) | |||
{ | |||
@@ -54,7 +51,7 @@ public: | |||
void removeListener (MouseListener* const listenerToRemove) | |||
{ | |||
const int index = listeners.indexOf (listenerToRemove); | |||
auto index = listeners.indexOf (listenerToRemove); | |||
if (index >= 0) | |||
{ | |||
@@ -86,20 +83,21 @@ public: | |||
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) | |||
{ | |||
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: | |||
Array<MouseListener*> listeners; | |||
int numDeepMouseListeners; | |||
int numDeepMouseListeners = 0; | |||
struct BailOutChecker2 | |||
{ | |||
@@ -175,11 +174,10 @@ struct 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; | |||
@@ -326,7 +324,7 @@ struct Component::ComponentHelpers | |||
if (comp.isOnDesktop()) | |||
{ | |||
if (ComponentPeer* peer = comp.getPeer()) | |||
if (auto* peer = comp.getPeer()) | |||
pointInParentSpace = ScalingHelpers::unscaledScreenPosToScaled | |||
(comp, peer->globalToLocal (ScalingHelpers::scaledScreenPosToUnscaled (pointInParentSpace))); | |||
else | |||
@@ -345,7 +343,7 @@ struct Component::ComponentHelpers | |||
{ | |||
if (comp.isOnDesktop()) | |||
{ | |||
if (ComponentPeer* peer = comp.getPeer()) | |||
if (auto* peer = comp.getPeer()) | |||
pointInLocalSpace = ScalingHelpers::unscaledScreenPosToScaled | |||
(peer->localToGlobal (ScalingHelpers::scaledScreenPosToUnscaled (comp, pointInLocalSpace))); | |||
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 | |||
// 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()) | |||
{ | |||
@@ -622,7 +620,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||
jmax (1, getHeight())); | |||
#endif | |||
const Point<int> topLeft (getScreenPosition()); | |||
auto topLeft = getScreenPosition(); | |||
bool wasFullscreen = false; | |||
bool wasMinimised = false; | |||
@@ -703,7 +701,7 @@ void Component::removeFromDesktop() | |||
if (flags.hasHeavyweightPeerFlag) | |||
{ | |||
ComponentPeer* const peer = ComponentPeer::getPeerFor (this); | |||
auto* peer = ComponentPeer::getPeerFor (this); | |||
jassert (peer != nullptr); | |||
flags.hasHeavyweightPeerFlag = false; | |||
@@ -753,7 +751,7 @@ void Component::setOpaque (const bool shouldBeOpaque) | |||
flags.opaqueFlag = shouldBeOpaque; | |||
if (flags.hasHeavyweightPeerFlag) | |||
if (const ComponentPeer* const peer = ComponentPeer::getPeerFor (this)) | |||
if (auto* peer = ComponentPeer::getPeerFor (this)) | |||
addToDesktop (peer->getStyleFlags()); // recreates the heavyweight window | |||
repaint(); | |||
@@ -955,8 +953,8 @@ void Component::toBehind (Component* const other) | |||
{ | |||
auto* us = getPeer(); | |||
auto* them = other->getPeer(); | |||
jassert (us != nullptr && them != nullptr); | |||
if (us != nullptr && them != nullptr) | |||
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 | |||
// for these, we'll need to create a new window | |||
const int oldFlags = peer->getStyleFlags(); | |||
auto oldFlags = peer->getStyleFlags(); | |||
removeFromDesktop(); | |||
addToDesktop (oldFlags); | |||
} | |||
@@ -1248,8 +1246,8 @@ void Component::setBounds (const String& newBoundsExpression) | |||
void Component::setBoundsRelative (const float x, const float y, | |||
const float w, const float h) | |||
{ | |||
const int pw = getParentWidth(); | |||
const int ph = getParentHeight(); | |||
auto pw = getParentWidth(); | |||
auto ph = getParentHeight(); | |||
setBounds (roundToInt (x * pw), | |||
roundToInt (y * ph), | |||
@@ -1448,7 +1446,7 @@ Component* Component::getComponentAt (Point<int> position) | |||
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) | |||
{ | |||
internalRepaint (Rectangle<int> (x, y, w, h)); | |||
internalRepaint ({ x, y, w, h }); | |||
} | |||
void Component::repaint (Rectangle<int> area) | |||
@@ -1964,7 +1962,7 @@ void Component::paintComponentAndChildren (Graphics& g) | |||
{ | |||
g.saveState(); | |||
if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, Point<int>()) && g.isClipEmpty())) | |||
if (! (ComponentHelpers::clipObscuredRegions (*this, g, clipBounds, {}) && g.isClipEmpty())) | |||
paint (g); | |||
g.restoreState(); | |||
@@ -2168,7 +2166,7 @@ void Component::sendLookAndFeelChange() | |||
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)); | |||
if (inheritFromParent && parentComponent != nullptr | |||
@@ -2201,7 +2199,7 @@ void Component::copyAllExplicitColoursTo (Component& target) const | |||
for (int i = properties.size(); --i >= 0;) | |||
{ | |||
const Identifier name (properties.getName(i)); | |||
auto name = properties.getName(i); | |||
if (name.toString().startsWith ("jcclr_")) | |||
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, | |||
float pressure, float orientation, float rotation, float tiltX, float tiltY) | |||
{ | |||
Desktop& desktop = Desktop::getInstance(); | |||
auto& desktop = Desktop::getInstance(); | |||
BailOutChecker checker (this); | |||
if (isCurrentlyBlockedByAnotherModalComponent()) | |||
@@ -2445,7 +2443,7 @@ void Component::internalMouseDown (MouseInputSource source, Point<float> relativ | |||
flags.mouseDownWasBlocked = false; | |||
for (Component* c = this; c != nullptr; c = c->parentComponent) | |||
for (auto* c = this; c != nullptr; c = c->parentComponent) | |||
{ | |||
if (c->isBroughtToFrontOnMouseClick()) | |||
{ | |||
@@ -2502,7 +2500,7 @@ void Component::internalMouseUp (MouseInputSource source, Point<float> relativeP | |||
if (checker.shouldBailOut()) | |||
return; | |||
Desktop& desktop = Desktop::getInstance(); | |||
auto& desktop = Desktop::getInstance(); | |||
desktop.getMouseListeners().callChecked (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) | |||
{ | |||
Desktop& desktop = Desktop::getInstance(); | |||
auto& desktop = Desktop::getInstance(); | |||
if (isCurrentlyBlockedByAnotherModalComponent()) | |||
{ | |||
@@ -2578,7 +2576,7 @@ void Component::internalMouseMove (MouseInputSource source, Point<float> relativ | |||
void Component::internalMouseWheel (MouseInputSource source, Point<float> relativePos, | |||
Time time, const MouseWheelDetails& wheel) | |||
{ | |||
Desktop& desktop = Desktop::getInstance(); | |||
auto& desktop = Desktop::getInstance(); | |||
BailOutChecker checker (this); | |||
const MouseEvent me (source, relativePos, source.getCurrentModifiers(), MouseInputSource::invalidPressure, | |||
@@ -2854,9 +2852,7 @@ void Component::moveKeyboardFocusToSibling (const bool moveToNext) | |||
if (parentComponent != nullptr) | |||
{ | |||
ScopedPointer<KeyboardFocusTraverser> traverser (createFocusTraverser()); | |||
if (traverser != nullptr) | |||
if (ScopedPointer<KeyboardFocusTraverser> traverser = createFocusTraverser()) | |||
{ | |||
auto* nextComp = moveToNext ? traverser->getNextComponent (this) | |||
: traverser->getPreviousComponent (this); | |||
@@ -762,8 +762,8 @@ public: | |||
template <class TargetClass> | |||
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 nullptr; | |||
@@ -289,13 +289,13 @@ public: | |||
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 | |||
{ | |||
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 */ | |||
}; | |||
@@ -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 | |||
{ | |||
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, | |||
const bool shouldParseTransform = true, | |||
AffineTransform* additonalTransform = nullptr) const | |||
@@ -718,9 +688,6 @@ private: | |||
return newState.parseShape (xml, path, false, additonalTransform); | |||
} | |||
if (xml->hasTagName ("use")) | |||
return useShape (xml, path); | |||
auto dp = new DrawablePath(); | |||
setCommonAttributes (*dp, xml); | |||
dp->setFill (Colours::transparentBlack); | |||
@@ -1159,7 +1126,7 @@ private: | |||
if (getStyleAttribute (xml, "font-weight").containsIgnoreCase ("bold")) | |||
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, | |||
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 Timer | |||
{ | |||
@@ -63,7 +63,7 @@ | |||
@see FileChooser | |||
*/ | |||
class JUCE_API FileChooserDialogBox : public ResizableWindow, | |||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private Button::Listener, | |||
private FileBrowserListener | |||
{ | |||
public: | |||
@@ -37,7 +37,7 @@ | |||
class JUCE_API FileSearchPathListComponent : public Component, | |||
public SettableTooltipClient, | |||
public FileDragAndDropTarget, | |||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private Button::Listener, | |||
private ListBoxModel | |||
{ | |||
public: | |||
@@ -65,8 +65,8 @@ class JUCE_API FilenameComponent : public Component, | |||
public SettableTooltipClient, | |||
public FileDragAndDropTarget, | |||
private AsyncUpdater, | |||
private ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private ComboBoxListener | |||
private Button::Listener, | |||
private ComboBox::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -129,6 +129,9 @@ | |||
#undef KeyPress | |||
#endif | |||
#include <map> | |||
#include <set> | |||
//============================================================================== | |||
namespace juce | |||
{ | |||
@@ -256,6 +259,11 @@ extern bool juce_areThereAnyAlwaysOnTopWindows(); | |||
// these classes are C++11-only | |||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
#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 | |||
#if JUCE_IOS || JUCE_WINDOWS | |||
@@ -157,6 +157,7 @@ class KeyPressMappingSet; | |||
class ApplicationCommandManagerListener; | |||
class DrawableButton; | |||
class FlexBox; | |||
class Grid; | |||
#include "mouse/juce_MouseCursor.h" | |||
#include "mouse/juce_MouseListener.h" | |||
@@ -294,6 +295,14 @@ class FlexBox; | |||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
#include "layout/juce_FlexItem.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 | |||
} |
@@ -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, | |||
public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||
public Button::Listener | |||
{ | |||
public: | |||
BehindFrontTabComp (TabbedButtonBar& tb) : owner (tb) | |||
@@ -311,6 +311,7 @@ public: | |||
virtual Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) = 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 drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) = 0; | |||
virtual void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) = 0; | |||
@@ -173,6 +173,11 @@ LookAndFeel_V2::LookAndFeel_V2() | |||
BubbleComponent::backgroundColourId, 0xeeeeeebb, | |||
BubbleComponent::outlineColourId, 0x77000000, | |||
TableHeaderComponent::textColourId, 0xff000000, | |||
TableHeaderComponent::backgroundColourId, 0xffe8ebf9, | |||
TableHeaderComponent::outlineColourId, 0x33000000, | |||
TableHeaderComponent::highlightColourId, 0x8899aadd, | |||
DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour, | |||
DirectoryContentsDisplayComponent::textColourId, 0xff000000, | |||
@@ -336,11 +341,9 @@ void LookAndFeel_V2::drawToggleButton (Graphics& g, ToggleButton& button, | |||
if (! button.isEnabled()) | |||
g.setOpacity (0.5f); | |||
const int textX = (int) tickWidth + 5; | |||
g.drawFittedText (button.getButtonText(), | |||
textX, 0, | |||
button.getWidth() - textX - 2, button.getHeight(), | |||
button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 5) | |||
.withTrimmedRight (2), | |||
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 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.fillRect (x, y, w, h); | |||
g.fillRect (boxArea); | |||
g.setColour (Colour (0x80000000)); | |||
g.drawRect (x, y, w, h); | |||
g.drawRect (boxArea); | |||
const float size = boxSize / 2 + 1.0f; | |||
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) | |||
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&) | |||
@@ -1106,6 +1109,8 @@ void LookAndFeel_V2::preparePopupMenuWindow (Component&) {} | |||
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) | |||
{ | |||
@@ -2166,6 +2171,11 @@ void LookAndFeel_V2::fillTabButtonShape (TabBarButton& button, Graphics& g, cons | |||
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) | |||
{ | |||
const Rectangle<float> area (button.getTextArea().toFloat()); | |||
@@ -2176,7 +2186,7 @@ void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool | |||
if (button.getTabbedButtonBar().isVertical()) | |||
std::swap (length, depth); | |||
Font font (depth * 0.6f); | |||
Font font (getTabButtonFont (button, depth)); | |||
font.setUnderline (button.hasKeyboardFocus (false)); | |||
AffineTransform t; | |||
@@ -2323,26 +2333,33 @@ void LookAndFeel_V2::drawTableHeaderBackground (Graphics& g, TableHeaderComponen | |||
Rectangle<int> area (header.getLocalBounds()); | |||
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)); | |||
g.fillRect (area); | |||
g.setColour (Colour (0x33000000)); | |||
g.setColour (header.findColour (TableHeaderComponent::outlineColourId)); | |||
g.fillRect (area.removeFromBottom (1)); | |||
for (int i = header.getNumColumns (true); --i >= 0;) | |||
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 columnFlags) | |||
{ | |||
auto highlightColour = header.findColour (TableHeaderComponent::highlightColourId); | |||
if (isMouseDown) | |||
g.fillAll (Colour (0x8899aadd)); | |||
g.fillAll (highlightColour); | |||
else if (isMouseOver) | |||
g.fillAll (Colour (0x5599aadd)); | |||
g.fillAll (highlightColour.withMultipliedAlpha (0.625f)); | |||
Rectangle<int> area (width, height); | |||
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.setColour (Colours::black); | |||
g.setColour (header.findColour (TableHeaderComponent::textColourId)); | |||
g.setFont (Font (height * 0.5f, Font::bold)); | |||
g.drawFittedText (columnName, area, Justification::centredLeft, 1); | |||
} | |||
@@ -182,6 +182,8 @@ public: | |||
bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) override; | |||
int getPopupMenuBorderSize() override; | |||
//============================================================================== | |||
void drawComboBox (Graphics&, int width, int height, bool isButtonDown, | |||
int buttonX, int buttonY, int buttonW, int buttonH, | |||
@@ -272,6 +274,7 @@ public: | |||
Rectangle<int> getTabButtonExtraComponentBounds (const TabBarButton&, Rectangle<int>& textArea, Component& extraComp) 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 drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) override; | |||
void drawTabAreaBehindFrontButton (TabbedButtonBar&, Graphics&, int w, int h) override; | |||
@@ -289,9 +292,9 @@ public: | |||
//============================================================================== | |||
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; | |||
@@ -39,6 +39,8 @@ LookAndFeel_V3::LookAndFeel_V3() | |||
setColour (Slider::thumbColourId, Colour (0xffddddff)); | |||
setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); | |||
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() {} | |||
@@ -153,14 +155,15 @@ void LookAndFeel_V3::drawButtonBackground (Graphics& g, Button& button, const Co | |||
void LookAndFeel_V3::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header) | |||
{ | |||
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.setColour (Colours::white.withAlpha (0.6f)); | |||
g.setColour (header.findColour (TableHeaderComponent::backgroundColourId)); | |||
g.fillRect (r); | |||
g.setColour (Colours::black.withAlpha (0.5f)); | |||
g.setColour (outlineColour); | |||
for (int i = header.getNumColumns (true); --i >= 0;) | |||
g.fillRect (header.getColumnPosition (i).removeFromRight (1)); | |||
@@ -333,11 +333,9 @@ void LookAndFeel_V4::drawToggleButton (Graphics& g, ToggleButton& button, | |||
if (! button.isEnabled()) | |||
g.setOpacity (0.5f); | |||
const auto textX = roundToInt (tickWidth) + 10; | |||
g.drawFittedText (button.getButtonText(), | |||
textX, 0, | |||
button.getWidth() - textX - 2, button.getHeight(), | |||
button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 10) | |||
.withTrimmedRight (2), | |||
Justification::centredLeft, 10); | |||
} | |||
@@ -27,7 +27,6 @@ | |||
namespace PopupMenuSettings | |||
{ | |||
const int scrollZone = 24; | |||
const int borderSize = 2; | |||
const int dismissCommandId = 0x6287345f; | |||
static bool menuWasHiddenBecauseOfAppChange = false; | |||
@@ -88,7 +87,7 @@ struct ItemComponent : public Component | |||
int itemW = 80; | |||
int itemH = 16; | |||
getIdealSize (itemW, itemH, standardItemHeight); | |||
setSize (itemW, jlimit (2, 600, itemH)); | |||
setSize (itemW, jlimit (1, 600, itemH)); | |||
addMouseListener (&parent, false); | |||
} | |||
@@ -290,7 +289,8 @@ public: | |||
auto& lf = getLookAndFeel(); | |||
if (parentComponent != nullptr) | |||
lf.drawResizableFrame (g, getWidth(), getHeight(), BorderSize<int> (PopupMenuSettings::borderSize)); | |||
lf.drawResizableFrame (g, getWidth(), getHeight(), | |||
BorderSize<int> (getLookAndFeel().getPopupMenuBorderSize())); | |||
if (canScroll()) | |||
{ | |||
@@ -598,7 +598,7 @@ public: | |||
return parentComponent->getLocalArea (nullptr, | |||
parentComponent->getScreenBounds() | |||
.reduced (PopupMenuSettings::borderSize) | |||
.reduced (getLookAndFeel().getPopupMenuBorderSize()) | |||
.getIntersection (parentArea)); | |||
} | |||
@@ -666,6 +666,9 @@ public: | |||
else | |||
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(); | |||
if (target.getCentreY() > parentArea.getCentreY()) | |||
y = jmax (parentArea.getY(), target.getBottom() - heightToUse); | |||
@@ -711,7 +714,7 @@ public: | |||
needsToScroll = contentHeight > actualH; | |||
width = updateYPositions(); | |||
height = actualH + PopupMenuSettings::borderSize * 2; | |||
height = actualH + getLookAndFeel().getPopupMenuBorderSize() * 2; | |||
} | |||
int workOutBestSize (const int maxMenuW) | |||
@@ -733,7 +736,7 @@ public: | |||
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); | |||
totalW += colW; | |||
@@ -832,7 +835,7 @@ public: | |||
childYOffset = jmax (childYOffset, 0); | |||
else if (delta > 0) | |||
childYOffset = jmin (childYOffset, | |||
contentHeight - windowPos.getHeight() + PopupMenuSettings::borderSize); | |||
contentHeight - windowPos.getHeight() + getLookAndFeel().getPopupMenuBorderSize()); | |||
updateYPositions(); | |||
} | |||
@@ -857,7 +860,7 @@ public: | |||
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) | |||
{ | |||
@@ -280,6 +280,8 @@ public: | |||
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. | |||
Note that native macOS menus do not support custom components. | |||
@see CustomComponent | |||
*/ | |||
void addCustomItem (int itemResultID, | |||
@@ -296,6 +298,8 @@ public: | |||
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 | |||
be triggered, so itemResultID is not used. | |||
Note that native macOS menus do support custom components. | |||
*/ | |||
void addCustomItem (int itemResultID, | |||
Component* customComponent, | |||
@@ -723,6 +727,8 @@ public: | |||
/** Return true if you want your popup menus to scale with the target component's AffineTransform | |||
or scale factor */ | |||
virtual bool shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options& options) = 0; | |||
virtual int getPopupMenuBorderSize() = 0; | |||
}; | |||
private: | |||
@@ -84,13 +84,17 @@ public: | |||
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 | |||
// 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) | |||
@@ -578,6 +582,7 @@ bool MouseInputSource::hasMouseWheel() const noexcept | |||
int MouseInputSource::getIndex() const noexcept { return pimpl->index; } | |||
bool MouseInputSource::isDragging() const noexcept { return pimpl->isDragging(); } | |||
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(); } | |||
float MouseInputSource::getCurrentPressure() const noexcept { return pimpl->pressure; } | |||
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.. | |||
if (s->isDragging() && ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) | |||
{ | |||
s->lastScreenPos = s->getScreenPosition(); | |||
s->lastScreenPos = s->getRawScreenPosition(); | |||
s->triggerFakeMove(); | |||
anyDragging = true; | |||
} | |||
@@ -105,6 +105,9 @@ public: | |||
/** Returns the last-known screen position of this source. */ | |||
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 | |||
held down on this device. | |||
*/ | |||
@@ -31,6 +31,11 @@ extern juce::JUCEApplicationBase* juce_CreateApplication(); // (from START_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, | |||
jstring appFile, jstring appDataDir)) | |||
@@ -83,6 +88,18 @@ JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME, quitApp, void, (JNIEnv* 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) \ | |||
METHOD (drawBitmap, "drawBitmap", "([IIIFFIIZLandroid/graphics/Paint;)V") \ | |||
@@ -248,20 +265,20 @@ public: | |||
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 | |||
{ | |||
return relativePosition + getScreenPosition().toFloat(); | |||
return relativePosition + getScreenPosition(); | |||
} | |||
Point<float> globalToLocal (Point<float> screenPosition) override | |||
{ | |||
return screenPosition - getScreenPosition().toFloat(); | |||
return screenPosition - getScreenPosition(); | |||
} | |||
void setMinimised (bool /*shouldBeMinimised*/) override | |||
@@ -390,7 +407,7 @@ public: | |||
void handleMouseDownCallback (int index, Point<float> sysPos, int64 time) | |||
{ | |||
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. | |||
handleMouseEvent (MouseInputSource::InputSourceType::touch, pos, currentModifiers.withoutMouseButtons(), | |||
@@ -403,7 +420,7 @@ public: | |||
void handleMouseDragCallback (int index, Point<float> pos, int64 time) | |||
{ | |||
pos /= scale; | |||
lastMousePos = pos; | |||
lastMousePos = localToGlobal (pos); | |||
jassert (index < 64); | |||
touchesDown = (touchesDown | (1 << (index & 63))); | |||
@@ -415,7 +432,7 @@ public: | |||
void handleMouseUpCallback (int index, Point<float> pos, int64 time) | |||
{ | |||
pos /= scale; | |||
lastMousePos = pos; | |||
lastMousePos = localToGlobal (pos); | |||
jassert (index < 64); | |||
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 | |||
{ | |||
return true; | |||
@@ -690,7 +719,37 @@ double Desktop::getDefaultMasterScale() | |||
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; | |||
} | |||
@@ -100,9 +100,9 @@ public: | |||
{ | |||
NSMenu* superMenu = [menu supermenu]; | |||
auto menuNames = currentModel->getMenuBarNames(); | |||
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu]; | |||
auto indexOfMenu = (int) [superMenu indexOfItemWithSubmenu: menu] - 1; | |||
[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();) | |||
addMenuItem (iter, menu, 1, indexOfMenu); | |||
@@ -57,6 +57,46 @@ static bool shouldDeactivateTitleBar = true; | |||
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 | |||
enum | |||
{ | |||
@@ -37,7 +37,7 @@ | |||
@see PropertyComponent | |||
*/ | |||
class JUCE_API BooleanPropertyComponent : public PropertyComponent, | |||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private Button::Listener | |||
{ | |||
protected: | |||
//============================================================================== | |||
@@ -37,7 +37,7 @@ | |||
@see PropertyComponent | |||
*/ | |||
class JUCE_API ButtonPropertyComponent : public PropertyComponent, | |||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||
private Button::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -25,7 +25,7 @@ | |||
*/ | |||
class ChoicePropertyComponent::RemapperValueSource : public Value::ValueSource, | |||
private ValueListener | |||
private Value::Listener | |||
{ | |||
public: | |||
RemapperValueSource (const Value& source, const Array<var>& map) | |||
@@ -47,7 +47,7 @@ | |||
@see PropertyComponent, PropertyPanel | |||
*/ | |||
class JUCE_API ChoicePropertyComponent : public PropertyComponent, | |||
private ComboBoxListener // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||
private ComboBox::Listener | |||
{ | |||
protected: | |||
/** Creates the component. | |||
@@ -34,7 +34,7 @@ | |||
@see PropertyComponent, Slider | |||
*/ | |||
class JUCE_API SliderPropertyComponent : public PropertyComponent, | |||
private SliderListener // (can't use Slider::Listener due to idiotic VC2005 bug) | |||
private Slider::Listener | |||
{ | |||
protected: | |||
//============================================================================== | |||
@@ -627,7 +627,7 @@ void ComboBox::removeListener (ComboBoxListener* listener) { listeners.remove | |||
void ComboBox::handleAsyncUpdate() | |||
{ | |||
Component::BailOutChecker checker (this); | |||
listeners.callChecked (checker, &ComboBoxListener::comboBoxChanged, this); // (can't use ComboBox::Listener due to idiotic VC2005 bug) | |||
listeners.callChecked (checker, &ComboBox::Listener::comboBoxChanged, this); | |||
} | |||
void ComboBox::sendChange (const NotificationType notification) | |||
@@ -44,8 +44,8 @@ | |||
*/ | |||
class JUCE_API ComboBox : public Component, | |||
public SettableTooltipClient, | |||
public LabelListener, // (can't use Label::Listener due to idiotic VC2005 bug) | |||
public ValueListener, | |||
public Label::Listener, | |||
public Value::Listener, | |||
private AsyncUpdater | |||
{ | |||
public: | |||
@@ -416,7 +416,7 @@ void Label::removeListener (LabelListener* const listener) | |||
void Label::callChangeListeners() | |||
{ | |||
Component::BailOutChecker checker (this); | |||
listeners.callChecked (checker, &LabelListener::labelTextChanged, this); // (can't use Label::Listener due to idiotic VC2005 bug) | |||
listeners.callChecked (checker, &Label::Listener::labelTextChanged, this); | |||
} | |||
//============================================================================== | |||
@@ -34,9 +34,9 @@ | |||
*/ | |||
class JUCE_API Label : public Component, | |||
public SettableTooltipClient, | |||
protected TextEditorListener, | |||
protected TextEditor::Listener, | |||
private ComponentListener, | |||
private ValueListener | |||
private Value::Listener | |||
{ | |||
public: | |||
//============================================================================== | |||
@@ -24,37 +24,16 @@ | |||
============================================================================== | |||
*/ | |||
class Slider::Pimpl : public AsyncUpdater, | |||
public ButtonListener, // (can't use Button::Listener due to idiotic VC2005 bug) | |||
public LabelListener, | |||
public ValueListener | |||
class Slider::Pimpl : private AsyncUpdater, | |||
private Button::Listener, | |||
private Label::Listener, | |||
private Value::Listener | |||
{ | |||
public: | |||
Pimpl (Slider& s, SliderStyle sliderStyle, TextEntryBoxPosition textBoxPosition) | |||
: owner (s), | |||
style (sliderStyle), | |||
lastCurrentValue (0), lastValueMin (0), lastValueMax (0), | |||
minimum (0), maximum (10), interval (0), doubleClickReturnValue (0), | |||
skewFactor (1.0), symmetricSkew (false), velocityModeSensitivity (1.0), | |||
velocityModeOffset (0.0), velocityModeThreshold (1), | |||
sliderRegionStart (0), sliderRegionSize (1), sliderBeingDragged (-1), | |||
pixelsForFullDragExtent (250), | |||
textBoxPos (textBoxPosition), | |||
numDecimalPlaces (7), | |||
textBoxWidth (80), textBoxHeight (20), | |||
incDecButtonMode (incDecButtonsNotDraggable), | |||
editableText (true), | |||
doubleClickToValue (false), | |||
isVelocityBased (false), | |||
userKeyOverridesVelocity (true), | |||
incDecButtonsSideBySide (false), | |||
sendChangeOnlyOnRelease (false), | |||
popupDisplayEnabled (false), | |||
menuEnabled (false), | |||
useDragEvents (false), | |||
scrollWheelEnabled (true), | |||
snapsToMousePos (true), | |||
parentForPopupDisplay (nullptr) | |||
textBoxPos (textBoxPosition) | |||
{ | |||
rotaryParams.startAngleRadians = float_Pi * 1.2f; | |||
rotaryParams.endAngleRadians = float_Pi * 2.8f; | |||
@@ -139,11 +118,13 @@ public: | |||
int v = std::abs (roundToInt (newInt * 10000000)); | |||
if (v > 0) | |||
{ | |||
while ((v % 10) == 0) | |||
{ | |||
--numDecimalPlaces; | |||
v /= 10; | |||
} | |||
} | |||
} | |||
// keep the current values inside the new range.. | |||
@@ -201,9 +182,7 @@ public: | |||
updateText(); | |||
owner.repaint(); | |||
if (popupDisplay != nullptr) | |||
popupDisplay->updatePosition (owner.getTextFromValue (newValue)); | |||
updatePopupDisplay (newValue); | |||
triggerChangeMessage (notification); | |||
} | |||
@@ -238,9 +217,7 @@ public: | |||
lastValueMin = newValue; | |||
valueMin = newValue; | |||
owner.repaint(); | |||
if (popupDisplay != nullptr) | |||
popupDisplay->updatePosition (owner.getTextFromValue (newValue)); | |||
updatePopupDisplay (newValue); | |||
triggerChangeMessage (notification); | |||
} | |||
@@ -275,9 +252,7 @@ public: | |||
lastValueMax = newValue; | |||
valueMax = newValue; | |||
owner.repaint(); | |||
if (popupDisplay != nullptr) | |||
popupDisplay->updatePosition (owner.getTextFromValue (valueMax.getValue())); | |||
updatePopupDisplay (valueMax.getValue()); | |||
triggerChangeMessage (notification); | |||
} | |||
@@ -344,7 +319,7 @@ public: | |||
Component::BailOutChecker checker (&owner); | |||
Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug) | |||
listeners.callChecked (checker, &SliderListener::sliderValueChanged, slider); // (can't use Slider::Listener due to idiotic VC2005 bug) | |||
listeners.callChecked (checker, &Slider::Listener::sliderValueChanged, slider); | |||
} | |||
void sendDragStart() | |||
@@ -353,7 +328,7 @@ public: | |||
Component::BailOutChecker checker (&owner); | |||
Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug) | |||
listeners.callChecked (checker, &SliderListener::sliderDragStarted, slider); | |||
listeners.callChecked (checker, &Slider::Listener::sliderDragStarted, slider); | |||
} | |||
void sendDragEnd() | |||
@@ -364,7 +339,7 @@ public: | |||
Component::BailOutChecker checker (&owner); | |||
Slider* slider = &owner; // (must use an intermediate variable here to avoid a VS2005 compiler bug) | |||
listeners.callChecked (checker, &SliderListener::sliderDragEnded, slider); | |||
listeners.callChecked (checker, &Slider::Listener::sliderDragEnded, slider); | |||
} | |||
struct DragInProgress | |||
@@ -382,9 +357,17 @@ public: | |||
if (style == IncDecButtons) | |||
{ | |||
const double delta = (button == incButton) ? interval : -interval; | |||
auto newValue = owner.snapValue (getValue() + delta, notDragging); | |||
DragInProgress drag (*this); | |||
setValue (owner.snapValue (getValue() + delta, notDragging), sendNotificationSync); | |||
if (currentDrag != nullptr) | |||
{ | |||
setValue (newValue, sendNotificationSync); | |||
} | |||
else | |||
{ | |||
DragInProgress drag (*this); | |||
setValue (newValue, sendNotificationSync); | |||
} | |||
} | |||
} | |||
@@ -693,6 +676,7 @@ public: | |||
if (dx * dx + dy * dy > 25.0f) | |||
{ | |||
double angle = std::atan2 ((double) dx, (double) -dy); | |||
while (angle < 0.0) | |||
angle += double_Pi * 2.0; | |||
@@ -743,12 +727,12 @@ public: | |||
|| ((style == LinearHorizontal || style == LinearVertical || style == LinearBar || style == LinearBarVertical) | |||
&& ! snapsToMousePos)) | |||
{ | |||
const float mouseDiff = (style == RotaryHorizontalDrag | |||
|| style == LinearHorizontal | |||
|| style == LinearBar | |||
|| (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||
? e.position.x - mouseDragStartPos.x | |||
: mouseDragStartPos.y - e.position.y; | |||
auto mouseDiff = (style == RotaryHorizontalDrag | |||
|| style == LinearHorizontal | |||
|| style == LinearBar | |||
|| (style == IncDecButtons && incDecDragDirectionIsHorizontal())) | |||
? e.position.x - mouseDragStartPos.x | |||
: mouseDragStartPos.y - e.position.y; | |||
newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
@@ -761,8 +745,8 @@ public: | |||
} | |||
else if (style == RotaryHorizontalVerticalDrag) | |||
{ | |||
const float mouseDiff = (e.position.x - mouseDragStartPos.x) | |||
+ (mouseDragStartPos.y - e.position.y); | |||
auto mouseDiff = (e.position.x - mouseDragStartPos.x) | |||
+ (mouseDragStartPos.y - e.position.y); | |||
newPos = owner.valueToProportionOfLength (valueOnMouseDown) | |||
+ mouseDiff * (1.0 / pixelsForFullDragExtent); | |||
@@ -822,6 +806,7 @@ public: | |||
useDragEvents = false; | |||
mouseDragStartPos = mousePosWhenLastDragged = e.position; | |||
currentDrag = nullptr; | |||
popupDisplay = nullptr; | |||
if (owner.isEnabled()) | |||
{ | |||
@@ -854,17 +839,10 @@ public: | |||
: currentValue)).getValue(); | |||
valueOnMouseDown = valueWhenLastDragged; | |||
if (popupDisplayEnabled) | |||
if (showPopupOnDrag || showPopupOnHover) | |||
{ | |||
PopupDisplayComponent* const popup = new PopupDisplayComponent (owner); | |||
popupDisplay = popup; | |||
if (parentForPopupDisplay != nullptr) | |||
parentForPopupDisplay->addChildComponent (popup); | |||
else | |||
popup->addToDesktop (ComponentPeer::windowIsTemporary); | |||
popup->setVisible (true); | |||
showPopupDisplay(); | |||
popupDisplay->stopTimer(); | |||
} | |||
currentDrag = new DragInProgress (*this); | |||
@@ -963,12 +941,57 @@ public: | |||
} | |||
else if (popupDisplay != nullptr) | |||
{ | |||
popupDisplay->startTimer (2000); | |||
popupDisplay->startTimer (200); | |||
} | |||
currentDrag = nullptr; | |||
} | |||
void mouseMove() | |||
{ | |||
if (showPopupOnHover | |||
&& style != TwoValueHorizontal | |||
&& style != TwoValueVertical) | |||
{ | |||
if (owner.isMouseOver (true) && owner.getTopLevelComponent()->hasKeyboardFocus (true)) | |||
{ | |||
if (popupDisplay == nullptr) | |||
showPopupDisplay(); | |||
if (popupDisplay != nullptr) | |||
popupDisplay->startTimer (2000); | |||
} | |||
} | |||
} | |||
void mouseExit() | |||
{ | |||
popupDisplay = nullptr; | |||
} | |||
void showPopupDisplay() | |||
{ | |||
if (popupDisplay == nullptr) | |||
{ | |||
popupDisplay = new PopupDisplayComponent (owner); | |||
updatePopupDisplay (getValue()); | |||
if (parentForPopupDisplay != nullptr) | |||
parentForPopupDisplay->addChildComponent (popupDisplay); | |||
else | |||
popupDisplay->addToDesktop (ComponentPeer::windowIsTemporary); | |||
popupDisplay->setVisible (true); | |||
} | |||
} | |||
void updatePopupDisplay (double valueToShow) | |||
{ | |||
if (popupDisplay != nullptr) | |||
popupDisplay->updatePosition (owner.getTextFromValue (valueToShow)); | |||
} | |||
bool canDoubleClickToValue() const | |||
{ | |||
return doubleClickToValue | |||
@@ -1180,50 +1203,50 @@ public: | |||
Slider& owner; | |||
SliderStyle style; | |||
ListenerList <SliderListener> listeners; | |||
ListenerList<Slider::Listener> listeners; | |||
Value currentValue, valueMin, valueMax; | |||
double lastCurrentValue, lastValueMin, lastValueMax; | |||
double minimum, maximum, interval, doubleClickReturnValue; | |||
double valueWhenLastDragged, valueOnMouseDown, skewFactor, lastAngle; | |||
bool symmetricSkew; | |||
double velocityModeSensitivity, velocityModeOffset, minMaxDiff; | |||
int velocityModeThreshold; | |||
double lastCurrentValue = 0, lastValueMin = 0, lastValueMax = 0; | |||
double minimum = 0, maximum = 10, interval = 0, doubleClickReturnValue = 0; | |||
double valueWhenLastDragged = 0, valueOnMouseDown = 0, skewFactor = 1.0, lastAngle = 0; | |||
bool symmetricSkew = false; | |||
double velocityModeSensitivity = 1.0, velocityModeOffset = 0, minMaxDiff = 0; | |||
int velocityModeThreshold = 1; | |||
RotaryParameters rotaryParams; | |||
Point<float> mouseDragStartPos, mousePosWhenLastDragged; | |||
int sliderRegionStart, sliderRegionSize; | |||
int sliderBeingDragged; | |||
int pixelsForFullDragExtent; | |||
int sliderRegionStart = 0, sliderRegionSize = 1; | |||
int sliderBeingDragged = -1; | |||
int pixelsForFullDragExtent = 250; | |||
Time lastMouseWheelTime; | |||
Rectangle<int> sliderRect; | |||
ScopedPointer<DragInProgress> currentDrag; | |||
TextEntryBoxPosition textBoxPos; | |||
String textSuffix; | |||
int numDecimalPlaces; | |||
int textBoxWidth, textBoxHeight; | |||
IncDecButtonMode incDecButtonMode; | |||
bool editableText; | |||
bool doubleClickToValue; | |||
bool isVelocityBased; | |||
bool userKeyOverridesVelocity; | |||
bool incDecButtonsSideBySide; | |||
bool sendChangeOnlyOnRelease; | |||
bool popupDisplayEnabled; | |||
bool menuEnabled; | |||
bool useDragEvents; | |||
bool incDecDragged; | |||
bool scrollWheelEnabled; | |||
bool snapsToMousePos; | |||
int numDecimalPlaces = 7; | |||
int textBoxWidth = 80, textBoxHeight = 20; | |||
IncDecButtonMode incDecButtonMode = incDecButtonsNotDraggable; | |||
bool editableText = true; | |||
bool doubleClickToValue = false; | |||
bool isVelocityBased = false; | |||
bool userKeyOverridesVelocity = true; | |||
bool incDecButtonsSideBySide = false; | |||
bool sendChangeOnlyOnRelease = false; | |||
bool showPopupOnDrag = false; | |||
bool showPopupOnHover = false; | |||
bool menuEnabled = false; | |||
bool useDragEvents = false; | |||
bool incDecDragged = false; | |||
bool scrollWheelEnabled = true; | |||
bool snapsToMousePos = true; | |||
ScopedPointer<Label> valueBox; | |||
ScopedPointer<Button> incButton, decButton; | |||
//============================================================================== | |||
class PopupDisplayComponent : public BubbleComponent, | |||
public Timer | |||
struct PopupDisplayComponent : public BubbleComponent, | |||
public Timer | |||
{ | |||
public: | |||
PopupDisplayComponent (Slider& s) | |||
: owner (s), | |||
font (s.getLookAndFeel().getSliderPopupFont (s)) | |||
@@ -1267,10 +1290,10 @@ public: | |||
}; | |||
ScopedPointer<PopupDisplayComponent> popupDisplay; | |||
Component* parentForPopupDisplay; | |||
Component* parentForPopupDisplay = nullptr; | |||
//============================================================================== | |||
static double smallestAngleBetween (const double a1, const double a2) noexcept | |||
static double smallestAngleBetween (double a1, double a2) noexcept | |||
{ | |||
return jmin (std::abs (a1 - a2), | |||
std::abs (a1 + double_Pi * 2.0 - a2), | |||
@@ -1311,8 +1334,8 @@ void Slider::init (SliderStyle style, TextEntryBoxPosition textBoxPos) | |||
Slider::~Slider() {} | |||
//============================================================================== | |||
void Slider::addListener (SliderListener* const listener) { pimpl->listeners.add (listener); } | |||
void Slider::removeListener (SliderListener* const listener) { pimpl->listeners.remove (listener); } | |||
void Slider::addListener (Listener* l) { pimpl->listeners.add (l); } | |||
void Slider::removeListener (Listener* l) { pimpl->listeners.remove (l); } | |||
//============================================================================== | |||
Slider::SliderStyle Slider::getSliderStyle() const noexcept { return pimpl->style; } | |||
@@ -1404,10 +1427,11 @@ void Slider::setChangeNotificationOnlyOnRelease (bool onlyNotifyOnRelease) | |||
bool Slider::getSliderSnapsToMousePosition() const noexcept { return pimpl->snapsToMousePos; } | |||
void Slider::setSliderSnapsToMousePosition (const bool shouldSnapToMouse) { pimpl->snapsToMousePos = shouldSnapToMouse; } | |||
void Slider::setPopupDisplayEnabled (const bool enabled, Component* const parentComponentToUse) | |||
void Slider::setPopupDisplayEnabled (bool showOnDrag, bool showOnHover, Component* parent) | |||
{ | |||
pimpl->popupDisplayEnabled = enabled; | |||
pimpl->parentForPopupDisplay = parentComponentToUse; | |||
pimpl->showPopupOnDrag = showOnDrag; | |||
pimpl->showPopupOnHover = showOnHover; | |||
pimpl->parentForPopupDisplay = parent; | |||
} | |||
Component* Slider::getCurrentPopupDisplay() const noexcept { return pimpl->popupDisplay.get(); } | |||
@@ -1570,7 +1594,9 @@ void Slider::resized() { pimpl->resized (getLookAndFeel()); } | |||
void Slider::focusOfChildComponentChanged (FocusChangeType) { repaint(); } | |||
void Slider::mouseDown (const MouseEvent& e) { pimpl->mouseDown (e); } | |||
void Slider::mouseUp (const MouseEvent&) { pimpl->mouseUp(); } | |||
void Slider::mouseUp (const MouseEvent&) { pimpl->mouseUp(); } | |||
void Slider::mouseMove (const MouseEvent&) { pimpl->mouseMove(); } | |||
void Slider::mouseExit (const MouseEvent&) { pimpl->mouseExit(); } | |||
void Slider::modifierKeysChanged (const ModifierKeys& modifiers) | |||
{ | |||
@@ -616,7 +616,7 @@ public: | |||
bool getSliderSnapsToMousePosition() const noexcept; | |||
/** If enabled, this gives the slider a pop-up bubble which appears while the | |||
slider is being dragged. | |||
slider is being dragged or hovered-over. | |||
This can be handy if your slider doesn't have a text-box, so that users can | |||
see the value just when they're changing it. | |||
@@ -627,7 +627,9 @@ public: | |||
transparent window, so if you're using an OS that can't do transparent windows | |||
you'll have to add it to a parent component instead). | |||
*/ | |||
void setPopupDisplayEnabled (bool isEnabled, Component* parentComponentToUse); | |||
void setPopupDisplayEnabled (bool shouldShowOnMouseDrag, | |||
bool shouldShowOnMouseHover, | |||
Component* parentComponentToUse); | |||
/** If a popup display is enabled and is currently visible, this returns the component | |||
that is being shown, or nullptr if none is currently in use. | |||
@@ -904,6 +906,10 @@ public: | |||
void focusOfChildComponentChanged (FocusChangeType) override; | |||
/** @internal */ | |||
void colourChanged() override; | |||
/** @internal */ | |||
void mouseMove (const MouseEvent&) override; | |||
/** @internal */ | |||
void mouseExit (const MouseEvent&) override; | |||
private: | |||
//============================================================================== | |||
@@ -537,7 +537,7 @@ void TableHeaderComponent::paint (Graphics& g) | |||
g.setOrigin (x, 0); | |||
g.reduceClipRegion (0, 0, ci->width, getHeight()); | |||
lf.drawTableHeaderColumn (g, ci->name, ci->id, ci->width, getHeight(), | |||
lf.drawTableHeaderColumn (g, *this, ci->name, ci->id, ci->width, getHeight(), | |||
ci->id == columnIdUnderMouse, | |||
ci->id == columnIdUnderMouse && isMouseButtonDown(), | |||
ci->propertyFlags); | |||
@@ -366,6 +366,24 @@ public: | |||
*/ | |||
virtual void reactToMenuItem (int menuReturnId, int columnIdClicked); | |||
//============================================================================== | |||
/** A set of colour IDs to use to change the colour of various aspects of the TableHeaderComponent. | |||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||
*/ | |||
enum ColourIds | |||
{ | |||
textColourId = 0x1003800, /**< The colour for the text in the header. */ | |||
backgroundColourId = 0x1003810, /**< The colour of the table header background. | |||
It's up to the LookAndFeel how this is used. */ | |||
outlineColourId = 0x1003820, /**< The colour of the table header's outline. */ | |||
highlightColourId = 0x1003830, /**< The colour of the table header background when | |||
the mouse is over or down above the the table | |||
header. It's up to the LookAndFeel to use a | |||
variant of this colour to destiuish between | |||
the down and hover state. */ | |||
}; | |||
//============================================================================== | |||
/** This abstract base class is implemented by LookAndFeel classes. */ | |||
struct JUCE_API LookAndFeelMethods | |||
@@ -374,7 +392,8 @@ public: | |||
virtual void drawTableHeaderBackground (Graphics&, TableHeaderComponent&) = 0; | |||
virtual void drawTableHeaderColumn (Graphics&, const String& columnName, int columnId, | |||
virtual void drawTableHeaderColumn (Graphics&, TableHeaderComponent&, | |||
const String& columnName, int columnId, | |||
int width, int height, | |||
bool isMouseOver, bool isMouseDown, int columnFlags) = 0; | |||
}; | |||