@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_basics", | |||
"name": "JUCE audio and midi data classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -155,7 +155,7 @@ MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | |||
MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, double t) | |||
: timeStamp (t) | |||
{ | |||
const uint8* src = static_cast <const uint8*> (srcData); | |||
const uint8* src = static_cast<const uint8*> (srcData); | |||
unsigned int byte = (unsigned int) *src; | |||
if (byte < 0x80) | |||
@@ -658,6 +658,36 @@ String MidiMessage::getTextFromTextMetaEvent() const | |||
CharPointer_UTF8 (textData + getMetaEventLength())); | |||
} | |||
MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||
{ | |||
jassert (type > 0 && type < 16) | |||
MidiMessage result; | |||
const size_t textSize = text.text.sizeInBytes() - 1; | |||
uint8 header[8]; | |||
size_t n = sizeof (header); | |||
header[--n] = (uint8) (textSize & 0x7f); | |||
for (size_t i = textSize; (i >>= 7) != 0;) | |||
header[--n] = (uint8) ((i & 0x7f) | 0x80); | |||
header[--n] = (uint8) type; | |||
header[--n] = 0xff; | |||
const size_t headerLen = sizeof (header) - n; | |||
uint8* const dest = result.allocateSpace ((int) (headerLen + textSize)); | |||
result.size = (int) (headerLen + textSize); | |||
memcpy (dest, header + n, headerLen); | |||
memcpy (dest + headerLen, text.text.getAddress(), textSize); | |||
return result; | |||
} | |||
bool MidiMessage::isTrackNameEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 3) && (*data == 0xff); } | |||
bool MidiMessage::isTempoMetaEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 81) && (*data == 0xff); } | |||
bool MidiMessage::isMidiChannelMetaEvent() const noexcept { const uint8* data = getRawData(); return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } | |||
@@ -168,9 +168,7 @@ public: | |||
bool isForChannel (int channelNumber) const noexcept; | |||
/** Changes the message's midi channel. | |||
This won't do anything for non-channel messages like sysexes. | |||
@param newChannelNumber the channel number to change it to, in the range 1 to 16 | |||
*/ | |||
void setChannel (int newChannelNumber) noexcept; | |||
@@ -181,17 +179,13 @@ public: | |||
bool isSysEx() const noexcept; | |||
/** Returns a pointer to the sysex data inside the message. | |||
If this event isn't a sysex event, it'll return 0. | |||
@see getSysExDataSize | |||
*/ | |||
const uint8* getSysExData() const noexcept; | |||
/** Returns the size of the sysex data. | |||
This value excludes the 0xf0 header byte and the 0xf7 at the end. | |||
@see getSysExData | |||
*/ | |||
int getSysExDataSize() const noexcept; | |||
@@ -252,15 +246,12 @@ public: | |||
bool isNoteOnOrOff() const noexcept; | |||
/** Returns the midi note number for note-on and note-off messages. | |||
If the message isn't a note-on or off, the value returned is undefined. | |||
@see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber | |||
*/ | |||
int getNoteNumber() const noexcept; | |||
/** Changes the midi note number of a note-on or note-off message. | |||
If the message isn't a note on or off, this will do nothing. | |||
*/ | |||
void setNoteNumber (int newNoteNumber) noexcept; | |||
@@ -320,16 +311,12 @@ public: | |||
//============================================================================== | |||
/** Returns true if the message is a program (patch) change message. | |||
@see getProgramChangeNumber, getGMInstrumentName | |||
*/ | |||
bool isProgramChange() const noexcept; | |||
/** Returns the new program number of a program change message. | |||
If the message isn't a program change, the value returned will be | |||
nonsense. | |||
If the message isn't a program change, the value returned is undefined. | |||
@see isProgramChange, getGMInstrumentName | |||
*/ | |||
int getProgramChangeNumber() const noexcept; | |||
@@ -344,7 +331,6 @@ public: | |||
//============================================================================== | |||
/** Returns true if the message is a pitch-wheel move. | |||
@see getPitchWheelValue, pitchWheel | |||
*/ | |||
bool isPitchWheel() const noexcept; | |||
@@ -433,7 +419,6 @@ public: | |||
/** Returns the controller number of a controller message. | |||
The name of the controller can be looked up using the getControllerName() method. | |||
Note that the value returned is invalid for messages that aren't controller changes. | |||
@see isController, getControllerName, getControllerValue | |||
@@ -443,7 +428,6 @@ public: | |||
/** Returns the controller value from a controller message. | |||
A value 0 to 127 is returned to indicate the new controller position. | |||
Note that the value returned is invalid for messages that aren't controller changes. | |||
@see isController, getControllerNumber | |||
@@ -467,13 +451,11 @@ public: | |||
int value) noexcept; | |||
/** Checks whether this message is an all-notes-off message. | |||
@see allNotesOff | |||
*/ | |||
bool isAllNotesOff() const noexcept; | |||
/** Checks whether this message is an all-sound-off message. | |||
@see allSoundOff | |||
*/ | |||
bool isAllSoundOff() const noexcept; | |||
@@ -520,13 +502,11 @@ public: | |||
int getMetaEventType() const noexcept; | |||
/** Returns a pointer to the data in a meta-event. | |||
@see isMetaEvent, getMetaEventLength | |||
*/ | |||
const uint8* getMetaEventData() const noexcept; | |||
/** Returns the length of the data for a meta-event. | |||
@see isMetaEvent, getMetaEventData | |||
*/ | |||
int getMetaEventLength() const noexcept; | |||
@@ -539,29 +519,28 @@ public: | |||
bool isEndOfTrackMetaEvent() const noexcept; | |||
/** Creates an end-of-track meta-event. | |||
@see isEndOfTrackMetaEvent | |||
*/ | |||
static MidiMessage endOfTrack() noexcept; | |||
/** Returns true if this is an 'track name' meta-event. | |||
You can use the getTextFromTextMetaEvent() method to get the track's name. | |||
*/ | |||
bool isTrackNameEvent() const noexcept; | |||
/** Returns true if this is a 'text' meta-event. | |||
@see getTextFromTextMetaEvent | |||
*/ | |||
bool isTextMetaEvent() const noexcept; | |||
/** Returns the text from a text meta-event. | |||
@see isTextMetaEvent | |||
*/ | |||
String getTextFromTextMetaEvent() const; | |||
/** Creates a text meta-event. */ | |||
static MidiMessage textMetaEvent (int type, StringRef text); | |||
//============================================================================== | |||
/** Returns true if this is a 'tempo' meta-event. | |||
@see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote | |||
@@ -660,7 +639,6 @@ public: | |||
//============================================================================== | |||
/** Returns true if this is a midi start event. | |||
@see midiStart | |||
*/ | |||
bool isMidiStart() const noexcept; | |||
@@ -669,7 +647,6 @@ public: | |||
static MidiMessage midiStart() noexcept; | |||
/** Returns true if this is a midi continue event. | |||
@see midiContinue | |||
*/ | |||
bool isMidiContinue() const noexcept; | |||
@@ -678,7 +655,6 @@ public: | |||
static MidiMessage midiContinue() noexcept; | |||
/** Returns true if this is a midi stop event. | |||
@see midiStop | |||
*/ | |||
bool isMidiStop() const noexcept; | |||
@@ -687,7 +663,6 @@ public: | |||
static MidiMessage midiStop() noexcept; | |||
/** Returns true if this is a midi clock event. | |||
@see midiClock, songPositionPointer | |||
*/ | |||
bool isMidiClock() const noexcept; | |||
@@ -696,13 +671,11 @@ public: | |||
static MidiMessage midiClock() noexcept; | |||
/** Returns true if this is a song-position-pointer message. | |||
@see getSongPositionPointerMidiBeat, songPositionPointer | |||
*/ | |||
bool isSongPositionPointer() const noexcept; | |||
/** Returns the midi beat-number of a song-position-pointer message. | |||
@see isSongPositionPointer, songPositionPointer | |||
*/ | |||
int getSongPositionPointerMidiBeat() const noexcept; | |||
@@ -719,23 +692,18 @@ public: | |||
//============================================================================== | |||
/** Returns true if this is a quarter-frame midi timecode message. | |||
@see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue | |||
*/ | |||
bool isQuarterFrame() const noexcept; | |||
/** Returns the sequence number of a quarter-frame midi timecode message. | |||
This will be a value between 0 and 7. | |||
@see isQuarterFrame, getQuarterFrameValue, quarterFrame | |||
*/ | |||
int getQuarterFrameSequenceNumber() const noexcept; | |||
/** Returns the value from a quarter-frame message. | |||
This will be the lower nybble of the message's data-byte, a value | |||
between 0 and 15 | |||
This will be the lower nybble of the message's data-byte, a value between 0 and 15 | |||
*/ | |||
int getQuarterFrameValue() const noexcept; | |||
@@ -747,7 +715,6 @@ public: | |||
static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; | |||
/** SMPTE timecode types. | |||
Used by the getFullFrameParameters() and fullFrame() methods. | |||
*/ | |||
enum SmpteTimecodeType | |||
@@ -758,8 +725,7 @@ public: | |||
fps30 = 3 | |||
}; | |||
/** Returns true if this is a full-frame midi timecode message. | |||
*/ | |||
/** Returns true if this is a full-frame midi timecode message. */ | |||
bool isFullFrame() const noexcept; | |||
/** Extracts the timecode information from a full-frame midi timecode message. | |||
@@ -773,8 +739,7 @@ public: | |||
int& frames, | |||
SmpteTimecodeType& timecodeType) const noexcept; | |||
/** Creates a full-frame MTC message. | |||
*/ | |||
/** Creates a full-frame MTC message. */ | |||
static MidiMessage fullFrame (int hours, | |||
int minutes, | |||
int seconds, | |||
@@ -799,7 +764,6 @@ public: | |||
}; | |||
/** Checks whether this is an MMC message. | |||
If it is, you can use the getMidiMachineControlCommand() to find out its type. | |||
*/ | |||
bool isMidiMachineControlMessage() const noexcept; | |||
@@ -65,7 +65,7 @@ struct JUCE_API AudioSourceChannelInfo | |||
Only the samples specified by the startSample and numSamples members of this structure | |||
should be affected by the call. | |||
The contents of the buffer when it is passed to the the AudioSource::getNextAudioBlock() | |||
The contents of the buffer when it is passed to the AudioSource::getNextAudioBlock() | |||
method can be treated as the input if the source is performing some kind of filter operation, | |||
but should be cleared if this is not the case - the clearActiveBufferRegion() is | |||
a handy way of doing this. | |||
@@ -114,7 +114,7 @@ void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
{ | |||
if (availableDeviceTypes.size() == 0) | |||
{ | |||
OwnedArray <AudioIODeviceType> types; | |||
OwnedArray<AudioIODeviceType> types; | |||
createAudioDeviceTypes (types); | |||
for (int i = 0; i < types.size(); ++i) | |||
@@ -127,7 +127,7 @@ void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
} | |||
} | |||
const OwnedArray <AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceTypes() | |||
const OwnedArray<AudioIODeviceType>& AudioDeviceManager::getAvailableDeviceTypes() | |||
{ | |||
scanDevicesIfNeeded(); | |||
return availableDeviceTypes; | |||
@@ -147,13 +147,13 @@ void AudioDeviceManager::audioDeviceListChanged() | |||
} | |||
//============================================================================== | |||
static void addIfNotNull (OwnedArray <AudioIODeviceType>& list, AudioIODeviceType* const device) | |||
static void addIfNotNull (OwnedArray<AudioIODeviceType>& list, AudioIODeviceType* const device) | |||
{ | |||
if (device != nullptr) | |||
list.add (device); | |||
} | |||
void AudioDeviceManager::createAudioDeviceTypes (OwnedArray <AudioIODeviceType>& list) | |||
void AudioDeviceManager::createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& list) | |||
{ | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_WASAPI()); | |||
addIfNotNull (list, AudioIODeviceType::createAudioIODeviceType_DirectSound()); | |||
@@ -181,7 +181,7 @@ void AudioDeviceManager::addAudioDeviceType (AudioIODeviceType* newDeviceType) | |||
//============================================================================== | |||
String AudioDeviceManager::initialise (const int numInputChannelsNeeded, | |||
const int numOutputChannelsNeeded, | |||
const XmlElement* const e, | |||
const XmlElement* const xml, | |||
const bool selectDefaultDeviceOnFailure, | |||
const String& preferredDefaultDeviceName, | |||
const AudioDeviceSetup* preferredSetupOptions) | |||
@@ -191,106 +191,127 @@ String AudioDeviceManager::initialise (const int numInputChannelsNeeded, | |||
numInputChansNeeded = numInputChannelsNeeded; | |||
numOutputChansNeeded = numOutputChannelsNeeded; | |||
if (e != nullptr && e->hasTagName ("DEVICESETUP")) | |||
{ | |||
lastExplicitSettings = new XmlElement (*e); | |||
if (xml != nullptr && xml->hasTagName ("DEVICESETUP")) | |||
return initialiseFromXML (*xml, selectDefaultDeviceOnFailure, | |||
preferredDefaultDeviceName, preferredSetupOptions); | |||
String error; | |||
AudioDeviceSetup setup; | |||
return initialiseDefault (preferredDefaultDeviceName, preferredSetupOptions); | |||
} | |||
if (preferredSetupOptions != nullptr) | |||
setup = *preferredSetupOptions; | |||
String AudioDeviceManager::initialiseDefault (const String& preferredDefaultDeviceName, | |||
const AudioDeviceSetup* preferredSetupOptions) | |||
{ | |||
AudioDeviceSetup setup; | |||
if (e->getStringAttribute ("audioDeviceName").isNotEmpty()) | |||
{ | |||
setup.inputDeviceName = setup.outputDeviceName | |||
= e->getStringAttribute ("audioDeviceName"); | |||
} | |||
else | |||
if (preferredSetupOptions != nullptr) | |||
{ | |||
setup = *preferredSetupOptions; | |||
} | |||
else if (preferredDefaultDeviceName.isNotEmpty()) | |||
{ | |||
for (int j = availableDeviceTypes.size(); --j >= 0;) | |||
{ | |||
setup.inputDeviceName = e->getStringAttribute ("audioInputDeviceName"); | |||
setup.outputDeviceName = e->getStringAttribute ("audioOutputDeviceName"); | |||
} | |||
AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); | |||
currentDeviceType = e->getStringAttribute ("deviceType"); | |||
const StringArray outs (type->getDeviceNames (false)); | |||
if (findType (currentDeviceType) == nullptr) | |||
{ | |||
if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) | |||
currentDeviceType = type->getTypeName(); | |||
else if (availableDeviceTypes.size() > 0) | |||
currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
for (int i = 0; i < outs.size(); ++i) | |||
{ | |||
if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
{ | |||
setup.outputDeviceName = outs[i]; | |||
break; | |||
} | |||
} | |||
const StringArray ins (type->getDeviceNames (true)); | |||
for (int i = 0; i < ins.size(); ++i) | |||
{ | |||
if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
{ | |||
setup.inputDeviceName = ins[i]; | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
setup.bufferSize = e->getIntAttribute ("audioDeviceBufferSize"); | |||
setup.sampleRate = e->getDoubleAttribute ("audioDeviceRate"); | |||
insertDefaultDeviceNames (setup); | |||
return setAudioDeviceSetup (setup, false); | |||
} | |||
setup.inputChannels .parseString (e->getStringAttribute ("audioDeviceInChans", "11"), 2); | |||
setup.outputChannels.parseString (e->getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||
String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||
const bool selectDefaultDeviceOnFailure, | |||
const String& preferredDefaultDeviceName, | |||
const AudioDeviceSetup* preferredSetupOptions) | |||
{ | |||
lastExplicitSettings = new XmlElement (xml); | |||
setup.useDefaultInputChannels = ! e->hasAttribute ("audioDeviceInChans"); | |||
setup.useDefaultOutputChannels = ! e->hasAttribute ("audioDeviceOutChans"); | |||
String error; | |||
AudioDeviceSetup setup; | |||
error = setAudioDeviceSetup (setup, true); | |||
if (preferredSetupOptions != nullptr) | |||
setup = *preferredSetupOptions; | |||
midiInsFromXml.clear(); | |||
forEachXmlChildElementWithTagName (*e, c, "MIDIINPUT") | |||
midiInsFromXml.add (c->getStringAttribute ("name")); | |||
if (xml.getStringAttribute ("audioDeviceName").isNotEmpty()) | |||
{ | |||
setup.inputDeviceName = setup.outputDeviceName | |||
= xml.getStringAttribute ("audioDeviceName"); | |||
} | |||
else | |||
{ | |||
setup.inputDeviceName = xml.getStringAttribute ("audioInputDeviceName"); | |||
setup.outputDeviceName = xml.getStringAttribute ("audioOutputDeviceName"); | |||
} | |||
const StringArray allMidiIns (MidiInput::getDevices()); | |||
currentDeviceType = xml.getStringAttribute ("deviceType"); | |||
for (int i = allMidiIns.size(); --i >= 0;) | |||
setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); | |||
if (findType (currentDeviceType) == nullptr) | |||
{ | |||
if (AudioIODeviceType* const type = findType (setup.inputDeviceName, setup.outputDeviceName)) | |||
currentDeviceType = type->getTypeName(); | |||
else if (availableDeviceTypes.size() > 0) | |||
currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
} | |||
if (error.isNotEmpty() && selectDefaultDeviceOnFailure) | |||
error = initialise (numInputChannelsNeeded, numOutputChannelsNeeded, 0, | |||
false, preferredDefaultDeviceName); | |||
setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); | |||
setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); | |||
setDefaultMidiOutput (e->getStringAttribute ("defaultMidiOutput")); | |||
setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | |||
setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||
return error; | |||
} | |||
else | |||
{ | |||
AudioDeviceSetup setup; | |||
setup.useDefaultInputChannels = ! xml.hasAttribute ("audioDeviceInChans"); | |||
setup.useDefaultOutputChannels = ! xml.hasAttribute ("audioDeviceOutChans"); | |||
if (preferredSetupOptions != nullptr) | |||
{ | |||
setup = *preferredSetupOptions; | |||
} | |||
else if (preferredDefaultDeviceName.isNotEmpty()) | |||
{ | |||
for (int j = availableDeviceTypes.size(); --j >= 0;) | |||
{ | |||
AudioIODeviceType* const type = availableDeviceTypes.getUnchecked(j); | |||
error = setAudioDeviceSetup (setup, true); | |||
const StringArray outs (type->getDeviceNames (false)); | |||
midiInsFromXml.clear(); | |||
for (int i = 0; i < outs.size(); ++i) | |||
{ | |||
if (outs[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
{ | |||
setup.outputDeviceName = outs[i]; | |||
break; | |||
} | |||
} | |||
forEachXmlChildElementWithTagName (xml, c, "MIDIINPUT") | |||
midiInsFromXml.add (c->getStringAttribute ("name")); | |||
const StringArray ins (type->getDeviceNames (true)); | |||
const StringArray allMidiIns (MidiInput::getDevices()); | |||
for (int i = 0; i < ins.size(); ++i) | |||
{ | |||
if (ins[i].matchesWildcard (preferredDefaultDeviceName, true)) | |||
{ | |||
setup.inputDeviceName = ins[i]; | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
for (int i = allMidiIns.size(); --i >= 0;) | |||
setMidiInputEnabled (allMidiIns[i], midiInsFromXml.contains (allMidiIns[i])); | |||
insertDefaultDeviceNames (setup); | |||
return setAudioDeviceSetup (setup, false); | |||
} | |||
if (error.isNotEmpty() && selectDefaultDeviceOnFailure) | |||
error = initialise (numInputChansNeeded, numOutputChansNeeded, | |||
nullptr, false, preferredDefaultDeviceName); | |||
setDefaultMidiOutput (xml.getStringAttribute ("defaultMidiOutput")); | |||
return error; | |||
} | |||
String AudioDeviceManager::initialiseWithDefaultDevices (int numInputChannelsNeeded, | |||
int numOutputChannelsNeeded) | |||
{ | |||
lastExplicitSettings = nullptr; | |||
return initialise (numInputChannelsNeeded, numOutputChannelsNeeded, | |||
nullptr, false, String(), nullptr); | |||
} | |||
void AudioDeviceManager::insertDefaultDeviceNames (AudioDeviceSetup& setup) const | |||
@@ -880,7 +901,7 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
{ | |||
if (defaultMidiOutputName != deviceName) | |||
{ | |||
Array <AudioIODeviceCallback*> oldCallbacks; | |||
Array<AudioIODeviceCallback*> oldCallbacks; | |||
{ | |||
const ScopedLock sl (audioCallbackLock); | |||
@@ -915,7 +936,7 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
void AudioDeviceManager::playTestSound() | |||
{ | |||
{ // cunningly nested to swap, unlock and delete in that order. | |||
ScopedPointer <AudioSampleBuffer> oldSound; | |||
ScopedPointer<AudioSampleBuffer> oldSound; | |||
{ | |||
const ScopedLock sl (audioCallbackLock); | |||
@@ -191,7 +191,11 @@ public: | |||
const XmlElement* savedState, | |||
bool selectDefaultDeviceOnFailure, | |||
const String& preferredDefaultDeviceName = String(), | |||
const AudioDeviceSetup* preferredSetupOptions = 0); | |||
const AudioDeviceSetup* preferredSetupOptions = nullptr); | |||
/** Resets everything to a default device setup, clearing any stored settings. */ | |||
String initialiseWithDefaultDevices (int numInputChannelsNeeded, | |||
int numOutputChannelsNeeded); | |||
/** Returns some XML representing the current state of the manager. | |||
@@ -383,7 +387,7 @@ public: | |||
/** Returns a list of the types of device supported. | |||
*/ | |||
const OwnedArray <AudioIODeviceType>& getAvailableDeviceTypes(); | |||
const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes(); | |||
//============================================================================== | |||
/** Creates a list of available types. | |||
@@ -394,7 +398,7 @@ public: | |||
You can override this if your app needs to do something specific, like avoid | |||
using DirectSound devices, etc. | |||
*/ | |||
virtual void createAudioDeviceTypes (OwnedArray <AudioIODeviceType>& types); | |||
virtual void createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& types); | |||
/** Adds a new device type to the list of types. | |||
The manager will take ownership of the object that is passed-in. | |||
@@ -446,30 +450,30 @@ public: | |||
private: | |||
//============================================================================== | |||
OwnedArray <AudioIODeviceType> availableDeviceTypes; | |||
OwnedArray <AudioDeviceSetup> lastDeviceTypeConfigs; | |||
OwnedArray<AudioIODeviceType> availableDeviceTypes; | |||
OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs; | |||
AudioDeviceSetup currentSetup; | |||
ScopedPointer <AudioIODevice> currentAudioDevice; | |||
Array <AudioIODeviceCallback*> callbacks; | |||
ScopedPointer<AudioIODevice> currentAudioDevice; | |||
Array<AudioIODeviceCallback*> callbacks; | |||
int numInputChansNeeded, numOutputChansNeeded; | |||
String currentDeviceType; | |||
BigInteger inputChannels, outputChannels; | |||
ScopedPointer <XmlElement> lastExplicitSettings; | |||
ScopedPointer<XmlElement> lastExplicitSettings; | |||
mutable bool listNeedsScanning; | |||
bool useInputNames; | |||
Atomic<int> inputLevelMeasurementEnabledCount; | |||
double inputLevel; | |||
ScopedPointer <AudioSampleBuffer> testSound; | |||
ScopedPointer<AudioSampleBuffer> testSound; | |||
int testSoundPosition; | |||
AudioSampleBuffer tempBuffer; | |||
StringArray midiInsFromXml; | |||
OwnedArray <MidiInput> enabledMidiInputs; | |||
Array <MidiInputCallback*> midiCallbacks; | |||
OwnedArray<MidiInput> enabledMidiInputs; | |||
Array<MidiInputCallback*> midiCallbacks; | |||
StringArray midiCallbackDevices; | |||
String defaultMidiOutputName; | |||
ScopedPointer <MidiOutput> defaultMidiOutput; | |||
ScopedPointer<MidiOutput> defaultMidiOutput; | |||
CriticalSection audioCallbackLock, midiCallbackLock; | |||
double cpuUsageMs, timeToCpuScale; | |||
@@ -500,6 +504,9 @@ private: | |||
double chooseBestSampleRate (double preferred) const; | |||
int chooseBestBufferSize (int preferred) const; | |||
void insertDefaultDeviceNames (AudioDeviceSetup&) const; | |||
String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*); | |||
String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure, | |||
const String& preferredDefaultDeviceName, const AudioDeviceSetup*); | |||
AudioIODeviceType* findType (const String& inputName, const String& outputName); | |||
AudioIODeviceType* findType (const String& typeName); | |||
@@ -118,7 +118,7 @@ public: | |||
/** | |||
A class for receiving events when audio devices are inserted or removed. | |||
You can register a AudioIODeviceType::Listener with an~AudioIODeviceType object | |||
You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object | |||
using the AudioIODeviceType::addListener() method, and it will be called when | |||
devices of that type are added or removed. | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_devices", | |||
"name": "JUCE audio and midi I/O device classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes to play and record from audio and midi i/o devices.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -123,6 +123,14 @@ private: | |||
{ | |||
if (pendingBytes > 0 && *d >= 0x80) | |||
{ | |||
if (*d == 0xf7) | |||
{ | |||
*dest++ = *d++; | |||
++pendingBytes; | |||
--numBytes; | |||
break; | |||
} | |||
if (*d >= 0xfa || *d == 0xf8) | |||
{ | |||
callback.handleIncomingMidiMessage (input, MidiMessage (*d, time)); | |||
@@ -131,11 +139,15 @@ private: | |||
} | |||
else | |||
{ | |||
if (*d == 0xf7) | |||
pendingBytes = 0; | |||
int used = 0; | |||
const MidiMessage m (d, numBytes, used, 0, time); | |||
if (used > 0) | |||
{ | |||
*dest++ = *d++; | |||
pendingBytes++; | |||
--numBytes; | |||
callback.handleIncomingMidiMessage (input, m); | |||
numBytes -= used; | |||
d += used; | |||
} | |||
break; | |||
@@ -144,7 +156,7 @@ private: | |||
else | |||
{ | |||
*dest++ = *d++; | |||
pendingBytes++; | |||
++pendingBytes; | |||
--numBytes; | |||
} | |||
} | |||
@@ -502,13 +502,9 @@ public: | |||
outputNames.clear(); | |||
outputIds.clear(); | |||
if (juce_libjackHandle == nullptr) | |||
{ | |||
juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | |||
if (juce_libjackHandle == nullptr) | |||
return; | |||
} | |||
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY); | |||
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY); | |||
if (juce_libjackHandle == nullptr) return; | |||
jack_status_t status; | |||
@@ -478,6 +478,12 @@ void MidiOutput::sendMessageNow (const MidiMessage& message) | |||
} | |||
else | |||
{ | |||
midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()); | |||
for (int i = 0; i < 50; ++i) | |||
{ | |||
if (midiOutShortMsg (handle->handle, *(unsigned int*) message.getRawData()) != MIDIERR_NOTREADY) | |||
break; | |||
Sleep (1); | |||
} | |||
} | |||
} |
@@ -43,7 +43,7 @@ class JUCE_API AudioSubsectionReader : public AudioFormatReader | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a AudioSubsectionReader for a given data source. | |||
/** Creates an AudioSubsectionReader for a given data source. | |||
@param sourceReader the source reader from which we'll be taking data | |||
@param subsectionStartSample the sample within the source reader which will be | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_formats", | |||
"name": "JUCE audio file format codecs", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for reading and writing various audio file formats.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -124,8 +124,7 @@ struct AAXClasses | |||
case 6: return AAX_eStemFormat_5_1; | |||
case 7: return AAX_eStemFormat_7_0_DTS; | |||
case 8: return AAX_eStemFormat_7_1_DTS; | |||
default: jassertfalse; break; // hmm - not a valid number of chans.. | |||
default: jassertfalse; break; | |||
} | |||
return AAX_eStemFormat_None; | |||
@@ -139,17 +138,45 @@ struct AAXClasses | |||
case AAX_eStemFormat_Mono: return 1; | |||
case AAX_eStemFormat_Stereo: return 2; | |||
case AAX_eStemFormat_LCR: return 3; | |||
case AAX_eStemFormat_LCRS: | |||
case AAX_eStemFormat_Quad: return 4; | |||
case AAX_eStemFormat_5_0: return 5; | |||
case AAX_eStemFormat_5_1: return 6; | |||
case AAX_eStemFormat_5_1: | |||
case AAX_eStemFormat_6_0: return 6; | |||
case AAX_eStemFormat_6_1: | |||
case AAX_eStemFormat_7_0_SDDS: | |||
case AAX_eStemFormat_7_0_DTS: return 7; | |||
case AAX_eStemFormat_7_1_SDDS: | |||
case AAX_eStemFormat_7_1_DTS: return 8; | |||
default: jassertfalse; break; // hmm - not a valid number of chans.. | |||
default: jassertfalse; break; | |||
} | |||
return 0; | |||
} | |||
static const char* getSpeakerArrangementString (AAX_EStemFormat format) noexcept | |||
{ | |||
switch (format) | |||
{ | |||
case AAX_eStemFormat_Mono: return "M"; | |||
case AAX_eStemFormat_Stereo: return "L R"; | |||
case AAX_eStemFormat_LCR: return "L C R"; | |||
case AAX_eStemFormat_LCRS: return "L C R S"; | |||
case AAX_eStemFormat_Quad: return "L R Ls Rs"; | |||
case AAX_eStemFormat_5_0: return "L C R Ls Rs"; | |||
case AAX_eStemFormat_5_1: return "L C R Ls Rs LFE"; | |||
case AAX_eStemFormat_6_0: return "L C R Ls Cs Rs"; | |||
case AAX_eStemFormat_6_1: return "L C R Ls Cs Rs LFE"; | |||
case AAX_eStemFormat_7_0_SDDS: return "L Lc C Rc R Ls Rs"; | |||
case AAX_eStemFormat_7_1_SDDS: return "L Lc C Rc R Ls Rs LFE"; | |||
case AAX_eStemFormat_7_0_DTS: return "L C R Lss Rss Lsr Rsr"; | |||
case AAX_eStemFormat_7_1_DTS: return "L C R Lss Rss Lsr Rsr LFE"; | |||
default: break; | |||
} | |||
return nullptr; | |||
} | |||
//============================================================================== | |||
struct JUCELibraryRefCount | |||
{ | |||
@@ -652,6 +679,7 @@ struct AAXClasses | |||
void audioProcessorChanged (AudioProcessor* processor) override | |||
{ | |||
++mNumPlugInChanges; | |||
check (Controller()->SetSignalLatency (processor->getLatencySamples())); | |||
} | |||
@@ -855,6 +883,9 @@ struct AAXClasses | |||
AudioProcessor& audioProcessor = getPluginInstance(); | |||
audioProcessor.setSpeakerArrangement (getSpeakerArrangementString (inputStemFormat), | |||
getSpeakerArrangementString (outputStemFormat)); | |||
audioProcessor.setPlayConfigDetails (numberOfInputChannels, numberOfOutputChannels, sampleRate, lastBufferSize); | |||
audioProcessor.prepareToPlay (sampleRate, lastBufferSize); | |||
@@ -931,6 +962,13 @@ struct AAXClasses | |||
// This value needs to match the RTAS wrapper's Type ID, so that | |||
// the host knows that the RTAS/AAX plugins are equivalent. | |||
properties->AddProperty (AAX_eProperty_PlugInID_Native, 'jcaa' + channelConfigIndex); | |||
properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, 'jyaa' + channelConfigIndex); | |||
#if JucePlugin_AAXDisableMultiMono | |||
properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, false); | |||
#else | |||
properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); | |||
#endif | |||
check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); | |||
} | |||
@@ -203,10 +203,8 @@ public: | |||
{ | |||
public: | |||
//============================================================================== | |||
JuceCustomUIView (AudioProcessor* const filter_, | |||
JucePlugInProcess* const process_) | |||
: filter (filter_), | |||
process (process_) | |||
JuceCustomUIView (AudioProcessor* ap, JucePlugInProcess* p) | |||
: filter (ap), process (p) | |||
{ | |||
// setting the size in here crashes PT for some reason, so keep it simple.. | |||
} | |||
@@ -237,7 +235,7 @@ public: | |||
r.bottom = editorComp->getHeight(); | |||
SetRect (&r); | |||
if ((oldRect.right != r.right) || (oldRect.bottom != r.bottom)) | |||
if (oldRect.right != r.right || oldRect.bottom != r.bottom) | |||
startTimer (50); | |||
} | |||
} | |||
@@ -269,8 +267,6 @@ public: | |||
#endif | |||
wrapper = nullptr; | |||
wrapper = new EditorCompWrapper (hostWindow, editorComp, this); | |||
process->touchAllParameters(); | |||
} | |||
} | |||
else | |||
@@ -774,23 +770,13 @@ protected: | |||
// xxx is there an RTAS equivalent? | |||
} | |||
void touchAllParameters() | |||
{ | |||
for (int i = 0; i < juceFilter->getNumParameters(); ++i) | |||
{ | |||
audioProcessorParameterChangeGestureBegin (0, i); | |||
audioProcessorParameterChanged (0, i, juceFilter->getParameter (i)); | |||
audioProcessorParameterChangeGestureEnd (0, i); | |||
} | |||
} | |||
public: | |||
// Need to use an intermediate class here rather than inheriting from AsyncUpdater, so that it can | |||
// be deleted before shutting down juce in our destructor. | |||
class InternalAsyncUpdater : public AsyncUpdater | |||
{ | |||
public: | |||
InternalAsyncUpdater (JucePlugInProcess& owner_) : owner (owner_) {} | |||
InternalAsyncUpdater (JucePlugInProcess& p) : owner (p) {} | |||
void handleAsyncUpdate() { owner.handleAsyncUpdate(); } | |||
private: | |||
@@ -927,29 +913,32 @@ public: | |||
for (int i = 0; i < numConfigs; ++i) | |||
{ | |||
CEffectType* const type | |||
= new CEffectTypeRTAS ('jcaa' + i, | |||
JucePlugin_RTASProductId, | |||
JucePlugin_RTASCategory); | |||
if (channelConfigs[i][0] <= 8 && channelConfigs[i][1] <= 8) | |||
{ | |||
CEffectType* const type | |||
= new CEffectTypeRTAS ('jcaa' + i, | |||
JucePlugin_RTASProductId, | |||
JucePlugin_RTASCategory); | |||
type->DefineTypeNames (createRTASName().toRawUTF8()); | |||
type->DefineSampleRateSupport (eSupports48kAnd96kAnd192k); | |||
type->DefineTypeNames (createRTASName().toRawUTF8()); | |||
type->DefineSampleRateSupport (eSupports48kAnd96kAnd192k); | |||
type->DefineStemFormats (getFormatForChans (channelConfigs [i][0] != 0 ? channelConfigs [i][0] : channelConfigs [i][1]), | |||
getFormatForChans (channelConfigs [i][1] != 0 ? channelConfigs [i][1] : channelConfigs [i][0])); | |||
type->DefineStemFormats (getFormatForChans (channelConfigs [i][0] != 0 ? channelConfigs [i][0] : channelConfigs [i][1]), | |||
getFormatForChans (channelConfigs [i][1] != 0 ? channelConfigs [i][1] : channelConfigs [i][0])); | |||
#if ! JucePlugin_RTASDisableBypass | |||
type->AddGestalt (pluginGestalt_CanBypass); | |||
#endif | |||
#if ! JucePlugin_RTASDisableBypass | |||
type->AddGestalt (pluginGestalt_CanBypass); | |||
#endif | |||
#if JucePlugin_RTASDisableMultiMono | |||
type->AddGestalt (pluginGestalt_DoesntSupportMultiMono); | |||
#endif | |||
#if JucePlugin_RTASDisableMultiMono | |||
type->AddGestalt (pluginGestalt_DoesntSupportMultiMono); | |||
#endif | |||
type->AddGestalt (pluginGestalt_SupportsVariableQuanta); | |||
type->AttachEffectProcessCreator (createNewProcess); | |||
type->AddGestalt (pluginGestalt_SupportsVariableQuanta); | |||
type->AttachEffectProcessCreator (createNewProcess); | |||
AddEffectType (type); | |||
AddEffectType (type); | |||
} | |||
} | |||
} | |||
@@ -105,7 +105,7 @@ public: | |||
const int y = settings->getIntValue ("windowY", -100); | |||
if (x != -100 && y != -100) | |||
setBoundsConstrained (Rectangle<int> (x, y, getWidth(), getHeight())); | |||
setBoundsConstrained (juce::Rectangle<int> (x, y, getWidth(), getHeight())); | |||
else | |||
centreWithSize (getWidth(), getHeight()); | |||
} | |||
@@ -176,7 +176,9 @@ public: | |||
toString128 (info.title, p.getParameterName (index)); | |||
toString128 (info.shortTitle, p.getParameterName (index, 8)); | |||
toString128 (info.units, p.getParameterLabel (index)); | |||
info.stepCount = (Steinberg::int32) p.getParameterNumSteps (index); | |||
const int numSteps = p.getParameterNumSteps (index); | |||
info.stepCount = (Steinberg::int32) (numSteps > 0 && numSteps < 0x7fffffff ? numSteps - 1 : 0); | |||
info.defaultNormalizedValue = p.getParameterDefaultValue (index); | |||
info.unitId = Vst::kRootUnitId; | |||
info.flags = p.isParameterAutomatable (index) ? Vst::ParameterInfo::kCanAutomate : 0; | |||
@@ -752,78 +754,72 @@ public: | |||
tresult PLUGIN_API setIoMode (Vst::IoMode) override { return kNotImplemented; } | |||
tresult PLUGIN_API getRoutingInfo (Vst::RoutingInfo&, Vst::RoutingInfo&) override { return kNotImplemented; } | |||
tresult PLUGIN_API setState (IBStream* state) override | |||
bool readFromMemoryStream (IBStream* state) const | |||
{ | |||
if (state != nullptr) | |||
FUnknownPtr<MemoryStream> s (state); | |||
if (s != nullptr | |||
&& s->getData() != nullptr | |||
&& s->getSize() > 0 | |||
&& s->getSize() < 1024 * 1024 * 100) // (some hosts seem to return junk for the size) | |||
{ | |||
// Reset to the beginning of the stream: | |||
if (state->seek (0, IBStream::kIBSeekSet, nullptr) != kResultTrue) | |||
return kResultFalse; | |||
// Adobe Audition CS6 hack to avoid trying to use corrupted streams: | |||
if (getHostType().isAdobeAudition()) | |||
if (s->getSize() >= 5 && memcmp (s->getData(), "VC2!E", 5) == 0) | |||
return kResultFalse; | |||
Steinberg::int64 end = -1; | |||
pluginInstance->setStateInformation (s->getData(), (int) s->getSize()); | |||
return true; | |||
} | |||
if (end < 0) | |||
{ | |||
FUnknownPtr<ISizeableStream> s (state); | |||
return false; | |||
} | |||
if (s != nullptr) | |||
s->getStreamSize (end); | |||
} | |||
bool readFromUnknownStream (IBStream* state) const | |||
{ | |||
MemoryOutputStream allData; | |||
{ | |||
const size_t bytesPerBlock = 4096; | |||
HeapBlock<char> buffer (bytesPerBlock); | |||
if (end < 0) | |||
for (;;) | |||
{ | |||
FUnknownPtr<MemoryStream> s (state); | |||
int32 bytesRead = 0; | |||
if (s != nullptr) | |||
if (state->read (buffer, (int32) bytesPerBlock, &bytesRead) == kResultTrue && bytesRead > 0) | |||
{ | |||
if (getHostType().isAdobeAudition()) | |||
{ | |||
// Adobe Audition CS6 hack to avoid trying to use corrupted streams: | |||
bool failed = true; | |||
if (const char* const data = s->getData()) | |||
{ | |||
if (s->getSize() >= 5 && data[0] != 'V' && data[1] != 'C' | |||
&& data[2] != '2' && data[3] != '!' && data[4] != 'E') | |||
{ | |||
failed = false; | |||
} | |||
} | |||
else | |||
{ | |||
jassertfalse; | |||
} | |||
if (failed) | |||
return kResultFalse; | |||
} | |||
end = (Steinberg::int64) s->getSize(); | |||
allData.write (buffer, bytesRead); | |||
continue; | |||
} | |||
break; | |||
} | |||
} | |||
if (end <= 0) | |||
return kResultFalse; | |||
const size_t dataSize = allData.getDataSize(); | |||
// Try reading the data, and setting the plugin state: | |||
Steinberg::int32 numBytes = (Steinberg::int32) jmin ((Steinberg::int64) std::numeric_limits<Steinberg::int32>::max(), end); | |||
if (dataSize > 0 && dataSize < 0x7fffffff) | |||
{ | |||
pluginInstance->setStateInformation (allData.getData(), (int) dataSize); | |||
return true; | |||
} | |||
Array<char> buff; | |||
buff.ensureStorageAllocated ((int) numBytes); | |||
void* buffer = buff.getRawDataPointer(); | |||
return false; | |||
} | |||
if (state->read (buffer, numBytes, &numBytes) == kResultTrue | |||
&& buffer != nullptr | |||
&& numBytes > 0) | |||
{ | |||
pluginInstance->setStateInformation (buffer, (int) numBytes); | |||
return kResultTrue; | |||
} | |||
tresult PLUGIN_API setState (IBStream* state) override | |||
{ | |||
if (state == nullptr) | |||
return kInvalidArgument; | |||
return kResultFalse; | |||
} | |||
FUnknownPtr<IBStream> stateRefHolder (state); // just in case the caller hasn't properly ref-counted the stream object | |||
return kInvalidArgument; | |||
if (state->seek (0, IBStream::kIBSeekSet, nullptr) == kResultTrue) | |||
if (readFromMemoryStream (state) || readFromUnknownStream (state)) | |||
return kResultTrue; | |||
return kResultFalse; | |||
} | |||
tresult PLUGIN_API getState (IBStream* state) override | |||
@@ -1084,7 +1080,7 @@ public: | |||
tresult PLUGIN_API process (Vst::ProcessData& data) override | |||
{ | |||
if (pluginInstance == nullptr || processContext.sampleRate <= 0.0) | |||
if (pluginInstance == nullptr) | |||
return kResultFalse; | |||
if (data.processContext != nullptr) | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_plugin_client", | |||
"name": "JUCE audio plugin wrapper classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for building VST, VST3, RTAS, AAX and AU plugins.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -27,11 +27,11 @@ | |||
//============================================================================== | |||
#define JUCE_DECLARE_VST3_COM_REF_METHODS \ | |||
Steinberg::uint32 JUCE_CALLTYPE addRef() override { return (Steinberg::uint32) ++refCount; } \ | |||
Steinberg::uint32 JUCE_CALLTYPE release() override { const int r = --refCount; if (r == 0) delete this; return (Steinberg::uint32) r; } | |||
Steinberg::uint32 PLUGIN_API addRef() override { return (Steinberg::uint32) ++refCount; } \ | |||
Steinberg::uint32 PLUGIN_API release() override { const int r = --refCount; if (r == 0) delete this; return (Steinberg::uint32) r; } | |||
#define JUCE_DECLARE_VST3_COM_QUERY_METHODS \ | |||
Steinberg::tresult PLUGIN_API JUCE_CALLTYPE queryInterface (const Steinberg::TUID, void** obj) override \ | |||
Steinberg::tresult PLUGIN_API queryInterface (const Steinberg::TUID, void** obj) override \ | |||
{ \ | |||
jassertfalse; \ | |||
*obj = nullptr; \ | |||
@@ -51,6 +51,14 @@ static bool doUIDsMatch (const Steinberg::TUID a, const Steinberg::TUID b) noexc | |||
return Steinberg::kResultOk; \ | |||
} | |||
#define TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID(CommonClassType, SourceClassType) \ | |||
if (doUIDsMatch (iid, CommonClassType::iid)) \ | |||
{ \ | |||
addRef(); \ | |||
*obj = (CommonClassType*) static_cast<SourceClassType*> (this); \ | |||
return Steinberg::kResultOk; \ | |||
} | |||
//============================================================================== | |||
static juce::String toString (const Steinberg::char8* string) noexcept { return juce::String (string); } | |||
static juce::String toString (const Steinberg::char16* string) noexcept { return juce::String (juce::CharPointer_UTF16 ((juce::CharPointer_UTF16::CharType*) string)); } | |||
@@ -70,19 +78,15 @@ static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
} | |||
#if JUCE_WINDOWS | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND; | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeHWND; | |||
#else | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView; | |||
static const Steinberg::FIDString defaultVST3WindowType = Steinberg::kPlatformTypeNSView; | |||
#endif | |||
//============================================================================== | |||
/** The equivalent numChannels and speaker arrangements should always | |||
match between this function and fillWithCorrespondingSpeakerArrangements(). | |||
There can only be 1 arrangement per channel count. (i.e.: 4 channels == k31Cine OR k40Cine) | |||
@see fillWithCorrespondingSpeakerArrangements | |||
/** For the sake of simplicity, there can only be 1 arrangement type per channel count. | |||
i.e.: 4 channels == k31Cine OR k40Cine | |||
*/ | |||
static Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numChannels) noexcept | |||
{ | |||
@@ -116,53 +120,6 @@ static Steinberg::Vst::SpeakerArrangement getArrangementForNumChannels (int numC | |||
return (Steinberg::Vst::SpeakerArrangement) bi.toInt64(); | |||
} | |||
/** The equivalent numChannels and speaker arrangements should always | |||
match between this function and getArrangementForNumChannels(). | |||
There can only be 1 arrangement per channel count. (i.e.: 4 channels == k31Cine OR k40Cine) | |||
@see getArrangementForNumChannels | |||
*/ | |||
static void fillWithCorrespondingSpeakerArrangements (Array<Steinberg::Vst::SpeakerArrangement>& destination, | |||
int numChannels) | |||
{ | |||
using namespace Steinberg::Vst::SpeakerArr; | |||
destination.clearQuick(); | |||
if (numChannels <= 0) | |||
{ | |||
destination.add (kEmpty); | |||
return; | |||
} | |||
// The order of the arrangement checks must be descending, since most plugins test for | |||
/// the first arrangement to match their number of specified channels. | |||
if (numChannels > 24) | |||
{ | |||
juce::BigInteger bi; | |||
bi.setRange (0, jmin (numChannels, (int) (sizeof (Steinberg::Vst::SpeakerArrangement) * 8)), true); | |||
destination.add ((Steinberg::Vst::SpeakerArrangement) bi.toInt64()); | |||
} | |||
if (numChannels >= 24) destination.add ((Steinberg::Vst::SpeakerArrangement) 1929904127); // k222 | |||
if (numChannels >= 14) destination.add (k131); | |||
if (numChannels >= 13) destination.add (k130); | |||
if (numChannels >= 12) destination.add (k111); | |||
if (numChannels >= 11) destination.add (k101); | |||
if (numChannels >= 10) destination.add (k91); | |||
if (numChannels >= 9) destination.add (k90); | |||
if (numChannels >= 8) destination.add (k71CineFullFront); | |||
if (numChannels >= 7) destination.add (k61Cine); | |||
if (numChannels >= 6) destination.add (k51); | |||
if (numChannels >= 5) destination.add (k50); | |||
if (numChannels >= 4) destination.add (k31Cine); | |||
if (numChannels >= 3) destination.add (k30Cine); | |||
if (numChannels >= 2) destination.add (kStereo); | |||
if (numChannels >= 1) destination.add (kMono); | |||
} | |||
//============================================================================== | |||
template <class ObjectType> | |||
class ComSmartPtr | |||
@@ -98,7 +98,7 @@ static void fillDescriptionWith (PluginDescription& description, ObjectType& obj | |||
description.version = toString (object.version).trim(); | |||
description.category = toString (object.subCategories).trim(); | |||
if (description.manufacturerName.isEmpty()) | |||
if (description.manufacturerName.trim().isEmpty()) | |||
description.manufacturerName = toString (object.vendor).trim(); | |||
} | |||
@@ -459,13 +459,13 @@ public: | |||
{ | |||
*obj = nullptr; | |||
if (! doIdsMatch (cid, iid)) | |||
if (! doUIDsMatch (cid, iid)) | |||
{ | |||
jassertfalse; | |||
return kInvalidArgument; | |||
} | |||
if (doIdsMatch (cid, Vst::IMessage::iid) && doIdsMatch (iid, Vst::IMessage::iid)) | |||
if (doUIDsMatch (cid, Vst::IMessage::iid) && doUIDsMatch (iid, Vst::IMessage::iid)) | |||
{ | |||
ComSmartPtr<Message> m (new Message (*this, attributeList)); | |||
messageQueue.add (m); | |||
@@ -473,7 +473,7 @@ public: | |||
*obj = m; | |||
return kResultOk; | |||
} | |||
else if (doIdsMatch (cid, Vst::IAttributeList::iid) && doIdsMatch (iid, Vst::IAttributeList::iid)) | |||
else if (doUIDsMatch (cid, Vst::IAttributeList::iid) && doUIDsMatch (iid, Vst::IAttributeList::iid)) | |||
{ | |||
ComSmartPtr<AttributeList> l (new AttributeList (this)); | |||
l->addRef(); | |||
@@ -526,7 +526,7 @@ public: | |||
//============================================================================== | |||
tresult PLUGIN_API queryInterface (const TUID iid, void** obj) override | |||
{ | |||
if (doIdsMatch (iid, Vst::IAttributeList::iid)) | |||
if (doUIDsMatch (iid, Vst::IAttributeList::iid)) | |||
{ | |||
*obj = attributeList.get(); | |||
return kResultOk; | |||
@@ -539,6 +539,7 @@ public: | |||
TEST_FOR_AND_RETURN_IF_VALID (Vst::IHostApplication) | |||
TEST_FOR_AND_RETURN_IF_VALID (Vst::IParamValueQueue) | |||
TEST_FOR_AND_RETURN_IF_VALID (Vst::IUnitHandler) | |||
TEST_FOR_COMMON_BASE_AND_RETURN_IF_VALID (FUnknown, Vst::IComponentHandler) | |||
*obj = nullptr; | |||
return kNotImplemented; | |||
@@ -550,12 +551,6 @@ private: | |||
String appName; | |||
VST3PluginInstance* owner; | |||
//============================================================================== | |||
static bool doIdsMatch (const TUID a, const TUID b) noexcept | |||
{ | |||
return std::memcmp (a, b, sizeof (TUID)) == 0; | |||
} | |||
//============================================================================== | |||
class Message : public Vst::IMessage | |||
{ | |||
@@ -1206,7 +1201,7 @@ public: | |||
setOpaque (true); | |||
setVisible (true); | |||
view->setFrame (this); | |||
warnOnFailure (view->setFrame (this)); | |||
ViewRect rect; | |||
warnOnFailure (view->getSize (&rect)); | |||
@@ -1215,7 +1210,7 @@ public: | |||
~VST3PluginWindow() | |||
{ | |||
view->removed(); | |||
warnOnFailure (view->removed()); | |||
getAudioProcessor()->editorBeingDeleted (this); | |||
#if JUCE_MAC | |||
@@ -1273,7 +1268,7 @@ public: | |||
} | |||
else | |||
{ | |||
view->getSize (&rect); | |||
warnOnFailure (view->getSize (&rect)); | |||
} | |||
#if JUCE_WINDOWS | |||
@@ -1485,12 +1480,6 @@ public: | |||
{ | |||
using namespace Vst; | |||
const int numInputs = getNumInputChannels(); | |||
const int numOutputs = getNumOutputChannels(); | |||
// Needed for having the same sample rate in processBlock(); some plugins need this! | |||
setPlayConfigDetails (numInputs, numOutputs, sampleRate, estimatedSamplesPerBlock); | |||
ProcessSetup setup; | |||
setup.symbolicSampleSize = kSample32; | |||
setup.maxSamplesPerBlock = estimatedSamplesPerBlock; | |||
@@ -1504,16 +1493,28 @@ public: | |||
editController->setComponentHandler (host); | |||
setStateForAllBusses (true); | |||
Array<SpeakerArrangement> inArrangements, outArrangements; | |||
fillWithCorrespondingSpeakerArrangements (inArrangements, numInputs); | |||
fillWithCorrespondingSpeakerArrangements (outArrangements, numOutputs); | |||
for (int i = 0; i < numInputAudioBusses; ++i) | |||
inArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (true, true, i).channelCount))); | |||
for (int i = 0; i < numOutputAudioBusses; ++i) | |||
outArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (false, true, i).channelCount))); | |||
warnOnFailure (processor->setBusArrangements (inArrangements.getRawDataPointer(), numInputAudioBusses, | |||
outArrangements.getRawDataPointer(), numOutputAudioBusses)); | |||
// Update the num. busses in case the configuration has been modified by the plugin. (May affect number of channels!): | |||
numInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); | |||
numOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); | |||
// Needed for having the same sample rate in processBlock(); some plugins need this! | |||
setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), | |||
getNumSingleDirectionChannelsFor (component, false, true), | |||
sampleRate, estimatedSamplesPerBlock); | |||
setStateForAllBusses (true); | |||
warnOnFailure (component->setActive (true)); | |||
warnOnFailure (processor->setProcessing (true)); | |||
} | |||
@@ -1716,7 +1717,7 @@ public: | |||
if (head != nullptr) | |||
{ | |||
ScopedPointer<Steinberg::MemoryStream> s (createMemoryStreamForState (*head, "IComponent")); | |||
ComSmartPtr<Steinberg::MemoryStream> s (createMemoryStreamForState (*head, "IComponent")); | |||
if (s != nullptr && component != nullptr) | |||
component->setState (s); | |||
@@ -119,15 +119,18 @@ struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewCompone | |||
{ | |||
if (NSView* parent = (NSView*) getView()) | |||
{ | |||
if (NSView* child = [[parent subviews] firstObject]) | |||
if ([[parent subviews] count] > 0) | |||
{ | |||
NSRect f = [parent frame]; | |||
NSSize newSize = [child frame].size; | |||
if (f.size.width != newSize.width || f.size.height != newSize.height) | |||
if (NSView* child = [[parent subviews] objectAtIndex: 0]) | |||
{ | |||
f.size = newSize; | |||
[parent setFrame: f]; | |||
NSRect f = [parent frame]; | |||
NSSize newSize = [child frame].size; | |||
if (f.size.width != newSize.width || f.size.height != newSize.height) | |||
{ | |||
f.size = newSize; | |||
[parent setFrame: f]; | |||
} | |||
} | |||
} | |||
} | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_processors", | |||
"name": "JUCE audio plugin hosting classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for loading and playing VST, AU, or internally-generated audio processors.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -102,7 +102,7 @@ public: | |||
/** True if the plug-in is part of a multi-type container, e.g. a VST Shell. */ | |||
bool hasSharedContainer; | |||
/** Returns true if the two descriptions refer the the same plug-in. | |||
/** Returns true if the two descriptions refer to the same plug-in. | |||
This isn't quite as simple as them just having the same file (because of | |||
shell plug-ins). | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_utils", | |||
"name": "JUCE extra audio utility classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for audio-related GUI and miscellaneous tasks.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -76,14 +76,14 @@ public: | |||
/** Changes or adds a named value. | |||
@returns true if a value was changed or added; false if the | |||
value was already set the the value passed-in. | |||
value was already set the value passed-in. | |||
*/ | |||
bool set (Identifier name, const var& newValue); | |||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
/** Changes or adds a named value. | |||
@returns true if a value was changed or added; false if the | |||
value was already set the the value passed-in. | |||
value was already set the value passed-in. | |||
*/ | |||
bool set (Identifier name, var&& newValue); | |||
#endif | |||
@@ -133,7 +133,6 @@ namespace juce | |||
#include "network/juce_MACAddress.cpp" | |||
#include "network/juce_NamedPipe.cpp" | |||
#include "network/juce_Socket.cpp" | |||
#include "network/juce_URL.cpp" | |||
#include "network/juce_IPAddress.cpp" | |||
#include "streams/juce_BufferedInputStream.cpp" | |||
#include "streams/juce_FileInputSource.cpp" | |||
@@ -219,5 +218,6 @@ namespace juce | |||
#include "threads/juce_ChildProcess.cpp" | |||
#include "threads/juce_HighResolutionTimer.cpp" | |||
#include "network/juce_URL.cpp" | |||
} |
@@ -64,7 +64,7 @@ | |||
//============================================================================= | |||
/** Config: JUCE_LOG_ASSERTIONS | |||
If this flag is enabled, the the jassert and jassertfalse macros will always use Logger::writeToLog() | |||
If this flag is enabled, the jassert and jassertfalse macros will always use Logger::writeToLog() | |||
to write a message when an assertion happens. | |||
Enabling it will also leave this turned on in release builds. When it's disabled, | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_core", | |||
"name": "JUCE core classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "The essential set of basic JUCE classes, as required by all the other JUCE modules. Includes text, container, memory, threading and i/o functionality.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "ISC Permissive", | |||
@@ -589,10 +589,34 @@ public final class JuceAppActivity extends Activity | |||
//============================================================================== | |||
public static class HTTPStream | |||
{ | |||
public HTTPStream (HttpURLConnection connection_) throws IOException | |||
public HTTPStream (HttpURLConnection connection_, | |||
int[] statusCode, StringBuffer responseHeaders) throws IOException | |||
{ | |||
connection = connection_; | |||
inputStream = new BufferedInputStream (connection.getInputStream()); | |||
try | |||
{ | |||
inputStream = new BufferedInputStream (connection.getInputStream()); | |||
} | |||
catch (IOException e) | |||
{ | |||
if (connection.getResponseCode() < org.apache.http.HttpStatus.SC_BAD_REQUEST) | |||
throw e; | |||
} | |||
finally | |||
{ | |||
statusCode[0] = connection.getResponseCode(); | |||
} | |||
if (statusCode[0] >= org.apache.http.HttpStatus.SC_BAD_REQUEST) | |||
inputStream = connection.getErrorStream(); | |||
else | |||
inputStream = connection.getInputStream(); | |||
for (java.util.Map.Entry<String, java.util.List<String>> entry : connection.getHeaderFields().entrySet()) | |||
if (entry.getKey() != null && entry.getValue() != null) | |||
responseHeaders.append (entry.getKey() + ": " | |||
+ android.text.TextUtils.join (",", entry.getValue()) + "\n"); | |||
} | |||
public final void release() | |||
@@ -634,30 +658,31 @@ public final class JuceAppActivity extends Activity | |||
private long position; | |||
} | |||
public static final HTTPStream createHTTPStream (String address, boolean isPost, byte[] postData, | |||
String headers, int timeOutMs, | |||
java.lang.StringBuffer responseHeaders) | |||
public static final HTTPStream createHTTPStream (String address, | |||
boolean isPost, byte[] postData, String headers, | |||
int timeOutMs, int[] statusCode, | |||
StringBuffer responseHeaders) | |||
{ | |||
try | |||
{ | |||
HttpURLConnection connection = (HttpURLConnection) (new URL (address).openConnection()); | |||
HttpURLConnection connection = (HttpURLConnection) (new URL(address) | |||
.openConnection()); | |||
if (connection != null) | |||
{ | |||
try | |||
{ | |||
if (isPost) | |||
{ | |||
connection.setConnectTimeout (timeOutMs); | |||
connection.setDoOutput (true); | |||
connection.setChunkedStreamingMode (0); | |||
connection.setRequestMethod("POST"); | |||
connection.setConnectTimeout(timeOutMs); | |||
connection.setDoOutput(true); | |||
connection.setChunkedStreamingMode(0); | |||
OutputStream out = connection.getOutputStream(); | |||
out.write (postData); | |||
out.write(postData); | |||
out.flush(); | |||
} | |||
return new HTTPStream (connection); | |||
return new HTTPStream (connection, statusCode, responseHeaders); | |||
} | |||
catch (Throwable e) | |||
{ | |||
@@ -665,8 +690,7 @@ public final class JuceAppActivity extends Activity | |||
} | |||
} | |||
} | |||
catch (Throwable e) | |||
{} | |||
catch (Throwable e) {} | |||
return null; | |||
} | |||
@@ -251,7 +251,7 @@ extern AndroidSystem android; | |||
class ThreadLocalJNIEnvHolder | |||
{ | |||
public: | |||
ThreadLocalJNIEnvHolder() | |||
ThreadLocalJNIEnvHolder() noexcept | |||
: jvm (nullptr) | |||
{ | |||
zeromem (threads, sizeof (threads)); | |||
@@ -269,18 +269,19 @@ public: | |||
addEnv (env); | |||
} | |||
JNIEnv* attach() | |||
JNIEnv* attach() noexcept | |||
{ | |||
JNIEnv* env = nullptr; | |||
jvm->AttachCurrentThread (&env, nullptr); | |||
if (env != nullptr) | |||
addEnv (env); | |||
if (JNIEnv* env = attachToCurrentThread()) | |||
{ | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
return addEnv (env); | |||
} | |||
return env; | |||
jassertfalse; | |||
return nullptr; | |||
} | |||
void detach() | |||
void detach() noexcept | |||
{ | |||
jvm->DetachCurrentThread(); | |||
@@ -294,54 +295,61 @@ public: | |||
JNIEnv* getOrAttach() noexcept | |||
{ | |||
JNIEnv* env = get(); | |||
if (JNIEnv* env = get()) | |||
return env; | |||
if (env == nullptr) | |||
env = attach(); | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
jassert (env != nullptr); | |||
return env; | |||
} | |||
if (JNIEnv* env = get()) | |||
return env; | |||
JNIEnv* get() const noexcept | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
for (int i = 0; i < maxThreads; ++i) | |||
if (threads[i] == thisThread) | |||
return envs[i]; | |||
if (JNIEnv* env = attachToCurrentThread()) | |||
return addEnv (env); | |||
return nullptr; | |||
} | |||
enum { maxThreads = 32 }; | |||
private: | |||
JavaVM* jvm; | |||
enum { maxThreads = 32 }; | |||
pthread_t threads [maxThreads]; | |||
JNIEnv* envs [maxThreads]; | |||
SpinLock addRemoveLock; | |||
void addEnv (JNIEnv* env) | |||
JNIEnv* addEnv (JNIEnv* env) noexcept | |||
{ | |||
SpinLock::ScopedLockType sl (addRemoveLock); | |||
const pthread_t thisThread = pthread_self(); | |||
if (get() == nullptr) | |||
for (int i = 0; i < maxThreads; ++i) | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
for (int i = 0; i < maxThreads; ++i) | |||
if (threads[i] == 0) | |||
{ | |||
if (threads[i] == 0) | |||
{ | |||
envs[i] = env; | |||
threads[i] = thisThread; | |||
return; | |||
} | |||
envs[i] = env; | |||
threads[i] = thisThread; | |||
return env; | |||
} | |||
} | |||
jassertfalse; // too many threads! | |||
return nullptr; | |||
} | |||
JNIEnv* get() const noexcept | |||
{ | |||
const pthread_t thisThread = pthread_self(); | |||
for (int i = 0; i < maxThreads; ++i) | |||
if (threads[i] == thisThread) | |||
return envs[i]; | |||
return nullptr; | |||
} | |||
JNIEnv* attachToCurrentThread() | |||
{ | |||
JNIEnv* env = nullptr; | |||
jvm->AttachCurrentThread (&env, nullptr); | |||
return env; | |||
} | |||
}; | |||
@@ -357,7 +365,7 @@ extern ThreadLocalJNIEnvHolder threadLocalJNIEnvHolder; | |||
METHOD (setClipboardContent, "setClipboardContent", "(Ljava/lang/String;)V") \ | |||
METHOD (excludeClipRegion, "excludeClipRegion", "(Landroid/graphics/Canvas;FFFF)V") \ | |||
METHOD (renderGlyph, "renderGlyph", "(CLandroid/graphics/Paint;Landroid/graphics/Matrix;Landroid/graphics/Rect;)[I") \ | |||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;ILjava/lang/StringBuffer;)L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$HTTPStream;") \ | |||
STATICMETHOD (createHTTPStream, "createHTTPStream", "(Ljava/lang/String;Z[BLjava/lang/String;I[ILjava/lang/StringBuffer;)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;J)V") \ | |||
@@ -71,6 +71,7 @@ public: | |||
WebInputStream (String address, bool isPost, const MemoryBlock& postData, | |||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers, int timeOutMs, StringPairArray* responseHeaders) | |||
: statusCode (0) | |||
{ | |||
if (! address.contains ("://")) | |||
address = "http://" + address; | |||
@@ -91,6 +92,9 @@ public: | |||
// thread. You'll need to move your networking code to a background thread to keep it happy.. | |||
jassert (Thread::getCurrentThread() != nullptr); | |||
jintArray statusCodeArray = env->NewIntArray (1); | |||
jassert (statusCodeArray != 0); | |||
stream = GlobalRef (env->CallStaticObjectMethod (JuceAppActivity, | |||
JuceAppActivity.createHTTPStream, | |||
javaString (address).get(), | |||
@@ -98,8 +102,14 @@ public: | |||
postDataArray, | |||
javaString (headers).get(), | |||
(jint) timeOutMs, | |||
statusCodeArray, | |||
responseHeaderBuffer.get())); | |||
jint* const statusCodeElements = env->GetIntArrayElements (statusCodeArray, 0); | |||
statusCode = statusCodeElements[0]; | |||
env->ReleaseIntArrayElements (statusCodeArray, statusCodeElements, 0); | |||
env->DeleteLocalRef (statusCodeArray); | |||
if (postDataArray != 0) | |||
env->DeleteLocalRef (postDataArray); | |||
@@ -135,6 +145,8 @@ public: | |||
} | |||
//============================================================================== | |||
bool isError() const { return stream == nullptr; } | |||
bool isExhausted() override { return stream != nullptr && stream.callBooleanMethod (HTTPStream.isExhausted); } | |||
int64 getTotalLength() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getTotalLength) : 0; } | |||
int64 getPosition() override { return stream != nullptr ? stream.callLongMethod (HTTPStream.getPosition) : 0; } | |||
@@ -162,18 +174,8 @@ public: | |||
//============================================================================== | |||
GlobalRef stream; | |||
int statusCode; | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||
}; | |||
InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, | |||
OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers, const int timeOutMs, StringPairArray* responseHeaders) | |||
{ | |||
ScopedPointer <WebInputStream> wi (new WebInputStream (address, isPost, postData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders)); | |||
return wi->stream != 0 ? wi.release() : nullptr; | |||
} |
@@ -72,11 +72,11 @@ public: | |||
WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | |||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||
: socketHandle (-1), levelsOfRedirection (0), | |||
: statusCode (0), socketHandle (-1), levelsOfRedirection (0), | |||
address (address_), headers (headers_), postData (postData_), position (0), | |||
finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||
{ | |||
createConnection (progressCallback, progressCallbackContext); | |||
statusCode = createConnection (progressCallback, progressCallbackContext); | |||
if (responseHeaders != nullptr && ! isError()) | |||
{ | |||
@@ -144,7 +144,7 @@ public: | |||
{ | |||
closeSocket(); | |||
position = 0; | |||
createConnection (0, 0); | |||
statusCode = createConnection (0, 0); | |||
} | |||
skipNextBytes (wantedPos - position); | |||
@@ -154,6 +154,8 @@ public: | |||
} | |||
//============================================================================== | |||
int statusCode; | |||
private: | |||
int socketHandle, levelsOfRedirection; | |||
StringArray headerLines; | |||
@@ -173,7 +175,7 @@ private: | |||
levelsOfRedirection = 0; | |||
} | |||
void createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | |||
int createConnection (URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext) | |||
{ | |||
closeSocket(); | |||
@@ -189,7 +191,7 @@ private: | |||
String hostName, hostPath; | |||
int hostPort; | |||
if (! decomposeURL (address, hostName, hostPath, hostPort)) | |||
return; | |||
return 0; | |||
String serverName, proxyName, proxyPath; | |||
int proxyPort = 0; | |||
@@ -199,7 +201,7 @@ private: | |||
if (proxyURL.startsWithIgnoreCase ("http://")) | |||
{ | |||
if (! decomposeURL (proxyURL, proxyName, proxyPath, proxyPort)) | |||
return; | |||
return 0; | |||
serverName = proxyName; | |||
port = proxyPort; | |||
@@ -219,14 +221,14 @@ private: | |||
struct addrinfo* result = nullptr; | |||
if (getaddrinfo (serverName.toUTF8(), String (port).toUTF8(), &hints, &result) != 0 || result == 0) | |||
return; | |||
return 0; | |||
socketHandle = socket (result->ai_family, result->ai_socktype, 0); | |||
if (socketHandle == -1) | |||
{ | |||
freeaddrinfo (result); | |||
return; | |||
return 0; | |||
} | |||
int receiveBufferSize = 16384; | |||
@@ -241,7 +243,7 @@ private: | |||
{ | |||
closeSocket(); | |||
freeaddrinfo (result); | |||
return; | |||
return 0; | |||
} | |||
freeaddrinfo (result); | |||
@@ -254,7 +256,7 @@ private: | |||
progressCallback, progressCallbackContext)) | |||
{ | |||
closeSocket(); | |||
return; | |||
return 0; | |||
} | |||
} | |||
@@ -265,15 +267,15 @@ private: | |||
{ | |||
headerLines = StringArray::fromLines (responseHeader); | |||
const int statusCode = responseHeader.fromFirstOccurrenceOf (" ", false, false) | |||
.substring (0, 3).getIntValue(); | |||
const int status = responseHeader.fromFirstOccurrenceOf (" ", false, false) | |||
.substring (0, 3).getIntValue(); | |||
//int contentLength = findHeaderItem (lines, "Content-Length:").getIntValue(); | |||
//bool isChunked = findHeaderItem (lines, "Transfer-Encoding:").equalsIgnoreCase ("chunked"); | |||
String location (findHeaderItem (headerLines, "Location:")); | |||
if (statusCode >= 300 && statusCode < 400 | |||
if (status >= 300 && status < 400 | |||
&& location.isNotEmpty() && location != address) | |||
{ | |||
if (! location.startsWithIgnoreCase ("http://")) | |||
@@ -282,18 +284,18 @@ private: | |||
if (++levelsOfRedirection <= 3) | |||
{ | |||
address = location; | |||
createConnection (progressCallback, progressCallbackContext); | |||
return; | |||
return createConnection (progressCallback, progressCallbackContext); | |||
} | |||
} | |||
else | |||
{ | |||
levelsOfRedirection = 0; | |||
return; | |||
return status; | |||
} | |||
} | |||
closeSocket(); | |||
return 0; | |||
} | |||
//============================================================================== | |||
@@ -437,14 +439,3 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||
}; | |||
InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, | |||
OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers, const int timeOutMs, StringPairArray* responseHeaders) | |||
{ | |||
ScopedPointer<WebInputStream> wi (new WebInputStream (address, isPost, postData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders)); | |||
return wi->isError() ? nullptr : wi.release(); | |||
} |
@@ -118,6 +118,7 @@ public: | |||
connection (nil), | |||
data ([[NSMutableData data] retain]), | |||
headers (nil), | |||
statusCode (0), | |||
initialised (false), | |||
hasFailed (false), | |||
hasFinished (false) | |||
@@ -202,7 +203,11 @@ public: | |||
headers = nil; | |||
if ([response isKindOfClass: [NSHTTPURLResponse class]]) | |||
headers = [[((NSHTTPURLResponse*) response) allHeaderFields] retain]; | |||
{ | |||
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response; | |||
headers = [[httpResponse allHeaderFields] retain]; | |||
statusCode = (int) [httpResponse statusCode]; | |||
} | |||
} | |||
void didFailWithError (NSError* error) | |||
@@ -251,6 +256,7 @@ public: | |||
NSURLConnection* connection; | |||
NSMutableData* data; | |||
NSDictionary* headers; | |||
int statusCode; | |||
bool initialised, hasFailed, hasFinished; | |||
private: | |||
@@ -318,7 +324,7 @@ public: | |||
WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | |||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||
: address (address_), headers (headers_), postData (postData_), position (0), | |||
: statusCode (0), address (address_), headers (headers_), postData (postData_), position (0), | |||
finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||
{ | |||
JUCE_AUTORELEASEPOOL | |||
@@ -327,6 +333,8 @@ public: | |||
if (responseHeaders != nullptr && connection != nullptr && connection->headers != nil) | |||
{ | |||
statusCode = connection->statusCode; | |||
NSEnumerator* enumerator = [connection->headers keyEnumerator]; | |||
while (NSString* key = [enumerator nextObject]) | |||
@@ -380,6 +388,8 @@ public: | |||
return true; | |||
} | |||
int statusCode; | |||
private: | |||
ScopedPointer<URLConnectionState> connection; | |||
String address, headers; | |||
@@ -428,14 +438,3 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||
}; | |||
InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, | |||
OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers, const int timeOutMs, StringPairArray* responseHeaders) | |||
{ | |||
ScopedPointer<WebInputStream> wi (new WebInputStream (address, isPost, postData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders)); | |||
return wi->isError() ? nullptr : wi.release(); | |||
} |
@@ -996,6 +996,11 @@ public: | |||
ActiveProcess (const StringArray& arguments, int streamFlags) | |||
: childPID (0), pipeHandle (0), readHandle (0) | |||
{ | |||
// Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX | |||
// you're trying to launch the .app folder rather than the actual binary inside it?) | |||
jassert ((! arguments[0].containsChar ('/')) | |||
|| File::getCurrentWorkingDirectory().getChildFile (arguments[0]).existsAsFile()); | |||
int pipeHandles[2] = { 0 }; | |||
if (pipe (pipeHandles) == 0) | |||
@@ -41,41 +41,50 @@ public: | |||
WebInputStream (const String& address_, bool isPost_, const MemoryBlock& postData_, | |||
URL::OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers_, int timeOutMs_, StringPairArray* responseHeaders) | |||
: connection (0), request (0), | |||
: statusCode (0), connection (0), request (0), | |||
address (address_), headers (headers_), postData (postData_), position (0), | |||
finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||
{ | |||
createConnection (progressCallback, progressCallbackContext); | |||
if (responseHeaders != nullptr && ! isError()) | |||
if (! isError()) | |||
{ | |||
DWORD bufferSizeBytes = 4096; | |||
for (;;) | |||
if (responseHeaders != nullptr) | |||
{ | |||
HeapBlock<char> buffer ((size_t) bufferSizeBytes); | |||
DWORD bufferSizeBytes = 4096; | |||
if (HttpQueryInfo (request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer.getData(), &bufferSizeBytes, 0)) | |||
for (;;) | |||
{ | |||
StringArray headersArray; | |||
headersArray.addLines (String (reinterpret_cast<const WCHAR*> (buffer.getData()))); | |||
HeapBlock<char> buffer ((size_t) bufferSizeBytes); | |||
for (int i = 0; i < headersArray.size(); ++i) | |||
if (HttpQueryInfo (request, HTTP_QUERY_RAW_HEADERS_CRLF, buffer.getData(), &bufferSizeBytes, 0)) | |||
{ | |||
const String& header = headersArray[i]; | |||
const String key (header.upToFirstOccurrenceOf (": ", false, false)); | |||
const String value (header.fromFirstOccurrenceOf (": ", false, false)); | |||
const String previousValue ((*responseHeaders) [key]); | |||
StringArray headersArray; | |||
headersArray.addLines (String (reinterpret_cast<const WCHAR*> (buffer.getData()))); | |||
for (int i = 0; i < headersArray.size(); ++i) | |||
{ | |||
const String& header = headersArray[i]; | |||
const String key (header.upToFirstOccurrenceOf (": ", false, false)); | |||
const String value (header.fromFirstOccurrenceOf (": ", false, false)); | |||
const String previousValue ((*responseHeaders) [key]); | |||
responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||
responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||
} | |||
break; | |||
} | |||
break; | |||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |||
break; | |||
} | |||
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |||
break; | |||
} | |||
DWORD status = 0; | |||
DWORD statusSize = sizeof (status); | |||
if (HttpQueryInfo (request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statusSize, 0)) | |||
statusCode = (int) status; | |||
} | |||
} | |||
@@ -145,6 +154,8 @@ public: | |||
return true; | |||
} | |||
int statusCode; | |||
private: | |||
//============================================================================== | |||
HINTERNET connection, request; | |||
@@ -305,17 +316,6 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebInputStream) | |||
}; | |||
InputStream* URL::createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, | |||
OpenStreamProgressCallback* progressCallback, void* progressCallbackContext, | |||
const String& headers, const int timeOutMs, StringPairArray* responseHeaders) | |||
{ | |||
ScopedPointer <WebInputStream> wi (new WebInputStream (address, isPost, postData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders)); | |||
return wi->isError() ? nullptr : wi.release(); | |||
} | |||
//============================================================================== | |||
struct GetAdaptersInfoHelper | |||
@@ -45,72 +45,67 @@ void Logger::outputDebugString (const String& text) | |||
#pragma intrinsic (__cpuid) | |||
#pragma intrinsic (__rdtsc) | |||
String SystemStats::getCpuVendor() | |||
static void callCPUID (int result[4], int infoType) | |||
{ | |||
int info [4]; | |||
__cpuid (info, 0); | |||
char v [12]; | |||
memcpy (v, info + 1, 4); | |||
memcpy (v + 4, info + 3, 4); | |||
memcpy (v + 8, info + 2, 4); | |||
return String (v, 12); | |||
__cpuid (result, infoType); | |||
} | |||
#else | |||
//============================================================================== | |||
// CPU info functions using old fashioned inline asm... | |||
static void juce_getCpuVendor (char* const v) | |||
static void callCPUID (int result[4], int infoType) | |||
{ | |||
int vendor[4] = { 0 }; | |||
#if ! JUCE_MINGW | |||
__try | |||
#endif | |||
{ | |||
#if JUCE_GCC | |||
unsigned int dummy = 0; | |||
__asm__ ("cpuid" : "=a" (dummy), "=b" (vendor[0]), "=c" (vendor[2]),"=d" (vendor[1]) : "a" (0)); | |||
__asm__ __volatile__ ("cpuid" : "=a" (result[0]), "=b" (result[1]), "=c" (result[2]),"=d" (result[3]) : "a" (infoType)); | |||
#else | |||
__asm | |||
{ | |||
mov eax, 0 | |||
mov esi, result | |||
mov eax, infoType | |||
xor ecx, ecx | |||
cpuid | |||
mov [vendor], ebx | |||
mov [vendor + 4], edx | |||
mov [vendor + 8], ecx | |||
mov dword ptr [esi + 0], eax | |||
mov dword ptr [esi + 4], ebx | |||
mov dword ptr [esi + 8], ecx | |||
mov dword ptr [esi + 12], edx | |||
} | |||
#endif | |||
} | |||
#if ! JUCE_MINGW | |||
__except (EXCEPTION_EXECUTE_HANDLER) | |||
{ | |||
} | |||
__except (EXCEPTION_EXECUTE_HANDLER) {} | |||
#endif | |||
memcpy (v, vendor, 16); | |||
} | |||
#endif | |||
String SystemStats::getCpuVendor() | |||
{ | |||
char v [16]; | |||
juce_getCpuVendor (v); | |||
return String (v, 16); | |||
} | |||
#endif | |||
int info[4] = { 0 }; | |||
callCPUID (info, 0); | |||
char v [12]; | |||
memcpy (v, info + 1, 4); | |||
memcpy (v + 4, info + 3, 4); | |||
memcpy (v + 8, info + 2, 4); | |||
return String (v, 12); | |||
} | |||
//============================================================================== | |||
void CPUInformation::initialise() noexcept | |||
{ | |||
hasMMX = IsProcessorFeaturePresent (PF_MMX_INSTRUCTIONS_AVAILABLE) != 0; | |||
hasSSE = IsProcessorFeaturePresent (PF_XMMI_INSTRUCTIONS_AVAILABLE) != 0; | |||
hasSSE2 = IsProcessorFeaturePresent (PF_XMMI64_INSTRUCTIONS_AVAILABLE) != 0; | |||
hasSSE3 = IsProcessorFeaturePresent (13 /*PF_SSE3_INSTRUCTIONS_AVAILABLE*/) != 0; | |||
has3DNow = IsProcessorFeaturePresent (7 /*PF_AMD3D_INSTRUCTIONS_AVAILABLE*/) != 0; | |||
int info[4] = { 0 }; | |||
callCPUID (info, 1); | |||
// NB: IsProcessorFeaturePresent doesn't work on XP | |||
hasMMX = (info[3] & (1 << 23)) != 0; | |||
hasSSE = (info[3] & (1 << 25)) != 0; | |||
hasSSE2 = (info[3] & (1 << 26)) != 0; | |||
hasSSE3 = (info[2] & (1 << 0)) != 0; | |||
has3DNow = (info[1] & (1 << 31)) != 0; | |||
SYSTEM_INFO systemInfo; | |||
GetNativeSystemInfo (&systemInfo); | |||
@@ -63,6 +63,13 @@ URL::URL (const String& u) : url (u) | |||
} | |||
} | |||
URL::URL (const String& u, int) : url (u) {} | |||
URL URL::createWithoutParsing (const String& u) | |||
{ | |||
return URL (u, 0); | |||
} | |||
URL::URL (const URL& other) | |||
: url (other.url), | |||
postData (other.postData), | |||
@@ -323,7 +330,8 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
void* const progressCallbackContext, | |||
String headers, | |||
const int timeOutMs, | |||
StringPairArray* const responseHeaders) const | |||
StringPairArray* const responseHeaders, | |||
int* statusCode) const | |||
{ | |||
MemoryBlock headersAndPostData; | |||
@@ -336,9 +344,15 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
if (! headers.endsWithChar ('\n')) | |||
headers << "\r\n"; | |||
return createNativeStream (toString (! usePostCommand), usePostCommand, headersAndPostData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders); | |||
ScopedPointer<WebInputStream> wi (new WebInputStream (toString (! usePostCommand), | |||
usePostCommand, headersAndPostData, | |||
progressCallback, progressCallbackContext, | |||
headers, timeOutMs, responseHeaders)); | |||
if (statusCode != nullptr) | |||
*statusCode = wi->statusCode; | |||
return wi->isError() ? nullptr : wi.release(); | |||
} | |||
//============================================================================== | |||
@@ -44,7 +44,11 @@ public: | |||
/** Creates an empty URL. */ | |||
URL(); | |||
/** Creates a URL from a string. */ | |||
/** Creates a URL from a string. | |||
This will parse any embedded parameters after a '?' character and store them | |||
in the list (see getParameterNames etc). If you don't want this to happen, you | |||
can use createWithoutParsing(). | |||
*/ | |||
URL (const String& url); | |||
/** Creates a copy of another URL. */ | |||
@@ -243,8 +247,10 @@ public: | |||
@param connectionTimeOutMs if 0, this will use whatever default setting the OS chooses. If | |||
a negative number, it will be infinite. Otherwise it specifies a | |||
time in milliseconds. | |||
@param responseHeaders if this is non-zero, all the (key, value) pairs received as headers | |||
@param responseHeaders if this is non-null, all the (key, value) pairs received as headers | |||
in the response will be stored in this array | |||
@param statusCode if this is non-null, it will get set to the http status code, if one | |||
is known, or 0 if a code isn't available | |||
@returns an input stream that the caller must delete, or a null pointer if there was an | |||
error trying to open it. | |||
*/ | |||
@@ -253,7 +259,8 @@ public: | |||
void* progressCallbackContext = nullptr, | |||
String extraHeaders = String(), | |||
int connectionTimeOutMs = 0, | |||
StringPairArray* responseHeaders = nullptr) const; | |||
StringPairArray* responseHeaders = nullptr, | |||
int* statusCode = nullptr) const; | |||
//============================================================================== | |||
@@ -326,18 +333,21 @@ public: | |||
*/ | |||
static String removeEscapeChars (const String& stringToRemoveEscapeCharsFrom); | |||
/** Returns a URL without attempting to remove any embedded parameters from the string. | |||
This may be necessary if you need to create a request that involves both POST | |||
parameters and parameters which are embedded in the URL address itself. | |||
*/ | |||
static URL createWithoutParsing (const String& url); | |||
private: | |||
//============================================================================== | |||
String url, postData; | |||
StringArray parameterNames, parameterValues; | |||
StringPairArray filesToUpload, mimeTypes; | |||
URL (const String&, int); | |||
void addParameter (const String&, const String&); | |||
static InputStream* createNativeStream (const String& address, bool isPost, const MemoryBlock& postData, | |||
OpenStreamProgressCallback* progressCallback, | |||
void* progressCallbackContext, const String& headers, | |||
const int timeOutMs, StringPairArray* responseHeaders); | |||
JUCE_LEAK_DETECTOR (URL) | |||
}; | |||
@@ -226,7 +226,7 @@ public: | |||
//============================================================================== | |||
/** Sets the string that will be written to the stream when the writeNewLine() | |||
method is called. | |||
By default this will be set the the value of NewLine::getDefault(). | |||
By default this will be set the value of NewLine::getDefault(). | |||
*/ | |||
void setNewLineString (const String& newLineString); | |||
@@ -36,7 +36,7 @@ | |||
*/ | |||
#define JUCE_MAJOR_VERSION 3 | |||
#define JUCE_MINOR_VERSION 0 | |||
#define JUCE_BUILDNUMBER 2 | |||
#define JUCE_BUILDNUMBER 4 | |||
/** Current Juce version number. | |||
@@ -38,6 +38,10 @@ | |||
one of these is by using RAII in the form of a local ScopedLock object - have a look | |||
through the codebase for many examples of how to do this. | |||
In almost all cases you'll want to declare your CriticalSection as a member variable. | |||
Occasionally you may want to declare one as a static variable, but in that case the usual | |||
C++ static object order-of-construction warnings should be heeded. | |||
@see ScopedLock, ScopedTryLock, ScopedUnlock, SpinLock, ReadWriteLock, Thread, InterProcessLock | |||
*/ | |||
class JUCE_API CriticalSection | |||
@@ -157,21 +161,27 @@ private: | |||
/** | |||
Automatically locks and unlocks a CriticalSection object. | |||
Use one of these as a local variable to provide RAII-based locking of a CriticalSection. | |||
You can use a ScopedLock as a local variable to provide RAII-based locking of a CriticalSection. | |||
e.g. @code | |||
CriticalSection myCriticalSection; | |||
for (;;) | |||
struct MyObject | |||
{ | |||
const ScopedLock myScopedLock (myCriticalSection); | |||
// myCriticalSection is now locked | |||
CriticalSection objectLock; | |||
// assuming that this example function will be called by multiple threads | |||
void foo() | |||
{ | |||
const ScopedLock myScopedLock (objectLock); | |||
// objectLock is now locked.. | |||
...do some stuff... | |||
...do some thread-safe work here... | |||
// myCriticalSection gets unlocked here. | |||
} | |||
// ..and objectLock gets unlocked here, as myScopedLock goes out of | |||
// scope at the end of the block | |||
} | |||
}; | |||
@endcode | |||
@see CriticalSection, ScopedUnlock | |||
@@ -189,29 +199,29 @@ typedef CriticalSection::ScopedLockType ScopedLock; | |||
e.g. @code | |||
CriticalSection myCriticalSection; | |||
for (;;) | |||
struct MyObject | |||
{ | |||
const ScopedLock myScopedLock (myCriticalSection); | |||
// myCriticalSection is now locked | |||
... do some stuff with it locked .. | |||
CriticalSection objectLock; | |||
while (xyz) | |||
void foo() | |||
{ | |||
... do some stuff with it locked .. | |||
{ | |||
const ScopedLock myScopedLock (objectLock); | |||
const ScopedUnlock unlocker (myCriticalSection); | |||
// objectLock is now locked.. | |||
// myCriticalSection is now unlocked for the remainder of this block, | |||
// and re-locked at the end. | |||
{ | |||
ScopedUnlock myUnlocker (objectLock); | |||
...do some stuff with it unlocked ... | |||
} | |||
// ..and now unlocked.. | |||
} | |||
// ..and now locked again.. | |||
} | |||
// myCriticalSection gets unlocked here. | |||
} | |||
// ..and finally unlocked. | |||
} | |||
}; | |||
@endcode | |||
@see CriticalSection, ScopedLock | |||
@@ -225,26 +235,27 @@ typedef CriticalSection::ScopedUnlockType ScopedUnlock; | |||
Use one of these as a local variable to control access to a CriticalSection. | |||
e.g. @code | |||
CriticalSection myCriticalSection; | |||
for (;;) | |||
struct MyObject | |||
{ | |||
const ScopedTryLock myScopedTryLock (myCriticalSection); | |||
CriticalSection objectLock; | |||
// Unlike using a ScopedLock, this may fail to actually get the lock, so you | |||
// should test this with the isLocked() method before doing your thread-unsafe | |||
// action.. | |||
if (myScopedTryLock.isLocked()) | |||
void foo() | |||
{ | |||
...do some stuff... | |||
const ScopedTryLock myScopedTryLock (objectLock); | |||
// Unlike using a ScopedLock, this may fail to actually get the lock, so you | |||
// must call the isLocked() method before making any assumptions.. | |||
if (myScopedTryLock.isLocked()) | |||
{ | |||
...safely do some work... | |||
} | |||
else | |||
{ | |||
// If we get here, then our attempt at locking failed because another thread had already locked it.. | |||
} | |||
} | |||
else | |||
{ | |||
..our attempt at locking failed because another thread had already locked it.. | |||
} | |||
// myCriticalSection gets unlocked here (if it was locked) | |||
} | |||
}; | |||
@endcode | |||
@see CriticalSection::tryEnter, ScopedLock, ScopedUnlock, ScopedReadLock | |||
@@ -57,7 +57,7 @@ public: | |||
struct JUCE_API Options | |||
{ | |||
/** Creates an empty Options structure. | |||
You'll need to fill-in the data memebers appropriately before using this structure. | |||
You'll need to fill-in the data members appropriately before using this structure. | |||
*/ | |||
Options(); | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_data_structures", | |||
"name": "JUCE data model helper classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for undo/redo management, and smart data structures.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -73,7 +73,7 @@ private: | |||
class ActionMessage; | |||
friend class ActionMessage; | |||
SortedSet <ActionListener*> actionListeners; | |||
SortedSet<ActionListener*> actionListeners; | |||
CriticalSection actionListenerLock; | |||
JUCE_DECLARE_NON_COPYABLE (ActionBroadcaster) | |||
@@ -28,10 +28,7 @@ | |||
//============================================================================== | |||
/** | |||
Receives callbacks to indicate that some kind of event has occurred. | |||
Used by various classes, e.g. buttons when they are pressed, to tell listeners | |||
about something that's happened. | |||
Interface class for delivery of events that are sent by an ActionBroadcaster. | |||
@see ActionBroadcaster, ChangeListener | |||
*/ | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_events", | |||
"name": "JUCE message and event handling classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for running an application's main event loop and sending/receiving messages, timers, etc.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -35,7 +35,7 @@ enum NotificationType | |||
dontSendNotification = 0, /**< No notification message should be sent. */ | |||
sendNotification = 1, /**< Requests a notification message, either synchronous or not. */ | |||
sendNotificationSync, /**< Requests a synchronous notification. */ | |||
sendNotificationAsync, /**< Requests a asynchronous notification. */ | |||
sendNotificationAsync, /**< Requests an asynchronous notification. */ | |||
}; | |||
@@ -151,7 +151,8 @@ public: | |||
*/ | |||
FloatType getAngleToPoint (Point other) const noexcept | |||
{ | |||
return static_cast<FloatType> (std::atan2 (other.x - x, y - other.y)); | |||
return static_cast<FloatType> (std::atan2 (static_cast<FloatType> (other.x - x), | |||
static_cast<FloatType> (y - other.y))); | |||
} | |||
/** Returns the point that would be reached by rotating this point clockwise | |||
@@ -169,8 +170,8 @@ public: | |||
*/ | |||
Point<FloatType> getPointOnCircumference (float radius, float angle) const noexcept | |||
{ | |||
return Point<FloatType> (static_cast <FloatType> (x + radius * std::sin (angle)), | |||
static_cast <FloatType> (y - radius * std::cos (angle))); | |||
return Point<FloatType> (static_cast<FloatType> (x + radius * std::sin (angle)), | |||
static_cast<FloatType> (y - radius * std::cos (angle))); | |||
} | |||
/** Taking this point to be the centre of an ellipse, this returns a point on its circumference. | |||
@@ -180,8 +181,8 @@ public: | |||
*/ | |||
Point<FloatType> getPointOnCircumference (float radiusX, float radiusY, float angle) const noexcept | |||
{ | |||
return Point<FloatType> (static_cast <FloatType> (x + radiusX * std::sin (angle)), | |||
static_cast <FloatType> (y - radiusY * std::cos (angle))); | |||
return Point<FloatType> (static_cast<FloatType> (x + radiusX * std::sin (angle)), | |||
static_cast<FloatType> (y - radiusY * std::cos (angle))); | |||
} | |||
/** Returns the dot-product of two points (x1 * x2 + y1 * y2). */ | |||
@@ -204,13 +205,13 @@ public: | |||
//============================================================================== | |||
/** Casts this point to a Point<int> object. */ | |||
Point<int> toInt() const noexcept { return Point<int> (static_cast <int> (x), static_cast<int> (y)); } | |||
Point<int> toInt() const noexcept { return Point<int> (static_cast<int> (x), static_cast<int> (y)); } | |||
/** Casts this point to a Point<float> object. */ | |||
Point<float> toFloat() const noexcept { return Point<float> (static_cast <float> (x), static_cast<float> (y)); } | |||
Point<float> toFloat() const noexcept { return Point<float> (static_cast<float> (x), static_cast<float> (y)); } | |||
/** Casts this point to a Point<double> object. */ | |||
Point<double> toDouble() const noexcept { return Point<double> (static_cast <double> (x), static_cast<double> (y)); } | |||
Point<double> toDouble() const noexcept { return Point<double> (static_cast<double> (x), static_cast<double> (y)); } | |||
/** Returns the point as a string in the form "x, y". */ | |||
String toString() const { return String (x) + ", " + String (y); } | |||
@@ -174,6 +174,12 @@ public: | |||
/** Changes the rectangle's height */ | |||
inline void setHeight (const ValueType newHeight) noexcept { h = newHeight; } | |||
/** Changes the position of the rectangle's centre (leaving its size unchanged). */ | |||
inline void setCentre (const ValueType newCentreX, const ValueType newCentreY) noexcept { pos.x = newCentreX - w / (ValueType) 2; pos.y = newCentreY - h / (ValueType) 2; } | |||
/** Changes the position of the rectangle's centre (leaving its size unchanged). */ | |||
inline void setCentre (const Point<ValueType> newCentre) noexcept { setCentre (newCentre.x, newCentre.y); } | |||
/** Returns a rectangle which has the same size and y-position as this one, but with a different x-position. */ | |||
Rectangle withX (const ValueType newX) const noexcept { return Rectangle (newX, pos.y, w, h); } | |||
@@ -189,6 +195,10 @@ public: | |||
/** Returns a rectangle whose size is the same as this one, but whose top-left position is (0, 0). */ | |||
Rectangle withZeroOrigin() const noexcept { return Rectangle (w, h); } | |||
/** Returns a rectangle with the same size as this one, but a new centre position. */ | |||
Rectangle withCentre (const Point<ValueType> newCentre) const noexcept { return Rectangle (newCentre.x - w / (ValueType) 2, | |||
newCentre.y - h / (ValueType) 2, w, h); } | |||
/** Returns a rectangle which has the same position and height as this one, but with a different width. */ | |||
Rectangle withWidth (ValueType newWidth) const noexcept { return Rectangle (pos.x, pos.y, newWidth, h); } | |||
@@ -31,6 +31,12 @@ ImagePixelData::ImagePixelData (const Image::PixelFormat format, const int w, co | |||
ImagePixelData::~ImagePixelData() | |||
{ | |||
listeners.call (&Listener::imageDataBeingDeleted, this); | |||
} | |||
void ImagePixelData::sendDataChangeMessage() | |||
{ | |||
listeners.call (&Listener::imageDataChanged, this); | |||
} | |||
//============================================================================== | |||
@@ -69,15 +75,19 @@ public: | |||
LowLevelGraphicsContext* createLowLevelContext() override | |||
{ | |||
sendDataChangeMessage(); | |||
return new LowLevelGraphicsSoftwareRenderer (Image (this)); | |||
} | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override | |||
{ | |||
bitmap.data = imageData + x * pixelStride + y * lineStride; | |||
bitmap.pixelFormat = pixelFormat; | |||
bitmap.lineStride = lineStride; | |||
bitmap.pixelStride = pixelStride; | |||
if (mode != Image::BitmapData::readOnly) | |||
sendDataChangeMessage(); | |||
} | |||
ImagePixelData* clone() override | |||
@@ -146,6 +156,9 @@ public: | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override | |||
{ | |||
image->initialiseBitmapData (bitmap, x + area.getX(), y + area.getY(), mode); | |||
if (mode != Image::BitmapData::readOnly) | |||
sendDataChangeMessage(); | |||
} | |||
ImagePixelData* clone() override | |||
@@ -455,6 +455,19 @@ public: | |||
typedef ReferenceCountedObjectPtr<ImagePixelData> Ptr; | |||
//============================================================================== | |||
struct Listener | |||
{ | |||
virtual ~Listener() {} | |||
virtual void imageDataChanged (ImagePixelData*) = 0; | |||
virtual void imageDataBeingDeleted (ImagePixelData*) = 0; | |||
}; | |||
ListenerList<Listener> listeners; | |||
void sendDataChangeMessage(); | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ImagePixelData) | |||
}; | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_graphics", | |||
"name": "JUCE graphics classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Classes for 2D vector graphics, image loading/saving, font handling, etc.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -52,15 +52,19 @@ public: | |||
LowLevelGraphicsContext* createLowLevelContext() override | |||
{ | |||
sendDataChangeMessage(); | |||
return new CoreGraphicsContext (context, height, 1.0f); | |||
} | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override | |||
{ | |||
bitmap.data = imageData + x * pixelStride + y * lineStride; | |||
bitmap.pixelFormat = pixelFormat; | |||
bitmap.lineStride = lineStride; | |||
bitmap.pixelStride = pixelStride; | |||
if (mode != Image::BitmapData::readOnly) | |||
sendDataChangeMessage(); | |||
} | |||
ImagePixelData* clone() override | |||
@@ -1230,7 +1230,12 @@ Typeface::Ptr Typeface::createSystemTypefaceFor (const Font& font) | |||
Typeface::Ptr Typeface::createSystemTypefaceFor (const void* data, size_t dataSize) | |||
{ | |||
#if JUCE_CORETEXT_AVAILABLE | |||
return new OSXTypeface (data, dataSize); | |||
#else | |||
jassertfalse; // You need CoreText enabled to use this feature! | |||
return nullptr; | |||
#endif | |||
} | |||
void Typeface::scanFolderForFonts (const File&) | |||
@@ -290,7 +290,7 @@ public: | |||
static ApplicationCommandTarget* findDefaultComponentTarget(); | |||
/** Examines this component and all its parents in turn, looking for the first one | |||
which is a ApplicationCommandTarget. | |||
which is an ApplicationCommandTarget. | |||
Returns the first ApplicationCommandTarget that it finds, or nullptr if none of them | |||
implement that class. | |||
@@ -29,7 +29,7 @@ | |||
//============================================================================== | |||
/** | |||
Manages and edits a list of keypresses, which it uses to invoke the appropriate | |||
command in a ApplicationCommandManager. | |||
command in an ApplicationCommandManager. | |||
Normally, you won't actually create a KeyPressMappingSet directly, because | |||
each ApplicationCommandManager contains its own KeyPressMappingSet, so typically | |||
@@ -326,12 +326,12 @@ public: | |||
void getVisibleArea (RectangleList<int>& result, bool includeSiblings) const; | |||
//============================================================================== | |||
/** Returns this component's x coordinate relative the the screen's top-left origin. | |||
/** Returns this component's x coordinate relative the screen's top-left origin. | |||
@see getX, localPointToGlobal | |||
*/ | |||
int getScreenX() const; | |||
/** Returns this component's y coordinate relative the the screen's top-left origin. | |||
/** Returns this component's y coordinate relative the screen's top-left origin. | |||
@see getY, localPointToGlobal | |||
*/ | |||
int getScreenY() const; | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_gui_basics", | |||
"name": "JUCE GUI core classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Basic user-interface components and related classes.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -104,6 +104,9 @@ LookAndFeel_V2::LookAndFeel_V2() | |||
ComboBox::backgroundColourId, 0xffffffff, | |||
ComboBox::arrowColourId, 0x99000000, | |||
PropertyComponent::backgroundColourId, 0x66ffffff, | |||
PropertyComponent::labelTextColourId, 0xff000000, | |||
TextPropertyComponent::backgroundColourId, 0xffffffff, | |||
TextPropertyComponent::textColourId, 0xff000000, | |||
TextPropertyComponent::outlineColourId, standardOutlineColour, | |||
@@ -1456,6 +1459,8 @@ Label* LookAndFeel_V2::createSliderTextBox (Slider& slider) | |||
l->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); | |||
l->setColour (TextEditor::highlightColourId, slider.findColour (Slider::textBoxHighlightColourId)); | |||
return l; | |||
} | |||
@@ -2302,20 +2307,16 @@ void LookAndFeel_V2::drawPropertyPanelSectionHeader (Graphics& g, const String& | |||
g.drawText (name, textX, 0, width - textX - 4, height, Justification::centredLeft, true); | |||
} | |||
void LookAndFeel_V2::drawPropertyComponentBackground (Graphics& g, int width, int height, | |||
PropertyComponent&) | |||
void LookAndFeel_V2::drawPropertyComponentBackground (Graphics& g, int width, int height, PropertyComponent& component) | |||
{ | |||
g.setColour (Colour (0x66ffffff)); | |||
g.setColour (component.findColour (PropertyComponent::backgroundColourId)); | |||
g.fillRect (0, 0, width, height - 1); | |||
} | |||
void LookAndFeel_V2::drawPropertyComponentLabel (Graphics& g, int, int height, | |||
PropertyComponent& component) | |||
void LookAndFeel_V2::drawPropertyComponentLabel (Graphics& g, int, int height, PropertyComponent& component) | |||
{ | |||
g.setColour (Colours::black); | |||
if (! component.isEnabled()) | |||
g.setOpacity (0.6f); | |||
g.setColour (component.findColour (PropertyComponent::labelTextColourId) | |||
.withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.6f)); | |||
g.setFont (jmin (height, 24) * 0.65f); | |||
@@ -617,15 +617,19 @@ public: | |||
LowLevelGraphicsContext* createLowLevelContext() override | |||
{ | |||
sendDataChangeMessage(); | |||
return new LowLevelGraphicsSoftwareRenderer (Image (this)); | |||
} | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override | |||
{ | |||
bitmap.data = imageData + x * pixelStride + y * lineStride; | |||
bitmap.pixelFormat = pixelFormat; | |||
bitmap.lineStride = lineStride; | |||
bitmap.pixelStride = pixelStride; | |||
if (mode != Image::BitmapData::readOnly) | |||
sendDataChangeMessage(); | |||
} | |||
ImagePixelData* clone() override | |||
@@ -328,15 +328,19 @@ public: | |||
LowLevelGraphicsContext* createLowLevelContext() override | |||
{ | |||
sendDataChangeMessage(); | |||
return new LowLevelGraphicsSoftwareRenderer (Image (this)); | |||
} | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode) override | |||
void initialiseBitmapData (Image::BitmapData& bitmap, int x, int y, Image::BitmapData::ReadWriteMode mode) override | |||
{ | |||
bitmap.data = imageData + x * pixelStride + y * lineStride; | |||
bitmap.pixelFormat = pixelFormat; | |||
bitmap.lineStride = lineStride; | |||
bitmap.pixelStride = pixelStride; | |||
if (mode != Image::BitmapData::readOnly) | |||
sendDataChangeMessage(); | |||
} | |||
ImagePixelData* clone() override | |||
@@ -3177,10 +3181,22 @@ void Desktop::setKioskComponent (Component* kioskModeComponent, bool enableOrDis | |||
} | |||
//============================================================================== | |||
static BOOL CALLBACK enumMonitorsProc (HMONITOR, HDC, LPRECT r, LPARAM userInfo) | |||
struct MonitorInfo | |||
{ | |||
MonitorInfo (Rectangle<int> rect, bool main) noexcept : isMain (main), bounds (rect) {} | |||
Rectangle<int> bounds; | |||
bool isMain; | |||
}; | |||
static BOOL CALLBACK enumMonitorsProc (HMONITOR hm, HDC, LPRECT r, LPARAM userInfo) | |||
{ | |||
Array <Rectangle<int> >* const monitorCoords = (Array <Rectangle<int> >*) userInfo; | |||
monitorCoords->add (rectangleFromRECT (*r)); | |||
MONITORINFO info = { 0 }; | |||
info.cbSize = sizeof (info); | |||
GetMonitorInfo (hm, &info); | |||
const bool isMain = (info.dwFlags & 1 /* MONITORINFOF_PRIMARY */) != 0; | |||
((Array<MonitorInfo>*) userInfo)->add (MonitorInfo (rectangleFromRECT (*r), isMain)); | |||
return TRUE; | |||
} | |||
@@ -3188,17 +3204,17 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
{ | |||
setDPIAwareness(); | |||
Array <Rectangle<int> > monitors; | |||
Array<MonitorInfo> monitors; | |||
EnumDisplayMonitors (0, 0, &enumMonitorsProc, (LPARAM) &monitors); | |||
if (monitors.size() == 0) | |||
monitors.add (MonitorInfo (rectangleFromRECT (getWindowRect (GetDesktopWindow())), true)); | |||
// make sure the first in the list is the main monitor | |||
for (int i = 1; i < monitors.size(); ++i) | |||
if (monitors.getReference(i).getPosition().isOrigin()) | |||
if (monitors.getReference(i).isMain) | |||
monitors.swap (i, 0); | |||
if (monitors.size() == 0) | |||
monitors.add (rectangleFromRECT (getWindowRect (GetDesktopWindow()))); | |||
RECT workArea; | |||
SystemParametersInfo (SPI_GETWORKAREA, 0, &workArea, 0); | |||
@@ -3207,12 +3223,12 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
for (int i = 0; i < monitors.size(); ++i) | |||
{ | |||
Display d; | |||
d.userArea = d.totalArea = monitors.getReference(i) / masterScale; | |||
d.isMain = (i == 0); | |||
d.scale = masterScale; | |||
d.dpi = dpi; | |||
d.userArea = d.totalArea = monitors.getReference(i).bounds / masterScale; | |||
d.isMain = monitors.getReference(i).isMain; | |||
d.scale = masterScale; | |||
d.dpi = dpi; | |||
if (i == 0) | |||
if (d.isMain) | |||
d.userArea = d.userArea.getIntersection (rectangleFromRECT (workArea) / masterScale); | |||
displays.add (d); | |||
@@ -102,6 +102,22 @@ public: | |||
/** By default, this just repaints the component. */ | |||
void enablementChanged() override; | |||
//============================================================================== | |||
/** A set of colour IDs to use to change the colour of various aspects of the combo box. | |||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||
methods. | |||
To change the colours of the menu that pops up | |||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||
*/ | |||
enum ColourIds | |||
{ | |||
backgroundColourId = 0x1008300, /**< The background colour to fill the component with. */ | |||
labelTextColourId = 0x1008301, /**< The colour for the property's label text. */ | |||
}; | |||
//============================================================================== | |||
/** This abstract base class is implemented by LookAndFeel classes. */ | |||
struct JUCE_API LookAndFeelMethods | |||
@@ -375,7 +375,7 @@ public: | |||
const double delta = (button == incButton) ? interval : -interval; | |||
DragInProgress drag (*this); | |||
setValue (owner.snapValue (getValue() + delta, false), sendNotificationSync); | |||
setValue (owner.snapValue (getValue() + delta, notDragging), sendNotificationSync); | |||
} | |||
} | |||
@@ -394,7 +394,7 @@ public: | |||
void labelTextChanged (Label* label) override | |||
{ | |||
const double newValue = owner.snapValue (owner.getValueFromText (label->getText()), false); | |||
const double newValue = owner.snapValue (owner.getValueFromText (label->getText()), notDragging); | |||
if (newValue != (double) currentValue.getValue()) | |||
{ | |||
@@ -873,6 +873,8 @@ public: | |||
&& ! ((style == LinearBar || style == LinearBarVertical) | |||
&& e.mouseWasClicked() && valueBox != nullptr && valueBox->isEditable())) | |||
{ | |||
DragMode dragMode = notDragging; | |||
if (style == Rotary) | |||
{ | |||
handleRotaryDrag (e); | |||
@@ -889,21 +891,27 @@ public: | |||
} | |||
if (isAbsoluteDragMode (e.mods) || (maximum - minimum) / sliderRegionSize < interval) | |||
{ | |||
dragMode = notDragging; | |||
handleAbsoluteDrag (e); | |||
} | |||
else | |||
{ | |||
dragMode = velocityDrag; | |||
handleVelocityDrag (e); | |||
} | |||
} | |||
valueWhenLastDragged = jlimit (minimum, maximum, valueWhenLastDragged); | |||
if (sliderBeingDragged == 0) | |||
{ | |||
setValue (owner.snapValue (valueWhenLastDragged, true), | |||
setValue (owner.snapValue (valueWhenLastDragged, dragMode), | |||
sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationSync); | |||
} | |||
else if (sliderBeingDragged == 1) | |||
{ | |||
setMinValue (owner.snapValue (valueWhenLastDragged, true), | |||
setMinValue (owner.snapValue (valueWhenLastDragged, dragMode), | |||
sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true); | |||
if (e.mods.isShiftDown()) | |||
@@ -913,7 +921,7 @@ public: | |||
} | |||
else if (sliderBeingDragged == 2) | |||
{ | |||
setMaxValue (owner.snapValue (valueWhenLastDragged, true), | |||
setMaxValue (owner.snapValue (valueWhenLastDragged, dragMode), | |||
sendChangeOnlyOnRelease ? dontSendNotification : sendNotificationAsync, true); | |||
if (e.mods.isShiftDown()) | |||
@@ -994,7 +1002,7 @@ public: | |||
delta = -delta; | |||
DragInProgress drag (*this); | |||
setValue (owner.snapValue (value + delta, false), sendNotificationSync); | |||
setValue (owner.snapValue (value + delta, notDragging), sendNotificationSync); | |||
} | |||
return true; | |||
@@ -1534,7 +1542,7 @@ double Slider::valueToProportionOfLength (double value) | |||
return skew == 1.0 ? n : pow (n, skew); | |||
} | |||
double Slider::snapValue (double attemptedValue, const bool) | |||
double Slider::snapValue (double attemptedValue, DragMode) | |||
{ | |||
return attemptedValue; | |||
} | |||
@@ -97,6 +97,16 @@ public: | |||
TextBoxBelow /**< Puts the text box below the slider, horizontally centred. */ | |||
}; | |||
/** Describes the type of mouse-dragging that is happening when a value is being changed. | |||
@see snapValue | |||
*/ | |||
enum DragMode | |||
{ | |||
notDragging, /**< Dragging is not active. */ | |||
absoluteDrag, /**< The dragging corresponds directly to the value that is displayed. */ | |||
velocityDrag /**< The dragging value change is relative to the velocity of the mouse mouvement. */ | |||
}; | |||
//============================================================================== | |||
/** Creates a slider. | |||
When created, you can set up the slider's style and range with setSliderStyle(), setRange(), etc. | |||
@@ -157,7 +167,7 @@ public: | |||
int getMouseDragSensitivity() const noexcept; | |||
//============================================================================== | |||
/** Changes the way the the mouse is used when dragging the slider. | |||
/** Changes the way the mouse is used when dragging the slider. | |||
If true, this will turn on velocity-sensitive dragging, so that | |||
the faster the mouse moves, the bigger the movement to the slider. This | |||
@@ -720,12 +730,13 @@ public: | |||
a given position, and allows a subclass to sanity-check this value, possibly | |||
returning a different value to use instead. | |||
@param attemptedValue the value the user is trying to enter | |||
@param userIsDragging true if the user is dragging with the mouse; false if | |||
they are entering the value using the text box | |||
@returns the value to use instead | |||
@param attemptedValue the value the user is trying to enter | |||
@param dragMode indicates whether the user is dragging with | |||
the mouse; notDragging if they are entering the value | |||
using the text box or other non-dragging interaction | |||
@returns the value to use instead | |||
*/ | |||
virtual double snapValue (double attemptedValue, bool userIsDragging); | |||
virtual double snapValue (double attemptedValue, DragMode dragMode); | |||
//============================================================================== | |||
@@ -874,6 +885,7 @@ private: | |||
JUCE_DEPRECATED (void setMaxValue (double, bool)); | |||
JUCE_DEPRECATED (void setMinAndMaxValues (double, double, bool, bool)); | |||
JUCE_DEPRECATED (void setMinAndMaxValues (double, double, bool)); | |||
virtual void snapValue (double, bool) {} | |||
#endif | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Slider) | |||
@@ -560,7 +560,7 @@ Viewport* TreeView::getViewport() const noexcept | |||
void TreeView::clearSelectedItems() | |||
{ | |||
if (rootItem != nullptr) | |||
rootItem->deselectAllRecursively(); | |||
rootItem->deselectAllRecursively (nullptr); | |||
} | |||
int TreeView::getNumSelectedItems (int maximumDepthToSearchTo) const noexcept | |||
@@ -1299,12 +1299,13 @@ bool TreeViewItem::isSelected() const noexcept | |||
return selected; | |||
} | |||
void TreeViewItem::deselectAllRecursively() | |||
void TreeViewItem::deselectAllRecursively (TreeViewItem* itemToIgnore) | |||
{ | |||
setSelected (false, false); | |||
if (this != itemToIgnore) | |||
setSelected (false, false); | |||
for (int i = 0; i < subItems.size(); ++i) | |||
subItems.getUnchecked(i)->deselectAllRecursively(); | |||
subItems.getUnchecked(i)->deselectAllRecursively (itemToIgnore); | |||
} | |||
void TreeViewItem::setSelected (const bool shouldBeSelected, | |||
@@ -1315,7 +1316,7 @@ void TreeViewItem::setSelected (const bool shouldBeSelected, | |||
return; | |||
if (deselectOtherItemsFirst) | |||
getTopLevelItem()->deselectAllRecursively(); | |||
getTopLevelItem()->deselectAllRecursively (this); | |||
if (shouldBeSelected != selected) | |||
{ | |||
@@ -557,7 +557,7 @@ private: | |||
TreeViewItem* getDeepestOpenParentItem() noexcept; | |||
int getNumRows() const noexcept; | |||
TreeViewItem* getItemOnRow (int index) noexcept; | |||
void deselectAllRecursively(); | |||
void deselectAllRecursively (TreeViewItem* itemToIgnore); | |||
int countSelectedItemsRecursively (int depth) const noexcept; | |||
TreeViewItem* getSelectedItemWithIndex (int index) noexcept; | |||
TreeViewItem* getNextVisibleItem (bool recurse) const noexcept; | |||
@@ -48,8 +48,6 @@ CallOutBox::~CallOutBox() | |||
{ | |||
} | |||
enum { callOutBoxDismissCommandId = 0x4f83a04b }; | |||
//============================================================================== | |||
class CallOutBoxCallback : public ModalComponentManager::Callback, | |||
private Timer | |||
@@ -68,7 +66,7 @@ public: | |||
void timerCallback() override | |||
{ | |||
if (! Process::isForegroundProcess()) | |||
callout.postCommandMessage (callOutBoxDismissCommandId); | |||
callout.dismiss(); | |||
} | |||
ScopedPointer<Component> content; | |||
@@ -127,7 +125,7 @@ void CallOutBox::inputAttemptWhenModal() | |||
// if you click on the area that originally popped-up the callout, you expect it | |||
// to get rid of the box, but deleting the box here allows the click to pass through and | |||
// probably re-trigger it, so we need to dismiss the box asynchronously to consume the click.. | |||
postCommandMessage (callOutBoxDismissCommandId); | |||
dismiss(); | |||
} | |||
else | |||
{ | |||
@@ -136,6 +134,8 @@ void CallOutBox::inputAttemptWhenModal() | |||
} | |||
} | |||
enum { callOutBoxDismissCommandId = 0x4f83a04b }; | |||
void CallOutBox::handleCommandMessage (int commandId) | |||
{ | |||
Component::handleCommandMessage (commandId); | |||
@@ -147,6 +147,11 @@ void CallOutBox::handleCommandMessage (int commandId) | |||
} | |||
} | |||
void CallOutBox::dismiss() | |||
{ | |||
postCommandMessage (callOutBoxDismissCommandId); | |||
} | |||
bool CallOutBox::keyPressed (const KeyPress& key) | |||
{ | |||
if (key.isKeyCode (KeyPress::escapeKey)) | |||
@@ -118,6 +118,11 @@ public: | |||
const Rectangle<int>& areaToPointTo, | |||
Component* parentComponent); | |||
/** Posts a message which will dismiss the callout box asynchronously. | |||
NB: it's safe to call this method from any thread. | |||
*/ | |||
void dismiss(); | |||
//============================================================================== | |||
/** This abstract base class is implemented by LookAndFeel classes. */ | |||
struct JUCE_API LookAndFeelMethods | |||
@@ -102,7 +102,7 @@ public: | |||
/** Returns the raw handle to whatever kind of window is being used. | |||
On windows, this is probably a HWND, on the mac, it's likely to be a WindowRef, | |||
but rememeber there's no guarantees what you'll get back. | |||
but remember there's no guarantees what you'll get back. | |||
*/ | |||
virtual void* getNativeHandle() const = 0; | |||
@@ -25,12 +25,13 @@ | |||
ThreadWithProgressWindow::ThreadWithProgressWindow (const String& title, | |||
const bool hasProgressBar, | |||
const bool hasCancelButton, | |||
const int timeOutMsWhenCancelling_, | |||
const int cancellingTimeOutMs, | |||
const String& cancelButtonText, | |||
Component* componentToCentreAround) | |||
: Thread ("Juce Progress Window"), | |||
progress (0.0), | |||
timeOutMsWhenCancelling (timeOutMsWhenCancelling_) | |||
: Thread ("ThreadWithProgressWindow"), | |||
progress (0.0), | |||
timeOutMsWhenCancelling (cancellingTimeOutMs), | |||
wasCancelledByUser (false) | |||
{ | |||
alertWindow = LookAndFeel::getDefaultLookAndFeel() | |||
.createAlertWindow (title, String(), | |||
@@ -52,8 +53,7 @@ ThreadWithProgressWindow::~ThreadWithProgressWindow() | |||
stopThread (timeOutMsWhenCancelling); | |||
} | |||
#if JUCE_MODAL_LOOPS_PERMITTED | |||
bool ThreadWithProgressWindow::runThread (const int priority) | |||
void ThreadWithProgressWindow::launchThread (int priority) | |||
{ | |||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
@@ -65,15 +65,8 @@ bool ThreadWithProgressWindow::runThread (const int priority) | |||
alertWindow->setMessage (message); | |||
} | |||
const bool finishedNaturally = alertWindow->runModalLoop() != 0; | |||
stopThread (timeOutMsWhenCancelling); | |||
alertWindow->setVisible (false); | |||
return finishedNaturally; | |||
alertWindow->enterModalState(); | |||
} | |||
#endif | |||
void ThreadWithProgressWindow::setProgress (const double newProgress) | |||
{ | |||
@@ -88,15 +81,34 @@ void ThreadWithProgressWindow::setStatusMessage (const String& newStatusMessage) | |||
void ThreadWithProgressWindow::timerCallback() | |||
{ | |||
if (! isThreadRunning()) | |||
bool threadStillRunning = isThreadRunning(); | |||
if (! (threadStillRunning && alertWindow->isCurrentlyModal())) | |||
{ | |||
// thread has finished normally.. | |||
stopTimer(); | |||
stopThread (timeOutMsWhenCancelling); | |||
alertWindow->exitModalState (1); | |||
alertWindow->setVisible (false); | |||
wasCancelledByUser = threadStillRunning; | |||
threadComplete (threadStillRunning); | |||
return; // (this may be deleted now) | |||
} | |||
else | |||
{ | |||
const ScopedLock sl (messageLock); | |||
alertWindow->setMessage (message); | |||
} | |||
const ScopedLock sl (messageLock); | |||
alertWindow->setMessage (message); | |||
} | |||
void ThreadWithProgressWindow::threadComplete (bool) {} | |||
#if JUCE_MODAL_LOOPS_PERMITTED | |||
bool ThreadWithProgressWindow::runThread (const int priority) | |||
{ | |||
launchThread (priority); | |||
while (isTimerRunning()) | |||
MessageManager::getInstance()->runDispatchLoopUntil (5); | |||
return ! wasCancelledByUser; | |||
} | |||
#endif |
@@ -111,6 +111,7 @@ public: | |||
~ThreadWithProgressWindow(); | |||
//============================================================================== | |||
#if JUCE_MODAL_LOOPS_PERMITTED | |||
/** Starts the thread and waits for it to finish. | |||
This will start the thread, make the dialog box appear, and wait until either | |||
@@ -123,6 +124,18 @@ public: | |||
@returns true if the thread finished normally; false if the user pressed cancel | |||
*/ | |||
bool runThread (int threadPriority = 5); | |||
#endif | |||
/** Starts the thread and returns. | |||
This will start the thread and make the dialog box appear in a modal state. When | |||
the thread finishes normally, or the cancel button is pressed, the window will be | |||
hidden and the threadComplete() method will be called. | |||
@param threadPriority the priority to use when starting the thread - see | |||
Thread::startThread() for values | |||
*/ | |||
void launchThread (int threadPriority = 5); | |||
/** The thread should call this periodically to update the position of the progress bar. | |||
@@ -137,15 +150,22 @@ public: | |||
/** Returns the AlertWindow that is being used. */ | |||
AlertWindow* getAlertWindow() const noexcept { return alertWindow; } | |||
//============================================================================== | |||
/** This method is called (on the message thread) when the operation has finished. | |||
You may choose to use this callback to delete the ThreadWithProgressWindow object. | |||
*/ | |||
virtual void threadComplete (bool userPressedCancel); | |||
private: | |||
//============================================================================== | |||
void timerCallback() override; | |||
double progress; | |||
ScopedPointer <AlertWindow> alertWindow; | |||
ScopedPointer<AlertWindow> alertWindow; | |||
String message; | |||
CriticalSection messageLock; | |||
const int timeOutMsWhenCancelling; | |||
bool wasCancelledByUser; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ThreadWithProgressWindow) | |||
}; | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_gui_extra", | |||
"name": "JUCE extended GUI classes", | |||
"version": "3.0.2", | |||
"version": "3.0.4", | |||
"description": "Miscellaneous GUI classes for specialised tasks.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||