| @@ -2,8 +2,8 @@ | |||
| set -e | |||
| JUCE_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/distrho/DISTRHO/libs/juce/source/modules/" | |||
| CARLA_MODULES_DIR="/home/falktx/Personal/FOSS/GIT/falktx/Carla/source/modules/" | |||
| JUCE_MODULES_DIR="/home/falktx/FOSS/GIT-mine/DISTRHO/libs/juce/source/modules/" | |||
| CARLA_MODULES_DIR="/home/falktx/FOSS/GIT-mine/Carla/source/modules/" | |||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
| @@ -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. | |||
| @@ -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 | |||
| @@ -27,11 +27,11 @@ | |||
| //============================================================================== | |||
| #define JUCE_DECLARE_VST3_COM_REF_METHODS \ | |||
| Steinberg::uint32 PLUGIN_API JUCE_CALLTYPE addRef() override { return (Steinberg::uint32) ++refCount; } \ | |||
| Steinberg::uint32 PLUGIN_API 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]; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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). | |||
| @@ -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, | |||
| @@ -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(); | |||
| @@ -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 | |||
| */ | |||
| @@ -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) | |||
| }; | |||
| @@ -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; | |||
| @@ -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) | |||
| }; | |||