| @@ -181,7 +181,7 @@ public: | |||
| for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series | |||
| output = allPass[0][j].process (output); | |||
| samples[i] = output * wet1 + input * dry; | |||
| samples[i] = output * wet1 + samples[i] * dry; | |||
| } | |||
| } | |||
| @@ -29,6 +29,7 @@ MidiMessageSequence::MidiMessageSequence() | |||
| MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) | |||
| { | |||
| list.addCopiesOf (other.list); | |||
| updateMatchedPairs(); | |||
| } | |||
| MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) | |||
| @@ -52,17 +53,17 @@ void MidiMessageSequence::clear() | |||
| list.clear(); | |||
| } | |||
| int MidiMessageSequence::getNumEvents() const | |||
| int MidiMessageSequence::getNumEvents() const noexcept | |||
| { | |||
| return list.size(); | |||
| } | |||
| MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const | |||
| MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (const int index) const noexcept | |||
| { | |||
| return list [index]; | |||
| } | |||
| double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const | |||
| double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const noexcept | |||
| { | |||
| if (const MidiEventHolder* const meh = list [index]) | |||
| if (meh->noteOffObject != nullptr) | |||
| @@ -71,7 +72,7 @@ double MidiMessageSequence::getTimeOfMatchingKeyUp (const int index) const | |||
| return 0.0; | |||
| } | |||
| int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const | |||
| int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcept | |||
| { | |||
| if (const MidiEventHolder* const meh = list [index]) | |||
| return list.indexOf (meh->noteOffObject); | |||
| @@ -79,12 +80,12 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const | |||
| return -1; | |||
| } | |||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const | |||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||
| { | |||
| return list.indexOf (event); | |||
| } | |||
| int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const | |||
| int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const noexcept | |||
| { | |||
| const int numEvents = list.size(); | |||
| @@ -97,17 +98,17 @@ int MidiMessageSequence::getNextIndexAtTime (const double timeStamp) const | |||
| } | |||
| //============================================================================== | |||
| double MidiMessageSequence::getStartTime() const | |||
| double MidiMessageSequence::getStartTime() const noexcept | |||
| { | |||
| return getEventTime (0); | |||
| } | |||
| double MidiMessageSequence::getEndTime() const | |||
| double MidiMessageSequence::getEndTime() const noexcept | |||
| { | |||
| return getEventTime (list.size() - 1); | |||
| } | |||
| double MidiMessageSequence::getEventTime (const int index) const | |||
| double MidiMessageSequence::getEventTime (const int index) const noexcept | |||
| { | |||
| if (const MidiEventHolder* const meh = list [index]) | |||
| return meh->message.getTimeStamp(); | |||
| @@ -181,13 +182,13 @@ void MidiMessageSequence::addSequence (const MidiMessageSequence& other, | |||
| } | |||
| //============================================================================== | |||
| void MidiMessageSequence::sort() | |||
| void MidiMessageSequence::sort() noexcept | |||
| { | |||
| MidiMessageSequenceSorter sorter; | |||
| list.sort (sorter, true); | |||
| } | |||
| void MidiMessageSequence::updateMatchedPairs() | |||
| void MidiMessageSequence::updateMatchedPairs() noexcept | |||
| { | |||
| for (int i = 0; i < list.size(); ++i) | |||
| { | |||
| @@ -226,7 +227,7 @@ void MidiMessageSequence::updateMatchedPairs() | |||
| } | |||
| } | |||
| void MidiMessageSequence::addTimeToMessages (const double delta) | |||
| void MidiMessageSequence::addTimeToMessages (const double delta) noexcept | |||
| { | |||
| for (int i = list.size(); --i >= 0;) | |||
| { | |||
| @@ -91,47 +91,47 @@ public: | |||
| void clear(); | |||
| /** Returns the number of events in the sequence. */ | |||
| int getNumEvents() const; | |||
| int getNumEvents() const noexcept; | |||
| /** Returns a pointer to one of the events. */ | |||
| MidiEventHolder* getEventPointer (int index) const; | |||
| MidiEventHolder* getEventPointer (int index) const noexcept; | |||
| /** Returns the time of the note-up that matches the note-on at this index. | |||
| If the event at this index isn't a note-on, it'll just return 0. | |||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | |||
| */ | |||
| double getTimeOfMatchingKeyUp (int index) const; | |||
| double getTimeOfMatchingKeyUp (int index) const noexcept; | |||
| /** Returns the index of the note-up that matches the note-on at this index. | |||
| If the event at this index isn't a note-on, it'll just return -1. | |||
| @see MidiMessageSequence::MidiEventHolder::noteOffObject | |||
| */ | |||
| int getIndexOfMatchingKeyUp (int index) const; | |||
| int getIndexOfMatchingKeyUp (int index) const noexcept; | |||
| /** Returns the index of an event. */ | |||
| int getIndexOf (MidiEventHolder* event) const; | |||
| int getIndexOf (MidiEventHolder* event) const noexcept; | |||
| /** Returns the index of the first event on or after the given timestamp. | |||
| If the time is beyond the end of the sequence, this will return the | |||
| number of events. | |||
| */ | |||
| int getNextIndexAtTime (double timeStamp) const; | |||
| int getNextIndexAtTime (double timeStamp) const noexcept; | |||
| //============================================================================== | |||
| /** Returns the timestamp of the first event in the sequence. | |||
| @see getEndTime | |||
| */ | |||
| double getStartTime() const; | |||
| double getStartTime() const noexcept; | |||
| /** Returns the timestamp of the last event in the sequence. | |||
| @see getStartTime | |||
| */ | |||
| double getEndTime() const; | |||
| double getEndTime() const noexcept; | |||
| /** Returns the timestamp of the event at a given index. | |||
| If the index is out-of-range, this will return 0.0 | |||
| */ | |||
| double getEventTime (int index) const; | |||
| double getEventTime (int index) const noexcept; | |||
| //============================================================================== | |||
| /** Inserts a midi message into the sequence. | |||
| @@ -185,13 +185,13 @@ public: | |||
| will scan the list and make sure all the note-offs in the MidiEventHolder | |||
| structures are pointing at the correct ones. | |||
| */ | |||
| void updateMatchedPairs(); | |||
| void updateMatchedPairs() noexcept; | |||
| /** Forces a sort of the sequence. | |||
| You may need to call this if you've manually modified the timestamps of some | |||
| events such that the overall order now needs updating. | |||
| */ | |||
| void sort(); | |||
| void sort() noexcept; | |||
| //============================================================================== | |||
| /** Copies all the messages for a particular midi channel to another sequence. | |||
| @@ -224,7 +224,7 @@ public: | |||
| /** Adds an offset to the timestamps of all events in the sequence. | |||
| @param deltaTime the amount to add to each timestamp. | |||
| */ | |||
| void addTimeToMessages (double deltaTime); | |||
| void addTimeToMessages (double deltaTime) noexcept; | |||
| //============================================================================== | |||
| /** Scans through the sequence to determine the state of any midi controllers at | |||
| @@ -859,8 +859,11 @@ void AudioDeviceManager::addMidiInputCallback (const String& name, MidiInputCall | |||
| if (name.isEmpty() || isMidiInputEnabled (name)) | |||
| { | |||
| const ScopedLock sl (midiCallbackLock); | |||
| midiCallbacks.add (callbackToAdd); | |||
| midiCallbackDevices.add (name); | |||
| MidiCallbackInfo mc; | |||
| mc.deviceName = name; | |||
| mc.callback = callbackToAdd; | |||
| midiCallbacks.add (mc); | |||
| } | |||
| } | |||
| @@ -868,11 +871,12 @@ void AudioDeviceManager::removeMidiInputCallback (const String& name, MidiInputC | |||
| { | |||
| for (int i = midiCallbacks.size(); --i >= 0;) | |||
| { | |||
| if (midiCallbackDevices[i] == name && midiCallbacks.getUnchecked(i) == callbackToRemove) | |||
| const MidiCallbackInfo& mc = midiCallbacks.getReference(i); | |||
| if (mc.callback == callbackToRemove && mc.deviceName == name) | |||
| { | |||
| const ScopedLock sl (midiCallbackLock); | |||
| midiCallbacks.remove (i); | |||
| midiCallbackDevices.remove (i); | |||
| } | |||
| } | |||
| } | |||
| @@ -881,16 +885,14 @@ void AudioDeviceManager::handleIncomingMidiMessageInt (MidiInput* source, const | |||
| { | |||
| if (! message.isActiveSense()) | |||
| { | |||
| const bool isDefaultSource = (source == nullptr || source == enabledMidiInputs.getFirst()); | |||
| const ScopedLock sl (midiCallbackLock); | |||
| for (int i = midiCallbackDevices.size(); --i >= 0;) | |||
| for (int i = 0; i < midiCallbacks.size(); ++i) | |||
| { | |||
| const String name (midiCallbackDevices[i]); | |||
| const MidiCallbackInfo& mc = midiCallbacks.getReference(i); | |||
| if ((isDefaultSource && name.isEmpty()) || (name.isNotEmpty() && name == source->getName())) | |||
| midiCallbacks.getUnchecked(i)->handleIncomingMidiMessage (source, message); | |||
| if (mc.deviceName.isEmpty() || mc.deviceName == source->getName()) | |||
| mc.callback->handleIncomingMidiMessage (source, message); | |||
| } | |||
| } | |||
| } | |||
| @@ -209,10 +209,9 @@ public: | |||
| //============================================================================== | |||
| /** Returns the current device properties that are in use. | |||
| @see setAudioDeviceSetup | |||
| */ | |||
| void getAudioDeviceSetup (AudioDeviceSetup& setup); | |||
| void getAudioDeviceSetup (AudioDeviceSetup& result); | |||
| /** Changes the current device or its settings. | |||
| @@ -261,9 +260,7 @@ public: | |||
| void setCurrentAudioDeviceType (const String& type, | |||
| bool treatAsChosenDevice); | |||
| /** Closes the currently-open device. | |||
| You can call restartLastAudioDevice() later to reopen it in the same state | |||
| that it was just in. | |||
| */ | |||
| @@ -305,8 +302,8 @@ public: | |||
| //============================================================================== | |||
| /** Returns the average proportion of available CPU being spent inside the audio callbacks. | |||
| Returns a value between 0 and 1.0 | |||
| @returns A value between 0 and 1.0 to indicate the approximate proportion of CPU | |||
| time spent in the callbacks. | |||
| */ | |||
| double getCpuUsage() const; | |||
| @@ -333,16 +330,16 @@ public: | |||
| void setMidiInputEnabled (const String& midiInputDeviceName, bool enabled); | |||
| /** Returns true if a given midi input device is being used. | |||
| @see setMidiInputEnabled | |||
| */ | |||
| bool isMidiInputEnabled (const String& midiInputDeviceName) const; | |||
| /** Registers a listener for callbacks when midi events arrive from a midi input. | |||
| The device name can be empty to indicate that it wants events from whatever the | |||
| current "default" device is. Or it can be the name of one of the midi input devices | |||
| (see MidiInput::getDevices() for the names). | |||
| The device name can be empty to indicate that it wants to receive all incoming | |||
| events from all the enabled MIDI inputs. Or it can be the name of one of the | |||
| MIDI input devices if it just wants the events from that device. (see | |||
| MidiInput::getDevices() for the list of device names). | |||
| Only devices which are enabled (see the setMidiInputEnabled() method) will have their | |||
| events forwarded on to listeners. | |||
| @@ -350,8 +347,7 @@ public: | |||
| void addMidiInputCallback (const String& midiInputDeviceName, | |||
| MidiInputCallback* callback); | |||
| /** Removes a listener that was previously registered with addMidiInputCallback(). | |||
| */ | |||
| /** Removes a listener that was previously registered with addMidiInputCallback(). */ | |||
| void removeMidiInputCallback (const String& midiInputDeviceName, | |||
| MidiInputCallback* callback); | |||
| @@ -371,22 +367,17 @@ public: | |||
| void setDefaultMidiOutput (const String& deviceName); | |||
| /** Returns the name of the default midi output. | |||
| @see setDefaultMidiOutput, getDefaultMidiOutput | |||
| */ | |||
| String getDefaultMidiOutputName() const { return defaultMidiOutputName; } | |||
| const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputName; } | |||
| /** Returns the current default midi output device. | |||
| If no device has been selected, or the device can't be opened, this will | |||
| return 0. | |||
| If no device has been selected, or the device can't be opened, this will return nullptr. | |||
| @see getDefaultMidiOutputName | |||
| */ | |||
| MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput; } | |||
| /** Returns a list of the types of device supported. | |||
| */ | |||
| /** Returns a list of the types of device supported. */ | |||
| const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes(); | |||
| //============================================================================== | |||
| @@ -429,9 +420,7 @@ public: | |||
| void enableInputLevelMeasurement (bool enableMeasurement); | |||
| /** Returns the current input level. | |||
| To use this, you must first enable it by calling enableInputLevelMeasurement(). | |||
| See enableInputLevelMeasurement() for more info. | |||
| */ | |||
| double getCurrentInputLevel() const; | |||
| @@ -468,10 +457,16 @@ private: | |||
| int testSoundPosition; | |||
| AudioSampleBuffer tempBuffer; | |||
| struct MidiCallbackInfo | |||
| { | |||
| String deviceName; | |||
| MidiInputCallback* callback; | |||
| }; | |||
| StringArray midiInsFromXml; | |||
| OwnedArray<MidiInput> enabledMidiInputs; | |||
| Array<MidiInputCallback*> midiCallbacks; | |||
| StringArray midiCallbackDevices; | |||
| Array<MidiCallbackInfo> midiCallbacks; | |||
| String defaultMidiOutputName; | |||
| ScopedPointer<MidiOutput> defaultMidiOutput; | |||
| CriticalSection audioCallbackLock, midiCallbackLock; | |||
| @@ -22,20 +22,16 @@ | |||
| ============================================================================== | |||
| */ | |||
| AudioIODevice::AudioIODevice (const String& deviceName, const String& typeName_) | |||
| : name (deviceName), | |||
| typeName (typeName_) | |||
| AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName) | |||
| : name (deviceName), typeName (deviceTypeName) | |||
| { | |||
| } | |||
| AudioIODevice::~AudioIODevice() | |||
| { | |||
| } | |||
| AudioIODevice::~AudioIODevice() {} | |||
| bool AudioIODevice::hasControlPanel() const | |||
| { | |||
| return false; | |||
| } | |||
| void AudioIODeviceCallback::audioDeviceError (const String&) {} | |||
| bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; } | |||
| bool AudioIODevice::hasControlPanel() const { return false; } | |||
| bool AudioIODevice::showControlPanel() | |||
| { | |||
| @@ -43,6 +39,3 @@ bool AudioIODevice::showControlPanel() | |||
| // their hasControlPanel() method. | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| void AudioIODeviceCallback::audioDeviceError (const String&) {} | |||
| @@ -289,6 +289,11 @@ public: | |||
| */ | |||
| virtual bool showControlPanel(); | |||
| /** On devices which support it, this allows automatic gain control or other | |||
| mic processing to be disabled. | |||
| If the device doesn't support this operation, it'll return false. | |||
| */ | |||
| virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled); | |||
| //============================================================================== | |||
| protected: | |||
| @@ -34,7 +34,7 @@ | |||
| method. Each of the objects returned can then be used to list the available | |||
| devices of that type. E.g. | |||
| @code | |||
| OwnedArray <AudioIODeviceType> types; | |||
| OwnedArray<AudioIODeviceType> types; | |||
| myAudioDeviceManager.createAudioDeviceTypes (types); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| @@ -125,6 +125,7 @@ | |||
| #if JUCE_USE_ANDROID_OPENSLES | |||
| #include <SLES/OpenSLES.h> | |||
| #include <SLES/OpenSLES_Android.h> | |||
| #include <SLES/OpenSLES_AndroidConfiguration.h> | |||
| #endif | |||
| #endif | |||
| @@ -80,7 +80,7 @@ public: | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | |||
| static const double rates[] = { 8000.0, 16000.0, 32000.0, 44100.0, 48000.0 }; | |||
| return Array<double> (rates, numElementsInArray (rates)); | |||
| } | |||
| @@ -165,29 +165,9 @@ public: | |||
| oldCallback->audioDeviceStopped(); | |||
| } | |||
| void run() override | |||
| bool setAudioPreprocessingEnabled (bool enable) override | |||
| { | |||
| if (recorder != nullptr) recorder->start(); | |||
| if (player != nullptr) player->start(); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (player != nullptr) player->writeBuffer (outputBuffer, *this); | |||
| if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | |||
| numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| { | |||
| outputBuffer.clear(); | |||
| } | |||
| } | |||
| return recorder != nullptr && recorder->setAudioPreprocessingEnabled (enable); | |||
| } | |||
| private: | |||
| @@ -212,6 +192,31 @@ private: | |||
| return oldCallback; | |||
| } | |||
| void run() override | |||
| { | |||
| if (recorder != nullptr) recorder->start(); | |||
| if (player != nullptr) player->start(); | |||
| while (! threadShouldExit()) | |||
| { | |||
| if (player != nullptr) player->writeBuffer (outputBuffer, *this); | |||
| if (recorder != nullptr) recorder->readNextBlock (inputBuffer, *this); | |||
| const ScopedLock sl (callbackLock); | |||
| if (callback != nullptr) | |||
| { | |||
| callback->audioDeviceIOCallback (numInputChannels > 0 ? inputBuffer.getArrayOfReadPointers() : nullptr, numInputChannels, | |||
| numOutputChannels > 0 ? outputBuffer.getArrayOfWritePointers() : nullptr, numOutputChannels, | |||
| actualBufferSize); | |||
| } | |||
| else | |||
| { | |||
| outputBuffer.clear(); | |||
| } | |||
| } | |||
| } | |||
| //================================================================================================== | |||
| struct Engine | |||
| { | |||
| @@ -230,6 +235,7 @@ private: | |||
| SL_IID_ANDROIDSIMPLEBUFFERQUEUE = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); | |||
| SL_IID_PLAY = (SLInterfaceID*) library.getFunction ("SL_IID_PLAY"); | |||
| SL_IID_RECORD = (SLInterfaceID*) library.getFunction ("SL_IID_RECORD"); | |||
| SL_IID_ANDROIDCONFIGURATION = (SLInterfaceID*) library.getFunction ("SL_IID_ANDROIDCONFIGURATION"); | |||
| check ((*engineObject)->Realize (engineObject, SL_BOOLEAN_FALSE)); | |||
| check ((*engineObject)->GetInterface (engineObject, *SL_IID_ENGINE, &engineInterface)); | |||
| @@ -271,6 +277,7 @@ private: | |||
| SLInterfaceID* SL_IID_ANDROIDSIMPLEBUFFERQUEUE; | |||
| SLInterfaceID* SL_IID_PLAY; | |||
| SLInterfaceID* SL_IID_RECORD; | |||
| SLInterfaceID* SL_IID_ANDROIDCONFIGURATION; | |||
| private: | |||
| DynamicLibrary library; | |||
| @@ -434,7 +441,8 @@ private: | |||
| struct Recorder | |||
| { | |||
| Recorder (int numChannels, int sampleRate, Engine& engine) | |||
| : recorderObject (nullptr), recorderRecord (nullptr), recorderBufferQueue (nullptr), | |||
| : recorderObject (nullptr), recorderRecord (nullptr), | |||
| recorderBufferQueue (nullptr), configObject (nullptr), | |||
| bufferList (numChannels) | |||
| { | |||
| jassert (numChannels == 1); // STEREO doesn't always work!! | |||
| @@ -466,6 +474,7 @@ private: | |||
| { | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_RECORD, &recorderRecord)); | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &recorderBufferQueue)); | |||
| check ((*recorderObject)->GetInterface (recorderObject, *engine.SL_IID_ANDROIDCONFIGURATION, &configObject)); | |||
| check ((*recorderBufferQueue)->RegisterCallback (recorderBufferQueue, staticCallback, this)); | |||
| check ((*recorderRecord)->SetRecordState (recorderRecord, SL_RECORDSTATE_STOPPED)); | |||
| @@ -532,10 +541,20 @@ private: | |||
| } | |||
| } | |||
| bool setAudioPreprocessingEnabled (bool enable) | |||
| { | |||
| SLuint32 mode = enable ? SL_ANDROID_RECORDING_PRESET_GENERIC | |||
| : SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION; | |||
| return configObject != nullptr | |||
| && check ((*configObject)->SetConfiguration (configObject, SL_ANDROID_KEY_RECORDING_PRESET, &mode, sizeof (mode))); | |||
| } | |||
| private: | |||
| SLObjectItf recorderObject; | |||
| SLRecordItf recorderRecord; | |||
| SLAndroidSimpleBufferQueueItf recorderBufferQueue; | |||
| SLAndroidConfigurationItf configObject; | |||
| BufferList bufferList; | |||
| @@ -67,7 +67,12 @@ public: | |||
| return s; | |||
| } | |||
| Array<double> getAvailableSampleRates() override { return sampleRates; } | |||
| Array<double> getAvailableSampleRates() override | |||
| { | |||
| // can't find a good way to actually ask the device for which of these it supports.. | |||
| static const double rates[] = { 8000.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0 }; | |||
| return Array<double> (rates, numElementsInArray (rates)); | |||
| } | |||
| Array<int> getAvailableBufferSizes() override | |||
| { | |||
| @@ -79,7 +84,7 @@ public: | |||
| return r; | |||
| } | |||
| int getDefaultBufferSize() override { return 1024; } | |||
| int getDefaultBufferSize() override { return 1024; } | |||
| String open (const BigInteger& inputChannelsWanted, | |||
| const BigInteger& outputChannelsWanted, | |||
| @@ -117,10 +122,9 @@ public: | |||
| AudioSessionAddPropertyListener (kAudioSessionProperty_AudioRouteChange, routingChangedStatic, this); | |||
| fixAudioRouteIfSetToReceiver(); | |||
| updateDeviceInfo(); | |||
| setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, targetSampleRate); | |||
| updateSampleRates(); | |||
| updateDeviceInfo(); | |||
| setSessionFloat32Property (kAudioSessionProperty_PreferredHardwareIOBufferDuration, preferredBufferSize / sampleRate); | |||
| updateCurrentBufferSize(); | |||
| @@ -162,8 +166,15 @@ public: | |||
| BigInteger getActiveOutputChannels() const override { return activeOutputChans; } | |||
| BigInteger getActiveInputChannels() const override { return activeInputChans; } | |||
| int getOutputLatencyInSamples() override { return 0; } //xxx | |||
| int getInputLatencyInSamples() override { return 0; } //xxx | |||
| int getOutputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareOutputLatency); } | |||
| int getInputLatencyInSamples() override { return getLatency (kAudioSessionProperty_CurrentHardwareInputLatency); } | |||
| int getLatency (AudioSessionPropertyID propID) | |||
| { | |||
| Float32 latency = 0; | |||
| getSessionProperty (propID, latency); | |||
| return roundToInt (latency * getCurrentSampleRate()); | |||
| } | |||
| void start (AudioIODeviceCallback* newCallback) override | |||
| { | |||
| @@ -197,11 +208,16 @@ public: | |||
| bool isPlaying() override { return isRunning && callback != nullptr; } | |||
| String getLastError() override { return lastError; } | |||
| bool setAudioPreprocessingEnabled (bool enable) override | |||
| { | |||
| return setSessionUInt32Property (kAudioSessionProperty_Mode, enable ? kAudioSessionMode_Default | |||
| : kAudioSessionMode_Measurement); | |||
| } | |||
| private: | |||
| //================================================================================================== | |||
| CriticalSection callbackLock; | |||
| Float64 sampleRate; | |||
| Array<Float64> sampleRates; | |||
| int numInputChannels, numOutputChannels; | |||
| int preferredBufferSize, actualBufferSize; | |||
| bool isRunning; | |||
| @@ -322,38 +338,6 @@ private: | |||
| getSessionProperty (kAudioSessionProperty_AudioInputAvailable, audioInputIsAvailable); | |||
| } | |||
| void updateSampleRates() | |||
| { | |||
| getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate); | |||
| sampleRates.clear(); | |||
| sampleRates.add (sampleRate); | |||
| const int commonSampleRates[] = { 8000, 16000, 22050, 32000, 44100, 48000 }; | |||
| for (int i = 0; i < numElementsInArray (commonSampleRates); ++i) | |||
| { | |||
| Float64 rate = (Float64) commonSampleRates[i]; | |||
| if (rate != sampleRate) | |||
| { | |||
| setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, rate); | |||
| Float64 actualSampleRate = 0.0; | |||
| getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, actualSampleRate); | |||
| if (actualSampleRate == rate) | |||
| sampleRates.add (actualSampleRate); | |||
| } | |||
| } | |||
| DefaultElementComparator<Float64> comparator; | |||
| sampleRates.sort (comparator); | |||
| setSessionFloat64Property (kAudioSessionProperty_PreferredHardwareSampleRate, sampleRate); | |||
| getSessionProperty (kAudioSessionProperty_CurrentHardwareSampleRate, sampleRate); | |||
| } | |||
| void updateCurrentBufferSize() | |||
| { | |||
| Float32 bufferDuration = sampleRate > 0 ? (Float32) (preferredBufferSize / sampleRate) : 0.0f; | |||
| @@ -455,12 +439,12 @@ private: | |||
| static OSStatus processStatic (void* client, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time, | |||
| UInt32 /*busNumber*/, UInt32 numFrames, AudioBufferList* data) | |||
| { | |||
| return static_cast <iOSAudioIODevice*> (client)->process (flags, time, numFrames, data); | |||
| return static_cast<iOSAudioIODevice*> (client)->process (flags, time, numFrames, data); | |||
| } | |||
| static void routingChangedStatic (void* client, AudioSessionPropertyID, UInt32 /*inDataSize*/, const void* propertyValue) | |||
| { | |||
| static_cast <iOSAudioIODevice*> (client)->routingChanged (propertyValue); | |||
| static_cast<iOSAudioIODevice*> (client)->routingChanged (propertyValue); | |||
| } | |||
| //================================================================================================== | |||
| @@ -552,9 +536,9 @@ private: | |||
| return AudioSessionGetProperty (propID, &valueSize, &result); | |||
| } | |||
| static void setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } | |||
| static void setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } | |||
| static void setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v); } | |||
| static bool setSessionUInt32Property (AudioSessionPropertyID propID, UInt32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| static bool setSessionFloat32Property (AudioSessionPropertyID propID, Float32 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| static bool setSessionFloat64Property (AudioSessionPropertyID propID, Float64 v) noexcept { AudioSessionSetProperty (propID, sizeof (v), &v) == kAudioSessionNoError; } | |||
| JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice) | |||
| }; | |||
| @@ -144,6 +144,7 @@ public: | |||
| : owner (d), | |||
| inputLatency (0), | |||
| outputLatency (0), | |||
| bitDepth (32), | |||
| callback (nullptr), | |||
| #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 | |||
| audioProcID (0), | |||
| @@ -352,6 +353,22 @@ public: | |||
| return (int) lat; | |||
| } | |||
| int getBitDepthFromDevice (AudioObjectPropertyScope scope) const | |||
| { | |||
| AudioObjectPropertyAddress pa; | |||
| pa.mElement = kAudioObjectPropertyElementMaster; | |||
| pa.mSelector = kAudioStreamPropertyPhysicalFormat; | |||
| pa.mScope = scope; | |||
| AudioStreamBasicDescription asbd; | |||
| UInt32 size = sizeof (asbd); | |||
| if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, nullptr, &size, &asbd))) | |||
| return (int) asbd.mBitsPerChannel; | |||
| return 0; | |||
| } | |||
| void updateDetailsFromDevice() | |||
| { | |||
| stopTimer(); | |||
| @@ -392,6 +409,13 @@ public: | |||
| StringArray newInNames (getChannelInfo (true, newInChans)); | |||
| StringArray newOutNames (getChannelInfo (false, newOutChans)); | |||
| const int inputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeInput); | |||
| const int outputBitDepth = getBitDepthFromDevice (kAudioDevicePropertyScopeOutput); | |||
| bitDepth = jmax (inputBitDepth, outputBitDepth); | |||
| if (bitDepth <= 0) | |||
| bitDepth = 32; | |||
| // after getting the new values, lock + apply them | |||
| const ScopedLock sl (callbackLock); | |||
| @@ -728,6 +752,7 @@ public: | |||
| //============================================================================== | |||
| CoreAudioIODevice& owner; | |||
| int inputLatency, outputLatency; | |||
| int bitDepth; | |||
| BigInteger activeInputChans, activeOutputChans; | |||
| StringArray inChanNames, outChanNames; | |||
| Array<double> sampleRates; | |||
| @@ -886,7 +911,7 @@ public: | |||
| Array<int> getAvailableBufferSizes() override { return internal->bufferSizes; } | |||
| double getCurrentSampleRate() override { return internal->getSampleRate(); } | |||
| int getCurrentBitDepth() override { return 32; } // no way to find out, so just assume it's high.. | |||
| int getCurrentBitDepth() override { return internal->bitDepth; } | |||
| int getCurrentBufferSizeSamples() override { return internal->getBufferSize(); } | |||
| int getDefaultBufferSize() override | |||
| @@ -1387,7 +1412,7 @@ private: | |||
| d.done = (d.numInputChans == 0); | |||
| } | |||
| for (int tries = 3;;) | |||
| for (int tries = 5;;) | |||
| { | |||
| bool anyRemaining = false; | |||
| @@ -1434,7 +1459,7 @@ private: | |||
| d.done = (d.numOutputChans == 0); | |||
| } | |||
| for (int tries = 3;;) | |||
| for (int tries = 5;;) | |||
| { | |||
| bool anyRemaining = false; | |||
| @@ -888,13 +888,13 @@ AiffAudioFormat::~AiffAudioFormat() | |||
| Array<int> AiffAudioFormat::getPossibleSampleRates() | |||
| { | |||
| const int rates[] = { 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 0 }; | |||
| return Array <int> (rates); | |||
| return Array<int> (rates); | |||
| } | |||
| Array<int> AiffAudioFormat::getPossibleBitDepths() | |||
| { | |||
| const int depths[] = { 8, 16, 24, 0 }; | |||
| return Array <int> (depths); | |||
| return Array<int> (depths); | |||
| } | |||
| bool AiffAudioFormat::canDoStereo() { return true; } | |||
| @@ -54,7 +54,7 @@ public: | |||
| (e.g. VST shells) can use a single DLL to create a set of different plugin | |||
| subtypes, so in that case, each subtype is returned as a separate object. | |||
| */ | |||
| virtual void findAllTypesForFile (OwnedArray <PluginDescription>& results, | |||
| virtual void findAllTypesForFile (OwnedArray<PluginDescription>& results, | |||
| const String& fileOrIdentifier) = 0; | |||
| /** Tries to recreate a type from a previously generated PluginDescription. | |||
| @@ -89,7 +89,7 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| OwnedArray <AudioPluginFormat> formats; | |||
| OwnedArray<AudioPluginFormat> formats; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | |||
| }; | |||
| @@ -37,7 +37,7 @@ public: | |||
| //============================================================================== | |||
| String getName() const override { return "AudioUnit"; } | |||
| void findAllTypesForFile (OwnedArray <PluginDescription>&, const String& fileOrIdentifier) override; | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; | |||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | |||
| @@ -138,7 +138,7 @@ namespace AudioUnitFormatHelpers | |||
| fileOrIdentifier.lastIndexOfChar ('/')) + 1)); | |||
| StringArray tokens; | |||
| tokens.addTokens (s, ",", String()); | |||
| tokens.addTokens (s, ",", StringRef()); | |||
| tokens.removeEmptyStrings(); | |||
| if (tokens.size() == 3) | |||
| @@ -915,7 +915,7 @@ private: | |||
| bool automatable; | |||
| }; | |||
| OwnedArray <ParamInfo> parameters; | |||
| OwnedArray<ParamInfo> parameters; | |||
| MidiDataConcatenator midiConcatenator; | |||
| CriticalSection midiInLock; | |||
| @@ -1600,7 +1600,7 @@ AudioUnitPluginFormat::~AudioUnitPluginFormat() | |||
| { | |||
| } | |||
| void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray <PluginDescription>& results, | |||
| void AudioUnitPluginFormat::findAllTypesForFile (OwnedArray<PluginDescription>& results, | |||
| const String& fileOrIdentifier) | |||
| { | |||
| if (! fileMightContainThisPluginType (fileOrIdentifier)) | |||
| @@ -85,6 +85,18 @@ static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
| //============================================================================== | |||
| static Steinberg::Vst::SpeakerArrangement getArrangementForBus (Steinberg::Vst::IAudioProcessor* processor, | |||
| bool isInput, int busIndex) | |||
| { | |||
| Steinberg::Vst::SpeakerArrangement arrangement = Steinberg::Vst::SpeakerArr::kEmpty; | |||
| if (processor != nullptr) | |||
| processor->getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput, | |||
| (Steinberg::int32) busIndex, arrangement); | |||
| return arrangement; | |||
| } | |||
| /** For the sake of simplicity, there can only be 1 arrangement type per channel count. | |||
| i.e.: 4 channels == k31Cine OR k40Cine | |||
| */ | |||
| @@ -126,9 +138,9 @@ class ComSmartPtr | |||
| { | |||
| public: | |||
| ComSmartPtr() noexcept : source (nullptr) {} | |||
| ComSmartPtr (ObjectType* object) noexcept : source (object) { if (source != nullptr) source->addRef(); } | |||
| ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } | |||
| ~ComSmartPtr() { if (source != nullptr) source->release(); } | |||
| ComSmartPtr (ObjectType* object, bool autoAddRef = true) noexcept : source (object) { if (source != nullptr && autoAddRef) source->addRef(); } | |||
| ComSmartPtr (const ComSmartPtr& other) noexcept : source (other.source) { if (source != nullptr) source->addRef(); } | |||
| ~ComSmartPtr() { if (source != nullptr) source->release(); } | |||
| operator ObjectType*() const noexcept { return source; } | |||
| ObjectType* get() const noexcept { return source; } | |||
| @@ -341,7 +353,7 @@ namespace VST3BufferExchange | |||
| Bus& bus, | |||
| AudioSampleBuffer& buffer, | |||
| int numChannels, int channelStartOffset, | |||
| int sampleOffset = 0) noexcept | |||
| int sampleOffset = 0) | |||
| { | |||
| const int channelEnd = numChannels + channelStartOffset; | |||
| jassert (channelEnd >= 0 && channelEnd <= buffer.getNumChannels()); | |||
| @@ -356,37 +368,52 @@ namespace VST3BufferExchange | |||
| vstBuffers.silenceFlags = 0; | |||
| } | |||
| static void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| Steinberg::Vst::IAudioProcessor& processor, | |||
| BusMap& busMapToUse, | |||
| bool isInput, int numBusses, | |||
| AudioSampleBuffer& source) | |||
| static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||
| Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||
| AudioSampleBuffer& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||
| for (int i = 0; i < numBusses; ++i) | |||
| if (index >= result.size()) | |||
| result.add (Steinberg::Vst::AudioBusBuffers()); | |||
| if (index >= busMapToUse.size()) | |||
| busMapToUse.add (Bus()); | |||
| if (numChansForBus > 0) | |||
| { | |||
| Steinberg::Vst::SpeakerArrangement arrangement = 0; | |||
| processor.getBusArrangement (isInput ? Steinberg::Vst::kInput : Steinberg::Vst::kOutput, | |||
| (Steinberg::int32) i, arrangement); | |||
| associateBufferTo (result.getReference (index), | |||
| busMapToUse.getReference (index), | |||
| source, numChansForBus, channelIndexOffset); | |||
| } | |||
| const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||
| channelIndexOffset += numChansForBus; | |||
| } | |||
| if (i >= result.size()) | |||
| result.add (Steinberg::Vst::AudioBusBuffers()); | |||
| static void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
| const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||
| AudioSampleBuffer& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| if (i >= busMapToUse.size()) | |||
| busMapToUse.add (Bus()); | |||
| for (int i = 0; i < arrangements.size(); ++i) | |||
| mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, | |||
| arrangements.getUnchecked (i), source); | |||
| } | |||
| if (numChansForBus > 0) | |||
| { | |||
| associateBufferTo (result.getReference (i), | |||
| busMapToUse.getReference (i), | |||
| source, numChansForBus, channelIndexOffset); | |||
| } | |||
| static void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| Steinberg::Vst::IAudioProcessor& processor, | |||
| BusMap& busMapToUse, bool isInput, int numBusses, | |||
| AudioSampleBuffer& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| channelIndexOffset += numChansForBus; | |||
| } | |||
| for (int i = 0; i < numBusses; ++i) | |||
| mapArrangementToBusses (channelIndexOffset, i, | |||
| result, busMapToUse, | |||
| getArrangementForBus (&processor, isInput, i), | |||
| source); | |||
| } | |||
| } | |||
| @@ -31,6 +31,7 @@ | |||
| #define JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY 1 | |||
| #endif | |||
| #include <map> | |||
| #include "juce_VST3Headers.h" | |||
| #undef JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | |||
| @@ -111,6 +112,7 @@ static void createPluginDescription (PluginDescription& description, | |||
| description.lastFileModTime = pluginFile.getLastModificationTime(); | |||
| description.manufacturerName = company; | |||
| description.name = name; | |||
| description.descriptiveName = name; | |||
| description.pluginFormatName = "VST3"; | |||
| description.numInputChannels = numInputs; | |||
| description.numOutputChannels = numOutputs; | |||
| @@ -378,32 +380,70 @@ public: | |||
| FUnknown* getFUnknown() { return static_cast<Vst::IComponentHandler*> (this); } | |||
| static bool hasFlag (Steinberg::int32 source, Steinberg::int32 flag) noexcept | |||
| { | |||
| return (source & flag) == flag; | |||
| } | |||
| //============================================================================== | |||
| tresult PLUGIN_API beginEdit (Vst::ParamID) override | |||
| tresult PLUGIN_API beginEdit (Vst::ParamID paramID) override | |||
| { | |||
| // XXX todo.. | |||
| return kResultFalse; | |||
| const int index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| owner->beginParameterChangeGesture (index); | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API performEdit (Vst::ParamID id, Vst::ParamValue valueNormalized) override | |||
| tresult PLUGIN_API performEdit (Vst::ParamID paramID, Vst::ParamValue valueNormalized) override | |||
| { | |||
| if (owner != nullptr) | |||
| return owner->editController->setParamNormalized (id, valueNormalized); | |||
| const int index = getIndexOfParamID (paramID); | |||
| return kResultFalse; | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| owner->sendParamChangeMessageToListeners (index, (float) valueNormalized); | |||
| return owner->editController->setParamNormalized (paramID, valueNormalized); | |||
| } | |||
| tresult PLUGIN_API endEdit (Vst::ParamID) override | |||
| tresult PLUGIN_API endEdit (Vst::ParamID paramID) override | |||
| { | |||
| // XXX todo.. | |||
| return kResultFalse; | |||
| const int index = getIndexOfParamID (paramID); | |||
| if (index < 0) | |||
| return kResultFalse; | |||
| owner->endParameterChangeGesture (index); | |||
| return kResultTrue; | |||
| } | |||
| tresult PLUGIN_API restartComponent (Steinberg::int32) override | |||
| tresult PLUGIN_API restartComponent (Steinberg::int32 flags) override | |||
| { | |||
| if (owner != nullptr) | |||
| { | |||
| owner->reset(); | |||
| if (hasFlag (flags, Vst::kReloadComponent)) | |||
| owner->reset(); | |||
| if (hasFlag (flags, Vst::kIoChanged)) | |||
| { | |||
| double sampleRate = owner->getSampleRate(); | |||
| int numSamples = owner->getBlockSize(); | |||
| if (sampleRate <= 8000.0) | |||
| sampleRate = 44100.0; | |||
| if (numSamples <= 0) | |||
| numSamples = 1024; | |||
| owner->prepareToPlay (owner->getSampleRate(), owner->getBlockSize()); | |||
| } | |||
| if (hasFlag (flags, Vst::kLatencyChanged)) | |||
| if (owner->processor != nullptr) | |||
| owner->setLatencySamples (jmax (0, (int) owner->processor->getLatencySamples())); | |||
| owner->updateHostDisplay(); | |||
| return kResultTrue; | |||
| } | |||
| @@ -437,9 +477,171 @@ public: | |||
| return kResultFalse; | |||
| } | |||
| //============================================================================== | |||
| class ContextMenu : public Vst::IContextMenu | |||
| { | |||
| public: | |||
| ContextMenu (VST3PluginInstance& pluginInstance) : owner (pluginInstance) {} | |||
| virtual ~ContextMenu() {} | |||
| JUCE_DECLARE_VST3_COM_REF_METHODS | |||
| JUCE_DECLARE_VST3_COM_QUERY_METHODS | |||
| Steinberg::int32 PLUGIN_API getItemCount() override { return (Steinberg::int32) items.size(); } | |||
| tresult PLUGIN_API addItem (const Item& item, IContextMenuTarget* target) override | |||
| { | |||
| jassert (target != nullptr); | |||
| ItemAndTarget newItem; | |||
| newItem.item = item; | |||
| newItem.target = target; | |||
| items.add (newItem); | |||
| return kResultOk; | |||
| } | |||
| tresult PLUGIN_API removeItem (const Item& toRemove, IContextMenuTarget* target) override | |||
| { | |||
| for (int i = items.size(); --i >= 0;) | |||
| { | |||
| ItemAndTarget& item = items.getReference(i); | |||
| if (item.item.tag == toRemove.tag && item.target == target) | |||
| items.remove (i); | |||
| } | |||
| return kResultOk; | |||
| } | |||
| tresult PLUGIN_API getItem (Steinberg::int32 tag, Item& result, IContextMenuTarget** target) override | |||
| { | |||
| for (int i = 0; i < items.size(); ++i) | |||
| { | |||
| const ItemAndTarget& item = items.getReference(i); | |||
| if (item.item.tag == tag) | |||
| { | |||
| result = item.item; | |||
| if (target != nullptr) | |||
| *target = item.target; | |||
| return kResultTrue; | |||
| } | |||
| } | |||
| zerostruct (result); | |||
| return kResultFalse; | |||
| } | |||
| tresult PLUGIN_API popup (Steinberg::UCoord x, Steinberg::UCoord y) override | |||
| { | |||
| Array<const Item*> subItemStack; | |||
| OwnedArray<PopupMenu> menuStack; | |||
| PopupMenu* topLevelMenu = menuStack.add (new PopupMenu()); | |||
| for (int i = 0; i < items.size(); ++i) | |||
| { | |||
| const Item& item = items.getReference (i).item; | |||
| PopupMenu* menuToUse = menuStack.getLast(); | |||
| if (hasFlag (item.flags, Item::kIsGroupStart & ~Item::kIsDisabled)) | |||
| { | |||
| subItemStack.add (&item); | |||
| menuStack.add (new PopupMenu()); | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsGroupEnd)) | |||
| { | |||
| if (const Item* subItem = subItemStack.getLast()) | |||
| { | |||
| if (PopupMenu* m = menuStack [menuStack.size() - 2]) | |||
| m->addSubMenu (toString (subItem->name), *menuToUse, | |||
| ! hasFlag (subItem->flags, Item::kIsDisabled), | |||
| nullptr, | |||
| hasFlag (subItem->flags, Item::kIsChecked)); | |||
| menuStack.removeLast (1); | |||
| subItemStack.removeLast (1); | |||
| } | |||
| } | |||
| else if (hasFlag (item.flags, Item::kIsSeparator)) | |||
| { | |||
| menuToUse->addSeparator(); | |||
| } | |||
| else | |||
| { | |||
| menuToUse->addItem (item.tag != 0 ? (int) item.tag : (int) zeroTagReplacement, | |||
| toString (item.name), | |||
| ! hasFlag (item.flags, Item::kIsDisabled), | |||
| hasFlag (item.flags, Item::kIsChecked)); | |||
| } | |||
| } | |||
| PopupMenu::Options options; | |||
| if (AudioProcessorEditor* ed = owner.getActiveEditor()) | |||
| options = options.withTargetScreenArea (ed->getScreenBounds().translated ((int) x, (int) y).withSize (1, 1)); | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| // Unfortunately, Steinberg's docs explicitly say this should be modal.. | |||
| handleResult (topLevelMenu->showMenu (options)); | |||
| #else | |||
| topLevelMenu->showMenuAsync (options, ModalCallbackFunction::create (menuFinished, ComSmartPtr<ContextMenu> (this))); | |||
| #endif | |||
| return kResultOk; | |||
| } | |||
| #if ! JUCE_MODAL_LOOPS_PERMITTED | |||
| static void menuFinished (int modalResult, ComSmartPtr<ContextMenu> menu) { menu->handleResult (modalResult); } | |||
| #endif | |||
| private: | |||
| enum { zeroTagReplacement = 0x7fffffff }; | |||
| Atomic<int> refCount; | |||
| VST3PluginInstance& owner; | |||
| struct ItemAndTarget | |||
| { | |||
| Item item; | |||
| ComSmartPtr<IContextMenuTarget> target; | |||
| }; | |||
| Array<ItemAndTarget> items; | |||
| void handleResult (int result) | |||
| { | |||
| if (result == 0) | |||
| return; | |||
| if (result == zeroTagReplacement) | |||
| result = 0; | |||
| for (int i = 0; i < items.size(); ++i) | |||
| { | |||
| const ItemAndTarget& item = items.getReference(i); | |||
| if ((int) item.item.tag == result) | |||
| { | |||
| if (item.target != nullptr) | |||
| item.target->executeMenuItem ((Steinberg::int32) result); | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContextMenu) | |||
| }; | |||
| Vst::IContextMenu* PLUGIN_API createContextMenu (IPlugView*, const Vst::ParamID*) override | |||
| { | |||
| jassertfalse; | |||
| if (owner != nullptr) | |||
| return new ContextMenu (*owner); | |||
| return nullptr; | |||
| } | |||
| @@ -549,9 +751,42 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| Atomic<int32> refCount; | |||
| VST3PluginInstance* const owner; | |||
| Atomic<int> refCount; | |||
| String appName; | |||
| VST3PluginInstance* owner; | |||
| typedef std::map<Vst::ParamID, int> ParamMapType; | |||
| ParamMapType paramToIndexMap; | |||
| int getIndexOfParamID (Vst::ParamID paramID) | |||
| { | |||
| if (owner == nullptr || owner->editController == nullptr) | |||
| return -1; | |||
| int result = getMappedParamID (paramID); | |||
| if (result < 0) | |||
| { | |||
| const int numParams = owner->editController->getParameterCount(); | |||
| for (int i = 0; i < numParams; ++i) | |||
| { | |||
| Vst::ParameterInfo paramInfo; | |||
| owner->editController->getParameterInfo (i, paramInfo); | |||
| paramToIndexMap[paramInfo.id] = i; | |||
| } | |||
| result = getMappedParamID (paramID); | |||
| } | |||
| return result; | |||
| } | |||
| int getMappedParamID (Vst::ParamID paramID) | |||
| { | |||
| const ParamMapType::iterator it (paramToIndexMap.find (paramID)); | |||
| return it != paramToIndexMap.end() ? it->second : -1; | |||
| } | |||
| //============================================================================== | |||
| class Message : public Vst::IMessage | |||
| @@ -577,9 +812,9 @@ private: | |||
| JUCE_DECLARE_VST3_COM_REF_METHODS | |||
| JUCE_DECLARE_VST3_COM_QUERY_METHODS | |||
| FIDString PLUGIN_API getMessageID() { return messageId.toRawUTF8(); } | |||
| void PLUGIN_API setMessageID (FIDString id) { messageId = toString (id); } | |||
| Vst::IAttributeList* PLUGIN_API getAttributes() { return attributeList; } | |||
| FIDString PLUGIN_API getMessageID() override { return messageId.toRawUTF8(); } | |||
| void PLUGIN_API setMessageID (FIDString id) override { messageId = toString (id); } | |||
| Vst::IAttributeList* PLUGIN_API getAttributes() override { return attributeList; } | |||
| var value; | |||
| @@ -587,7 +822,7 @@ private: | |||
| VST3HostContext& owner; | |||
| ComSmartPtr<Vst::IAttributeList> attributeList; | |||
| String messageId; | |||
| Atomic<int32> refCount; | |||
| Atomic<int> refCount; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Message) | |||
| }; | |||
| @@ -715,7 +950,7 @@ private: | |||
| private: | |||
| VST3HostContext* owner; | |||
| Atomic<int32> refCount; | |||
| Atomic<int> refCount; | |||
| //============================================================================== | |||
| template<typename Type> | |||
| @@ -1195,7 +1430,8 @@ public: | |||
| VST3PluginWindow (AudioProcessor* owner, IPlugView* pluginView) | |||
| : AudioProcessorEditor (owner), | |||
| ComponentMovementWatcher (this), | |||
| view (pluginView), | |||
| refCount (1), | |||
| view (pluginView, false), | |||
| pluginHandle (nullptr), | |||
| recursiveResize (false) | |||
| { | |||
| @@ -1266,20 +1502,32 @@ public: | |||
| { | |||
| rect.right = (Steinberg::int32) getWidth(); | |||
| rect.bottom = (Steinberg::int32) getHeight(); | |||
| view->checkSizeConstraint (&rect); | |||
| setSize ((int) rect.getWidth(), (int) rect.getHeight()); | |||
| #if JUCE_WINDOWS | |||
| SetWindowPos (pluginHandle, 0, | |||
| pos.x, pos.y, rect.getWidth(), rect.getHeight(), | |||
| isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
| #elif JUCE_MAC | |||
| dummyComponent.setBounds (getLocalBounds()); | |||
| #endif | |||
| view->onSize (&rect); | |||
| } | |||
| else | |||
| { | |||
| warnOnFailure (view->getSize (&rect)); | |||
| } | |||
| #if JUCE_WINDOWS | |||
| SetWindowPos (pluginHandle, 0, | |||
| pos.x, pos.y, rect.getWidth(), rect.getHeight(), | |||
| isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
| #elif JUCE_MAC | |||
| dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| SetWindowPos (pluginHandle, 0, | |||
| pos.x, pos.y, rect.getWidth(), rect.getHeight(), | |||
| isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||
| #elif JUCE_MAC | |||
| dummyComponent.setBounds (0, 0, (int) rect.getWidth(), (int) rect.getHeight()); | |||
| #endif | |||
| } | |||
| // Some plugins don't update their cursor correctly when mousing out the window | |||
| Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | |||
| @@ -1364,7 +1612,7 @@ private: | |||
| if (peer != nullptr) | |||
| pluginHandle = (HandleFormat) peer->getNativeHandle(); | |||
| #elif JUCE_MAC | |||
| dummyComponent.setBounds (getBounds().withZeroOrigin()); | |||
| dummyComponent.setBounds (getLocalBounds()); | |||
| addAndMakeVisible (dummyComponent); | |||
| pluginHandle = (NSView*) dummyComponent.getView(); | |||
| jassert (pluginHandle != nil); | |||
| @@ -1391,7 +1639,8 @@ public: | |||
| midiInputs (new MidiEventList()), | |||
| midiOutputs (new MidiEventList()), | |||
| isComponentInitialised (false), | |||
| isControllerInitialised (false) | |||
| isControllerInitialised (false), | |||
| isActive (false) | |||
| { | |||
| host = new VST3HostContext (this); | |||
| } | |||
| @@ -1413,7 +1662,6 @@ public: | |||
| if (isControllerInitialised) editController->terminate(); | |||
| if (isComponentInitialised) component->terminate(); | |||
| //Deletion order appears to matter: | |||
| componentConnection = nullptr; | |||
| editControllerConnection = nullptr; | |||
| unitData = nullptr; | |||
| @@ -1425,6 +1673,8 @@ public: | |||
| editController2 = nullptr; | |||
| editController = nullptr; | |||
| component = nullptr; | |||
| host = nullptr; | |||
| module = nullptr; | |||
| } | |||
| bool initialise() | |||
| @@ -1478,8 +1728,27 @@ public: | |||
| return module != nullptr ? module->name : String::empty; | |||
| } | |||
| void repopulateArrangements() | |||
| { | |||
| inputArrangements.clearQuick(); | |||
| outputArrangements.clearQuick(); | |||
| // NB: Some plugins need a valid arrangement despite specifying 0 for their I/O busses | |||
| for (int i = 0; i < jmax (1, numInputAudioBusses); ++i) | |||
| inputArrangements.add (getArrangementForBus (processor, true, i)); | |||
| for (int i = 0; i < jmax (1, numOutputAudioBusses); ++i) | |||
| outputArrangements.add (getArrangementForBus (processor, false, i)); | |||
| } | |||
| void prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) override | |||
| { | |||
| // Avoid redundantly calling things like setActive, which can be a heavy-duty call for some plugins: | |||
| if (isActive | |||
| && getSampleRate() == sampleRate | |||
| && getBlockSize() == estimatedSamplesPerBlock) | |||
| return; | |||
| using namespace Vst; | |||
| ProcessSetup setup; | |||
| @@ -1495,20 +1764,25 @@ public: | |||
| editController->setComponentHandler (host); | |||
| Array<SpeakerArrangement> inArrangements, outArrangements; | |||
| if (inputArrangements.size() <= 0 || outputArrangements.size() <= 0) | |||
| repopulateArrangements(); | |||
| for (int i = 0; i < numInputAudioBusses; ++i) | |||
| inArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (true, true, i).channelCount))); | |||
| warnOnFailure (processor->setBusArrangements (inputArrangements.getRawDataPointer(), numInputAudioBusses, | |||
| outputArrangements.getRawDataPointer(), numOutputAudioBusses)); | |||
| for (int i = 0; i < numOutputAudioBusses; ++i) | |||
| outArrangements.add (getArrangementForNumChannels (jmax (0, (int) getBusInfo (false, true, i).channelCount))); | |||
| // Update the num. busses in case the configuration has been modified by the plugin. (May affect number of channels!): | |||
| const int newNumInputAudioBusses = getNumSingleDirectionBussesFor (component, true, true); | |||
| const int newNumOutputAudioBusses = getNumSingleDirectionBussesFor (component, false, true); | |||
| warnOnFailure (processor->setBusArrangements (inArrangements.getRawDataPointer(), numInputAudioBusses, | |||
| outArrangements.getRawDataPointer(), numOutputAudioBusses)); | |||
| // Repopulate arrangements if the number of busses have changed: | |||
| if (numInputAudioBusses != newNumInputAudioBusses | |||
| || numOutputAudioBusses != newNumOutputAudioBusses) | |||
| { | |||
| numInputAudioBusses = newNumInputAudioBusses; | |||
| numOutputAudioBusses = newNumOutputAudioBusses; | |||
| // 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); | |||
| repopulateArrangements(); | |||
| } | |||
| // Needed for having the same sample rate in processBlock(); some plugins need this! | |||
| setPlayConfigDetails (getNumSingleDirectionChannelsFor (component, true, true), | |||
| @@ -1517,21 +1791,30 @@ public: | |||
| setStateForAllBusses (true); | |||
| setLatencySamples (jmax (0, (int) processor->getLatencySamples())); | |||
| warnOnFailure (component->setActive (true)); | |||
| warnOnFailure (processor->setProcessing (true)); | |||
| isActive = true; | |||
| } | |||
| void releaseResources() override | |||
| { | |||
| if (! isActive) | |||
| return; // Avoids redundantly calling things like setActive | |||
| JUCE_TRY | |||
| { | |||
| isActive = false; | |||
| setStateForAllBusses (false); | |||
| if (processor != nullptr) | |||
| processor->setProcessing (false); | |||
| warnOnFailure (processor->setProcessing (false)); | |||
| if (component != nullptr) | |||
| component->setActive (false); | |||
| warnOnFailure (component->setActive (false)); | |||
| } | |||
| JUCE_CATCH_ALL_ASSERT | |||
| } | |||
| @@ -1540,7 +1823,8 @@ public: | |||
| { | |||
| using namespace Vst; | |||
| if (processor != nullptr | |||
| if (isActive | |||
| && processor != nullptr | |||
| && processor->canProcessSampleSize (kSample32) == kResultTrue) | |||
| { | |||
| const int numSamples = buffer.getNumSamples(); | |||
| @@ -1612,15 +1896,22 @@ public: | |||
| //============================================================================== | |||
| bool silenceInProducesSilenceOut() const override | |||
| { | |||
| return processor == nullptr; | |||
| if (processor != nullptr) | |||
| return processor->getTailSamples() == Vst::kNoTail; | |||
| return true; | |||
| } | |||
| /** May return a negative value as a means of informing us that the plugin has "infinite tail," or 0 for "no tail." */ | |||
| double getTailLengthSeconds() const override | |||
| { | |||
| if (processor != nullptr) | |||
| return (double) jmin ((int) jmax ((Steinberg::uint32) 0, processor->getTailSamples()), 0x7fffffff) | |||
| * getSampleRate(); | |||
| { | |||
| const double sampleRate = getSampleRate(); | |||
| if (sampleRate > 0.0) | |||
| return jlimit (0, 0x7fffffff, (int) processor->getTailSamples()) / sampleRate; | |||
| } | |||
| return 0.0; | |||
| } | |||
| @@ -1640,7 +1931,7 @@ public: | |||
| if (getActiveEditor() != nullptr) | |||
| return true; | |||
| ComSmartPtr<IPlugView> view (tryCreatingView()); | |||
| ComSmartPtr<IPlugView> view (tryCreatingView(), false); | |||
| return view != nullptr; | |||
| } | |||
| @@ -1706,6 +1997,16 @@ public: | |||
| return toString (result); | |||
| } | |||
| //============================================================================== | |||
| void reset() override | |||
| { | |||
| if (component != nullptr) | |||
| { | |||
| component->setActive (false); | |||
| component->setActive (true); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void getStateInformation (MemoryBlock& destData) override | |||
| { | |||
| @@ -1758,7 +2059,7 @@ private: | |||
| //============================================================================== | |||
| VST3ModuleHandle::Ptr module; | |||
| friend class VST3HostContext; | |||
| friend VST3HostContext; | |||
| ComSmartPtr<VST3HostContext> host; | |||
| // Information objects: | |||
| @@ -1785,6 +2086,7 @@ private: | |||
| as very poorly specified by the Steinberg SDK | |||
| */ | |||
| int numInputAudioBusses, numOutputAudioBusses; | |||
| Array<Vst::SpeakerArrangement> inputArrangements, outputArrangements; // Caching to improve performance and to avoid possible non-thread-safe calls to getBusArrangements(). | |||
| VST3BufferExchange::BusMap inputBusMap, outputBusMap; | |||
| Array<Vst::AudioBusBuffers> inputBusses, outputBusses; | |||
| @@ -1829,21 +2131,21 @@ private: | |||
| JUCE_DECLARE_VST3_COM_REF_METHODS | |||
| JUCE_DECLARE_VST3_COM_QUERY_METHODS | |||
| Steinberg::int32 PLUGIN_API getParameterCount() { return 0; } | |||
| Steinberg::int32 PLUGIN_API getParameterCount() override { return 0; } | |||
| Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32) | |||
| Vst::IParamValueQueue* PLUGIN_API getParameterData (Steinberg::int32) override | |||
| { | |||
| return nullptr; | |||
| } | |||
| Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID&, Steinberg::int32& index) | |||
| Vst::IParamValueQueue* PLUGIN_API addParameterData (const Vst::ParamID&, Steinberg::int32& index) override | |||
| { | |||
| index = 0; | |||
| return nullptr; | |||
| } | |||
| private: | |||
| Atomic<int32> refCount; | |||
| Atomic<int> refCount; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ParameterChangeList) | |||
| }; | |||
| @@ -1851,7 +2153,7 @@ private: | |||
| ComSmartPtr<ParameterChangeList> inputParameterChanges, outputParameterChanges; | |||
| ComSmartPtr<MidiEventList> midiInputs, midiOutputs; | |||
| Vst::ProcessContext timingInfo; //< Only use this in processBlock()! | |||
| bool isComponentInitialised, isControllerInitialised; | |||
| bool isComponentInitialised, isControllerInitialised, isActive; | |||
| //============================================================================== | |||
| bool fetchComponentAndController (IPluginFactory* factory, const Steinberg::int32 numClasses) | |||
| @@ -2031,11 +2333,11 @@ private: | |||
| Vst::BusInfo getBusInfo (bool forInput, bool forAudio, int index = 0) const | |||
| { | |||
| Vst::BusInfo busInfo; | |||
| busInfo.mediaType = forAudio ? Vst::kAudio : Vst::kEvent; | |||
| busInfo.direction = forInput ? Vst::kInput : Vst::kOutput; | |||
| component->getBusInfo (forAudio ? Vst::kAudio : Vst::kEvent, | |||
| forInput ? Vst::kInput : Vst::kOutput, | |||
| component->getBusInfo (busInfo.mediaType, busInfo.direction, | |||
| (Steinberg::int32) index, busInfo); | |||
| return busInfo; | |||
| } | |||
| @@ -2056,11 +2358,8 @@ private: | |||
| { | |||
| using namespace VST3BufferExchange; | |||
| mapBufferToBusses (inputBusses, *processor, inputBusMap, | |||
| true, numInputAudioBusses, buffer); | |||
| mapBufferToBusses (outputBusses, *processor, outputBusMap, | |||
| false, numOutputAudioBusses, buffer); | |||
| mapBufferToBusses (inputBusses, inputBusMap, inputArrangements, buffer); | |||
| mapBufferToBusses (outputBusses, outputBusMap, outputArrangements, buffer); | |||
| destination.inputs = inputBusses.getRawDataPointer(); | |||
| destination.outputs = outputBusses.getRawDataPointer(); | |||
| @@ -1285,7 +1285,8 @@ public: | |||
| handleUpdateNowIfNeeded(); | |||
| for (int i = ComponentPeer::getNumPeers(); --i >= 0;) | |||
| ComponentPeer::getPeer(i)->performAnyPendingRepaintsNow(); | |||
| if (ComponentPeer* p = ComponentPeer::getPeer(i)) | |||
| p->performAnyPendingRepaintsNow(); | |||
| } | |||
| break; | |||
| @@ -2317,10 +2318,8 @@ private: | |||
| { | |||
| if (isOpen) | |||
| { | |||
| #if ! (JUCE_MAC && JUCE_SUPPORT_CARBON) | |||
| JUCE_VST_LOG ("Closing VST UI: " + plugin.getName()); | |||
| isOpen = false; | |||
| dispatch (effEditClose, 0, 0, 0, 0); | |||
| stopTimer(); | |||
| @@ -2336,7 +2335,6 @@ private: | |||
| pluginWindow = 0; | |||
| pluginProc = 0; | |||
| #endif | |||
| #endif | |||
| } | |||
| } | |||
| @@ -44,11 +44,7 @@ class JUCE_API AudioProcessor | |||
| { | |||
| protected: | |||
| //============================================================================== | |||
| /** Constructor. | |||
| You can also do your initialisation tasks in the initialiseFilterInfo() | |||
| call, which will be made after this object has been created. | |||
| */ | |||
| /** Constructor. */ | |||
| AudioProcessor(); | |||
| public: | |||
| @@ -388,11 +388,11 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| ReferenceCountedArray <Node> nodes; | |||
| OwnedArray <Connection> connections; | |||
| ReferenceCountedArray<Node> nodes; | |||
| OwnedArray<Connection> connections; | |||
| uint32 lastNodeId; | |||
| AudioSampleBuffer renderingBuffers; | |||
| OwnedArray <MidiBuffer> midiBuffers; | |||
| OwnedArray<MidiBuffer> midiBuffers; | |||
| Array<void*> renderingOps; | |||
| friend class AudioGraphIOProcessor; | |||
| @@ -27,12 +27,12 @@ class ProcessorParameterPropertyComp : public PropertyComponent, | |||
| private Timer | |||
| { | |||
| public: | |||
| ProcessorParameterPropertyComp (const String& name, AudioProcessor& p, const int index_) | |||
| ProcessorParameterPropertyComp (const String& name, AudioProcessor& p, int paramIndex) | |||
| : PropertyComponent (name), | |||
| owner (p), | |||
| index (index_), | |||
| index (paramIndex), | |||
| paramHasChanged (false), | |||
| slider (p, index_) | |||
| slider (p, paramIndex) | |||
| { | |||
| startTimer (100); | |||
| addAndMakeVisible (slider); | |||
| @@ -44,15 +44,19 @@ public: | |||
| owner.removeListener (this); | |||
| } | |||
| void refresh() | |||
| void refresh() override | |||
| { | |||
| paramHasChanged = false; | |||
| slider.setValue (owner.getParameter (index), dontSendNotification); | |||
| if (slider.getThumbBeingDragged() < 0) | |||
| slider.setValue (owner.getParameter (index), dontSendNotification); | |||
| slider.updateText(); | |||
| } | |||
| void audioProcessorChanged (AudioProcessor*) {} | |||
| void audioProcessorChanged (AudioProcessor*) override {} | |||
| void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) | |||
| void audioProcessorParameterChanged (AudioProcessor*, int parameterIndex, float) override | |||
| { | |||
| if (parameterIndex == index) | |||
| paramHasChanged = true; | |||
| @@ -76,27 +80,34 @@ private: | |||
| class ParamSlider : public Slider | |||
| { | |||
| public: | |||
| ParamSlider (AudioProcessor& p, const int index_) | |||
| : owner (p), | |||
| index (index_) | |||
| ParamSlider (AudioProcessor& p, int paramIndex) : owner (p), index (paramIndex) | |||
| { | |||
| setRange (0.0, 1.0, 0.0); | |||
| const int steps = owner.getParameterNumSteps (index); | |||
| if (steps > 1 && steps < 0x7fffffff) | |||
| setRange (0.0, 1.0, 1.0 / (steps - 1.0)); | |||
| else | |||
| setRange (0.0, 1.0); | |||
| setSliderStyle (Slider::LinearBar); | |||
| setTextBoxIsEditable (false); | |||
| setScrollWheelEnabled (false); | |||
| setScrollWheelEnabled (true); | |||
| } | |||
| void valueChanged() | |||
| void valueChanged() override | |||
| { | |||
| const float newVal = (float) getValue(); | |||
| if (owner.getParameter (index) != newVal) | |||
| { | |||
| owner.setParameterNotifyingHost (index, newVal); | |||
| updateText(); | |||
| } | |||
| } | |||
| String getTextFromValue (double /*value*/) | |||
| String getTextFromValue (double /*value*/) override | |||
| { | |||
| return owner.getParameterText (index); | |||
| return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||
| } | |||
| private: | |||
| @@ -35,7 +35,7 @@ AbstractFifo::AbstractFifo (const int capacity) noexcept | |||
| AbstractFifo::~AbstractFifo() {} | |||
| int AbstractFifo::getTotalSize() const noexcept { return bufferSize; } | |||
| int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady(); } | |||
| int AbstractFifo::getFreeSpace() const noexcept { return bufferSize - getNumReady() - 1; } | |||
| int AbstractFifo::getNumReady() const noexcept | |||
| { | |||
| @@ -42,7 +42,7 @@ | |||
| - it must be able to be relocated in memory by a memcpy without this causing any problems - so | |||
| objects whose functionality relies on external pointers or references to themselves can not be used. | |||
| You can of course have an array of pointers to any kind of object, e.g. Array <MyClass*>, but if | |||
| You can of course have an array of pointers to any kind of object, e.g. Array<MyClass*>, but if | |||
| you do this, the array doesn't take any ownership of the objects - see the OwnedArray class or the | |||
| ReferenceCountedArray class for more powerful ways of holding lists of objects. | |||
| @@ -31,7 +31,7 @@ DynamicObject::DynamicObject() | |||
| } | |||
| DynamicObject::DynamicObject (const DynamicObject& other) | |||
| : properties (other.properties) | |||
| : ReferenceCountedObject(), properties (other.properties) | |||
| { | |||
| } | |||
| @@ -58,7 +58,7 @@ public: | |||
| virtual bool hasProperty (const Identifier& propertyName) const; | |||
| /** Returns a named property. | |||
| This returns a void if no such property exists. | |||
| This returns var::null if no such property exists. | |||
| */ | |||
| virtual var getProperty (const Identifier& propertyName) const; | |||
| @@ -361,6 +361,14 @@ public: | |||
| */ | |||
| File getLinkedTarget() const; | |||
| /** Returns a unique identifier for the file, if one is available. | |||
| Depending on the OS and file-system, this may be a unix inode number or | |||
| a win32 file identifier, or 0 if it fails to find one. The number will | |||
| be unique on the filesystem, but not globally. | |||
| */ | |||
| uint64 getFileIdentifier() const; | |||
| //============================================================================== | |||
| /** Returns the last modification time of this file. | |||
| @@ -28,40 +28,33 @@ | |||
| int64 juce_fileSetPosition (void* handle, int64 pos); | |||
| //============================================================================== | |||
| FileInputStream::FileInputStream (const File& f) | |||
| : file (f), | |||
| fileHandle (nullptr), | |||
| currentPosition (0), | |||
| status (Result::ok()), | |||
| needToSeek (true) | |||
| status (Result::ok()) | |||
| { | |||
| openHandle(); | |||
| } | |||
| FileInputStream::~FileInputStream() | |||
| { | |||
| closeHandle(); | |||
| } | |||
| //============================================================================== | |||
| int64 FileInputStream::getTotalLength() | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| return file.getSize(); | |||
| } | |||
| int FileInputStream::read (void* buffer, int bytesToRead) | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| if (needToSeek) | |||
| { | |||
| if (juce_fileSetPosition (fileHandle, currentPosition) < 0) | |||
| return 0; | |||
| needToSeek = false; | |||
| } | |||
| // The buffer should never be null, and a negative size is probably a | |||
| // sign that something is broken! | |||
| jassert (buffer != nullptr && bytesToRead >= 0); | |||
| const size_t num = readInternal (buffer, (size_t) bytesToRead); | |||
| currentPosition += num; | |||
| @@ -81,15 +74,11 @@ int64 FileInputStream::getPosition() | |||
| bool FileInputStream::setPosition (int64 pos) | |||
| { | |||
| // You should always check that a stream opened successfully before using it! | |||
| jassert (openedOk()); | |||
| if (pos != currentPosition) | |||
| { | |||
| pos = jlimit ((int64) 0, getTotalLength(), pos); | |||
| needToSeek |= (currentPosition != pos); | |||
| currentPosition = pos; | |||
| } | |||
| currentPosition = juce_fileSetPosition (fileHandle, pos); | |||
| return true; | |||
| return currentPosition == pos; | |||
| } | |||
| @@ -40,10 +40,11 @@ class JUCE_API FileInputStream : public InputStream | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a FileInputStream. | |||
| /** Creates a FileInputStream to read from the given file. | |||
| @param fileToRead the file to read from - if the file can't be accessed for some | |||
| reason, then the stream will just contain no data | |||
| After creating a FileInputStream, you should use openedOk() or failedToOpen() | |||
| to make sure that it's OK before trying to read from it! If it failed, you | |||
| can call getStatus() to get more error information. | |||
| */ | |||
| explicit FileInputStream (const File& fileToRead); | |||
| @@ -73,24 +74,23 @@ public: | |||
| //============================================================================== | |||
| int64 getTotalLength() override; | |||
| int read (void* destBuffer, int maxBytesToRead) override; | |||
| int read (void*, int) override; | |||
| bool isExhausted() override; | |||
| int64 getPosition() override; | |||
| bool setPosition (int64 pos) override; | |||
| bool setPosition (int64) override; | |||
| private: | |||
| //============================================================================== | |||
| File file; | |||
| const File file; | |||
| void* fileHandle; | |||
| int64 currentPosition; | |||
| Result status; | |||
| bool needToSeek; | |||
| void openHandle(); | |||
| void closeHandle(); | |||
| size_t readInternal (void* buffer, size_t numBytes); | |||
| size_t readInternal (void*, size_t); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileInputStream) | |||
| }; | |||
| #endif // JUCE_FILEINPUTSTREAM_H_INCLUDED | |||
| @@ -768,7 +768,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| { | |||
| FunctionObject() noexcept {} | |||
| FunctionObject (const FunctionObject& other) : functionCode (other.functionCode) | |||
| FunctionObject (const FunctionObject& other) : DynamicObject(), functionCode (other.functionCode) | |||
| { | |||
| ExpressionTreeBuilder tb (functionCode); | |||
| tb.parseFunctionParamsAndBody (*this); | |||
| @@ -139,7 +139,7 @@ private: | |||
| static SharedObjectHolder& getSharedObjectHolder() noexcept | |||
| { | |||
| static char holder [sizeof (SharedObjectHolder)] = { 0 }; | |||
| static void* holder [(sizeof (SharedObjectHolder) + sizeof(void*) - 1) / sizeof(void*)] = { 0 }; | |||
| return *reinterpret_cast<SharedObjectHolder*> (holder); | |||
| } | |||
| @@ -399,16 +399,21 @@ public final class JuceAppActivity extends Activity | |||
| private native void handleKeyDown (long host, int keycode, int textchar); | |||
| private native void handleKeyUp (long host, int keycode, int textchar); | |||
| public void showKeyboard (boolean shouldShow) | |||
| public void showKeyboard (String type) | |||
| { | |||
| InputMethodManager imm = (InputMethodManager) getSystemService (Context.INPUT_METHOD_SERVICE); | |||
| if (imm != null) | |||
| { | |||
| if (shouldShow) | |||
| imm.showSoftInput (this, InputMethodManager.SHOW_FORCED); | |||
| if (type.length() > 0) | |||
| { | |||
| imm.showSoftInput (this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT); | |||
| imm.setInputMethod (getWindowToken(), type); | |||
| } | |||
| else | |||
| { | |||
| imm.hideSoftInputFromWindow (getWindowToken(), 0); | |||
| } | |||
| } | |||
| } | |||
| @@ -179,6 +179,16 @@ namespace AndroidStatsHelpers | |||
| JuceAppActivity.getLocaleValue, | |||
| isRegion))); | |||
| } | |||
| #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD) | |||
| DECLARE_JNI_CLASS (BuildClass, "android/os/Build"); | |||
| #undef JNI_CLASS_MEMBERS | |||
| String getAndroidOsBuildValue (const char* fieldName) | |||
| { | |||
| return juceString (LocalRef<jstring> ((jstring) getEnv()->GetStaticObjectField ( | |||
| BuildClass, getEnv()->GetStaticFieldID (BuildClass, fieldName, "Ljava/lang/String;")))); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| @@ -194,7 +204,8 @@ String SystemStats::getOperatingSystemName() | |||
| String SystemStats::getDeviceDescription() | |||
| { | |||
| return String::empty; | |||
| return AndroidStatsHelpers::getAndroidOsBuildValue ("MODEL") | |||
| + "-" + AndroidStatsHelpers::getAndroidOsBuildValue ("SERIAL"); | |||
| } | |||
| bool SystemStats::isOperatingSystem64Bit() | |||
| @@ -262,7 +273,7 @@ String SystemStats::getComputerName() | |||
| String SystemStats::getUserLanguage() { return AndroidStatsHelpers::getLocaleValue (false); } | |||
| String SystemStats::getUserRegion() { return AndroidStatsHelpers::getLocaleValue (true); } | |||
| String SystemStats::getDisplayLanguage() { return getUserLanguage(); } | |||
| String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } | |||
| //============================================================================== | |||
| void CPUInformation::initialise() noexcept | |||
| @@ -46,7 +46,10 @@ void MACAddress::findAllAddresses (Array<MACAddress>& result) | |||
| && (ifr.ifr_flags & IFF_LOOPBACK) == 0 | |||
| && ioctl (s, SIOCGIFHWADDR, &ifr) == 0) | |||
| { | |||
| result.addIfNotAlreadyThere (MACAddress ((const uint8*) ifr.ifr_hwaddr.sa_data)); | |||
| MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data); | |||
| if (! ma.isNull()) | |||
| result.addIfNotAlreadyThere (ma); | |||
| } | |||
| } | |||
| @@ -139,7 +139,7 @@ static String getLocaleValue (nl_item key) | |||
| String SystemStats::getUserLanguage() { return getLocaleValue (_NL_IDENTIFICATION_LANGUAGE); } | |||
| String SystemStats::getUserRegion() { return getLocaleValue (_NL_IDENTIFICATION_TERRITORY); } | |||
| String SystemStats::getDisplayLanguage() { return getUserLanguage(); } | |||
| String SystemStats::getDisplayLanguage() { return getUserLanguage() + "-" + getUserRegion(); } | |||
| //============================================================================== | |||
| void CPUInformation::initialise() noexcept | |||
| @@ -39,12 +39,17 @@ void MACAddress::findAllAddresses (Array<MACAddress>& result) | |||
| { | |||
| const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; | |||
| #ifndef IFT_ETHER | |||
| #define IFT_ETHER 6 | |||
| #endif | |||
| #ifndef IFT_ETHER | |||
| enum { IFT_ETHER = 6 }; | |||
| #endif | |||
| if (sadd->sdl_type == IFT_ETHER) | |||
| result.addIfNotAlreadyThere (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); | |||
| { | |||
| MACAddress ma (MACAddress (((const uint8*) sadd->sdl_data) + sadd->sdl_nlen)); | |||
| if (! ma.isNull()) | |||
| result.addIfNotAlreadyThere (ma); | |||
| } | |||
| } | |||
| } | |||
| @@ -124,7 +124,7 @@ SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() | |||
| return iOS; | |||
| #else | |||
| StringArray parts; | |||
| parts.addTokens (getOSXVersion(), ".", String()); | |||
| parts.addTokens (getOSXVersion(), ".", StringRef()); | |||
| jassert (parts[0].getIntValue() == 10); | |||
| const int major = parts[1].getIntValue(); | |||
| @@ -280,6 +280,12 @@ int64 File::getSize() const | |||
| return juce_stat (fullPath, info) ? info.st_size : 0; | |||
| } | |||
| uint64 File::getFileIdentifier() const | |||
| { | |||
| juce_statStruct info; | |||
| return juce_stat (fullPath, info) ? (uint64) info.st_ino : 0; | |||
| } | |||
| //============================================================================== | |||
| bool File::hasWriteAccess() const | |||
| { | |||
| @@ -391,13 +397,10 @@ void FileInputStream::openHandle() | |||
| status = getResultForErrno(); | |||
| } | |||
| void FileInputStream::closeHandle() | |||
| FileInputStream::~FileInputStream() | |||
| { | |||
| if (fileHandle != 0) | |||
| { | |||
| close (getFD (fileHandle)); | |||
| fileHandle = 0; | |||
| } | |||
| } | |||
| size_t FileInputStream::readInternal (void* const buffer, const size_t numBytes) | |||
| @@ -1017,12 +1020,12 @@ public: | |||
| // we're the child process.. | |||
| close (pipeHandles[0]); // close the read handle | |||
| if ((streamFlags | wantStdOut) != 0) | |||
| if ((streamFlags & wantStdOut) != 0) | |||
| dup2 (pipeHandles[1], 1); // turns the pipe into stdout | |||
| else | |||
| close (STDOUT_FILENO); | |||
| if ((streamFlags | wantStdErr) != 0) | |||
| if ((streamFlags & wantStdErr) != 0) | |||
| dup2 (pipeHandles[1], 2); | |||
| else | |||
| close (STDERR_FILENO); | |||
| @@ -1210,7 +1213,7 @@ private: | |||
| { | |||
| mach_timebase_info_data_t timebase; | |||
| (void) mach_timebase_info (&timebase); | |||
| delta = (((uint64_t) (millis * 1000000.0)) * timebase.numer) / timebase.denom; | |||
| delta = (((uint64_t) (millis * 1000000.0)) * timebase.denom) / timebase.numer; | |||
| time = mach_absolute_time(); | |||
| } | |||
| @@ -29,7 +29,7 @@ | |||
| #ifndef JUCE_WIN32_COMSMARTPTR_H_INCLUDED | |||
| #define JUCE_WIN32_COMSMARTPTR_H_INCLUDED | |||
| #if ! defined (_MSC_VER) //|| defined (__uuidof)) | |||
| #if ! (defined (_MSC_VER) || defined (__uuidof)) | |||
| template<typename Type> struct UUIDGetter { static CLSID get() { jassertfalse; return CLSID(); } }; | |||
| #define __uuidof(x) UUIDGetter<x>::get() | |||
| #endif | |||
| @@ -234,7 +234,7 @@ void FileInputStream::openHandle() | |||
| status = WindowsFileHelpers::getResultForLastError(); | |||
| } | |||
| void FileInputStream::closeHandle() | |||
| FileInputStream::~FileInputStream() | |||
| { | |||
| CloseHandle ((HANDLE) fileHandle); | |||
| } | |||
| @@ -474,6 +474,28 @@ int64 File::getVolumeTotalSize() const | |||
| return WindowsFileHelpers::getDiskSpaceInfo (getFullPathName(), true); | |||
| } | |||
| uint64 File::getFileIdentifier() const | |||
| { | |||
| uint64 result = 0; | |||
| HANDLE h = CreateFile (getFullPathName().toWideCharPointer(), | |||
| GENERIC_READ, FILE_SHARE_READ, nullptr, | |||
| OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); | |||
| if (h != INVALID_HANDLE_VALUE) | |||
| { | |||
| BY_HANDLE_FILE_INFORMATION info; | |||
| zerostruct (info); | |||
| if (GetFileInformationByHandle (h, &info)) | |||
| result = (((uint64) info.nFileIndexHigh) << 32) | info.nFileIndexLow; | |||
| CloseHandle (h); | |||
| } | |||
| return result; | |||
| } | |||
| //============================================================================== | |||
| bool File::isOnCDRomDrive() const | |||
| { | |||
| @@ -287,7 +287,7 @@ private: | |||
| if (bytesToDo > 0 | |||
| && ! InternetWriteFile (request, | |||
| static_cast <const char*> (postData.getData()) + bytesSent, | |||
| static_cast<const char*> (postData.getData()) + bytesSent, | |||
| (DWORD) bytesToDo, &bytesDone)) | |||
| { | |||
| break; | |||
| @@ -342,7 +342,13 @@ struct GetAdaptersInfoHelper | |||
| namespace MACAddressHelpers | |||
| { | |||
| void getViaGetAdaptersInfo (Array<MACAddress>& result) | |||
| static void addAddress (Array<MACAddress>& result, const MACAddress& ma) | |||
| { | |||
| if (! ma.isNull()) | |||
| result.addIfNotAlreadyThere (ma); | |||
| } | |||
| static void getViaGetAdaptersInfo (Array<MACAddress>& result) | |||
| { | |||
| GetAdaptersInfoHelper gah; | |||
| @@ -350,11 +356,11 @@ namespace MACAddressHelpers | |||
| { | |||
| for (PIP_ADAPTER_INFO adapter = gah.adapterInfo; adapter != nullptr; adapter = adapter->Next) | |||
| if (adapter->AddressLength >= 6) | |||
| result.addIfNotAlreadyThere (MACAddress (adapter->Address)); | |||
| addAddress (result, MACAddress (adapter->Address)); | |||
| } | |||
| } | |||
| void getViaNetBios (Array<MACAddress>& result) | |||
| static void getViaNetBios (Array<MACAddress>& result) | |||
| { | |||
| DynamicLibrary dll ("netapi32.dll"); | |||
| JUCE_LOAD_WINAPI_FUNCTION (dll, Netbios, NetbiosCall, UCHAR, (PNCB)) | |||
| @@ -396,7 +402,7 @@ namespace MACAddressHelpers | |||
| ncb.ncb_length = sizeof (ASTAT); | |||
| if (NetbiosCall (&ncb) == 0 && astat.adapt.adapter_type == 0xfe) | |||
| result.addIfNotAlreadyThere (MACAddress (astat.adapt.adapter_address)); | |||
| addAddress (result, MACAddress (astat.adapt.adapter_address)); | |||
| } | |||
| } | |||
| } | |||
| @@ -428,8 +428,16 @@ String SystemStats::getDisplayLanguage() | |||
| DynamicLibrary dll ("kernel32.dll"); | |||
| JUCE_LOAD_WINAPI_FUNCTION (dll, GetUserDefaultUILanguage, getUserDefaultUILanguage, LANGID, (void)) | |||
| if (getUserDefaultUILanguage != nullptr) | |||
| return getLocaleValue (MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT), LOCALE_SISO639LANGNAME, "en"); | |||
| if (getUserDefaultUILanguage == nullptr) | |||
| return "en"; | |||
| return "en"; | |||
| const DWORD langID = MAKELCID (getUserDefaultUILanguage(), SORT_DEFAULT); | |||
| String mainLang (getLocaleValue (langID, LOCALE_SISO639LANGNAME, "en")); | |||
| String region (getLocaleValue (langID, LOCALE_SISO3166CTRYNAME, nullptr)); | |||
| if (region.isNotEmpty()) | |||
| mainLang << '-' << region; | |||
| return mainLang; | |||
| } | |||
| @@ -241,8 +241,6 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPost | |||
| { | |||
| MemoryOutputStream data (headersAndPostData, false); | |||
| data << URLHelpers::getMangledParameters (*this); | |||
| if (filesToUpload.size() > 0) | |||
| { | |||
| // (this doesn't currently support mixing custom post-data with uploads..) | |||
| @@ -285,7 +283,8 @@ void URL::createHeadersAndPostData (String& headers, MemoryBlock& headersAndPost | |||
| } | |||
| else | |||
| { | |||
| data << postData; | |||
| data << URLHelpers::getMangledParameters (*this) | |||
| << postData; | |||
| // if the user-supplied headers didn't contain a content-type, add one now.. | |||
| if (! headers.containsIgnoreCase ("Content-Type")) | |||
| @@ -118,7 +118,9 @@ public: | |||
| static String getUserRegion(); | |||
| /** Returns the user's display language. | |||
| The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2) | |||
| The return value is a 2 or 3 letter language code (ISO 639-1 or ISO 639-2). | |||
| Note that depending on the OS and region, this may also be followed by a dash | |||
| and a sub-region code, e.g "en-GB" | |||
| */ | |||
| static String getDisplayLanguage(); | |||
| @@ -161,7 +161,7 @@ | |||
| #define JUCE_32BIT 1 | |||
| #endif | |||
| #ifdef __arm__ | |||
| #if defined (__arm__) || defined (__arm64__) | |||
| #define JUCE_ARM 1 | |||
| #elif __MMX__ || __SSE__ || __amd64__ | |||
| #define JUCE_INTEL 1 | |||
| @@ -194,11 +194,11 @@ LocalisedStrings* LocalisedStrings::getCurrentMappings() | |||
| String LocalisedStrings::translateWithCurrentMappings (const String& text) { return juce::translate (text); } | |||
| String LocalisedStrings::translateWithCurrentMappings (const char* text) { return juce::translate (text); } | |||
| String translate (const String& text) { return juce::translate (text, text); } | |||
| String translate (const char* text) { return juce::translate (String (text)); } | |||
| String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); } | |||
| JUCE_API String translate (const String& text) { return juce::translate (text, text); } | |||
| JUCE_API String translate (const char* text) { return juce::translate (String (text)); } | |||
| JUCE_API String translate (CharPointer_UTF8 text) { return juce::translate (String (text)); } | |||
| String translate (const String& text, const String& resultIfNotFound) | |||
| JUCE_API String translate (const String& text, const String& resultIfNotFound) | |||
| { | |||
| const SpinLock::ScopedLockType sl (currentMappingsLock); | |||
| @@ -226,22 +226,22 @@ private: | |||
| /** Uses the LocalisedStrings class to translate the given string literal. | |||
| @see LocalisedStrings | |||
| */ | |||
| String translate (const String& stringLiteral); | |||
| JUCE_API String translate (const String& stringLiteral); | |||
| /** Uses the LocalisedStrings class to translate the given string literal. | |||
| @see LocalisedStrings | |||
| */ | |||
| String translate (const char* stringLiteral); | |||
| JUCE_API String translate (const char* stringLiteral); | |||
| /** Uses the LocalisedStrings class to translate the given string literal. | |||
| @see LocalisedStrings | |||
| */ | |||
| String translate (CharPointer_UTF8 stringLiteral); | |||
| JUCE_API String translate (CharPointer_UTF8 stringLiteral); | |||
| /** Uses the LocalisedStrings class to translate the given string literal. | |||
| @see LocalisedStrings | |||
| */ | |||
| String translate (const String& stringLiteral, const String& resultIfNotFound); | |||
| JUCE_API String translate (const String& stringLiteral, const String& resultIfNotFound); | |||
| #endif // JUCE_LOCALISEDSTRINGS_H_INCLUDED | |||
| @@ -53,7 +53,7 @@ File PropertiesFile::Options::getDefaultFile() const | |||
| File dir (commonToAllUsers ? "/Library/" | |||
| : "~/Library/"); | |||
| if (osxLibrarySubFolder != "Preferences" && osxLibrarySubFolder != "Application Support") | |||
| if (osxLibrarySubFolder != "Preferences" && ! osxLibrarySubFolder.startsWith ("Application Support")) | |||
| { | |||
| /* The PropertiesFile class always used to put its settings files in "Library/Preferences", but Apple | |||
| have changed their advice, and now stipulate that settings should go in "Library/Application Support". | |||
| @@ -61,7 +61,8 @@ File PropertiesFile::Options::getDefaultFile() const | |||
| Because older apps would be broken by a silent change in this class's behaviour, you must now | |||
| explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||
| In newer apps, you should always set this to "Application Support". | |||
| In newer apps, you should always set this to "Application Support" | |||
| or "Application Support/YourSubFolderName". | |||
| If your app needs to load settings files that were created by older versions of juce and | |||
| you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||
| @@ -85,7 +85,8 @@ public: | |||
| Because older apps would be broken by a silent change in this class's behaviour, you must now | |||
| explicitly set the osxLibrarySubFolder value to indicate which path you want to use. | |||
| In newer apps, you should always set this to "Application Support". | |||
| In newer apps, you should always set this to "Application Support" or | |||
| "Application Support/YourSubFolderName". | |||
| If your app needs to load settings files that were created by older versions of juce and | |||
| you want to maintain backwards-compatibility, then you can set this to "Preferences". | |||
| @@ -22,76 +22,18 @@ | |||
| ============================================================================== | |||
| */ | |||
| class SharedValueSourceUpdater : public ReferenceCountedObject, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| SharedValueSourceUpdater() : sourcesBeingIterated (nullptr) {} | |||
| ~SharedValueSourceUpdater() { masterReference.clear(); } | |||
| void update (Value::ValueSource* const source) | |||
| { | |||
| sourcesNeedingAnUpdate.add (source); | |||
| if (sourcesBeingIterated == nullptr) | |||
| triggerAsyncUpdate(); | |||
| } | |||
| void valueDeleted (Value::ValueSource* const source) | |||
| { | |||
| sourcesNeedingAnUpdate.removeValue (source); | |||
| if (sourcesBeingIterated != nullptr) | |||
| sourcesBeingIterated->removeValue (source); | |||
| } | |||
| WeakReference<SharedValueSourceUpdater>::Master masterReference; | |||
| private: | |||
| typedef SortedSet<Value::ValueSource*> SourceSet; | |||
| SourceSet sourcesNeedingAnUpdate; | |||
| SourceSet* sourcesBeingIterated; | |||
| void handleAsyncUpdate() override | |||
| { | |||
| const ReferenceCountedObjectPtr<SharedValueSourceUpdater> localRef (this); | |||
| { | |||
| const ScopedValueSetter<SourceSet*> inside (sourcesBeingIterated, nullptr, nullptr); | |||
| int maxLoops = 10; | |||
| while (sourcesNeedingAnUpdate.size() > 0) | |||
| { | |||
| if (--maxLoops == 0) | |||
| { | |||
| triggerAsyncUpdate(); | |||
| break; | |||
| } | |||
| SourceSet sources; | |||
| sources.swapWith (sourcesNeedingAnUpdate); | |||
| sourcesBeingIterated = &sources; | |||
| for (int i = sources.size(); --i >= 0;) | |||
| if (i < sources.size()) | |||
| sources.getUnchecked(i)->sendChangeMessage (true); | |||
| } | |||
| } | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SharedValueSourceUpdater) | |||
| }; | |||
| static WeakReference<SharedValueSourceUpdater> sharedUpdater; | |||
| Value::ValueSource::ValueSource() | |||
| { | |||
| } | |||
| Value::ValueSource::~ValueSource() | |||
| { | |||
| if (asyncUpdater != nullptr) | |||
| static_cast <SharedValueSourceUpdater*> (asyncUpdater.get())->valueDeleted (this); | |||
| cancelPendingUpdate(); | |||
| } | |||
| void Value::ValueSource::handleAsyncUpdate() | |||
| { | |||
| sendChangeMessage (true); | |||
| } | |||
| void Value::ValueSource::sendChangeMessage (const bool synchronous) | |||
| @@ -103,7 +45,8 @@ void Value::ValueSource::sendChangeMessage (const bool synchronous) | |||
| if (synchronous) | |||
| { | |||
| const ReferenceCountedObjectPtr<ValueSource> localRef (this); | |||
| asyncUpdater = nullptr; | |||
| cancelPendingUpdate(); | |||
| for (int i = numListeners; --i >= 0;) | |||
| if (Value* const v = valuesWithListeners[i]) | |||
| @@ -111,22 +54,7 @@ void Value::ValueSource::sendChangeMessage (const bool synchronous) | |||
| } | |||
| else | |||
| { | |||
| SharedValueSourceUpdater* updater = static_cast <SharedValueSourceUpdater*> (asyncUpdater.get()); | |||
| if (updater == nullptr) | |||
| { | |||
| if (sharedUpdater == nullptr) | |||
| { | |||
| asyncUpdater = updater = new SharedValueSourceUpdater(); | |||
| sharedUpdater = updater; | |||
| } | |||
| else | |||
| { | |||
| asyncUpdater = updater = sharedUpdater.get(); | |||
| } | |||
| } | |||
| updater->update (this); | |||
| triggerAsyncUpdate(); | |||
| } | |||
| } | |||
| } | |||
| @@ -195,13 +123,13 @@ Value& Value::operator= (const Value& other) | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| Value::Value (Value&& other) noexcept | |||
| : value (static_cast <ReferenceCountedObjectPtr <ValueSource>&&> (other.value)) | |||
| : value (static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value)) | |||
| { | |||
| } | |||
| Value& Value::operator= (Value&& other) noexcept | |||
| { | |||
| value = static_cast <ReferenceCountedObjectPtr <ValueSource>&&> (other.value); | |||
| value = static_cast<ReferenceCountedObjectPtr<ValueSource>&&> (other.value); | |||
| return *this; | |||
| } | |||
| #endif | |||
| @@ -167,7 +167,8 @@ public: | |||
| of a ValueSource object. If you're feeling adventurous, you can create your own custom | |||
| ValueSource classes to allow Value objects to represent your own custom data items. | |||
| */ | |||
| class JUCE_API ValueSource : public SingleThreadedReferenceCountedObject | |||
| class JUCE_API ValueSource : public ReferenceCountedObject, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| ValueSource(); | |||
| @@ -192,8 +193,10 @@ public: | |||
| protected: | |||
| //============================================================================== | |||
| friend class Value; | |||
| SortedSet <Value*> valuesWithListeners; | |||
| ReferenceCountedObjectPtr<ReferenceCountedObject> asyncUpdater; | |||
| SortedSet<Value*> valuesWithListeners; | |||
| private: | |||
| void handleAsyncUpdate() override; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ValueSource) | |||
| }; | |||
| @@ -210,8 +213,8 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| friend class ValueSource; | |||
| ReferenceCountedObjectPtr <ValueSource> value; | |||
| ListenerList <Listener> listeners; | |||
| ReferenceCountedObjectPtr<ValueSource> value; | |||
| ListenerList<Listener> listeners; | |||
| void callListeners(); | |||
| @@ -68,8 +68,8 @@ public: | |||
| callback happens, this will cancel the handleAsyncUpdate() callback. | |||
| Note that this method simply cancels the next callback - if a callback is already | |||
| in progress on a different thread, this won't block until it finishes, so there's | |||
| no guarantee that the callback isn't still running when you return from | |||
| in progress on a different thread, this won't block until the callback finishes, so | |||
| there's no guarantee that the callback isn't still running when the method returns. | |||
| */ | |||
| void cancelPendingUpdate() noexcept; | |||
| @@ -100,7 +100,7 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (const bool returnIfNoPend | |||
| using namespace WindowsMessageHelpers; | |||
| MSG m; | |||
| if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, 0)) | |||
| if (returnIfNoPendingMessages && ! PeekMessage (&m, (HWND) 0, 0, 0, PM_NOREMOVE)) | |||
| return false; | |||
| if (GetMessage (&m, (HWND) 0, 0, 0) >= 0) | |||
| @@ -438,14 +438,14 @@ void Graphics::drawRect (Rectangle<float> r, const float lineThickness) const | |||
| //============================================================================== | |||
| void Graphics::fillEllipse (const Rectangle<float>& area) const | |||
| { | |||
| fillEllipse (area.getX(), area.getY(), area.getWidth(), area.getHeight()); | |||
| Path p; | |||
| p.addEllipse (area); | |||
| fillPath (p); | |||
| } | |||
| void Graphics::fillEllipse (float x, float y, float width, float height) const | |||
| void Graphics::fillEllipse (float x, float y, float w, float h) const | |||
| { | |||
| Path p; | |||
| p.addEllipse (x, y, width, height); | |||
| fillPath (p); | |||
| fillEllipse (Rectangle<float> (x, y, w, h)); | |||
| } | |||
| void Graphics::drawEllipse (float x, float y, float width, float height, float lineThickness) const | |||
| @@ -52,6 +52,11 @@ public: | |||
| /** Loads a typeface from a previously saved stream. | |||
| The stream must have been created by writeToStream(). | |||
| NOTE! Since this class was written, support was added for loading real font files from | |||
| memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
| is more appropriate than using this class to store it in a proprietary format. | |||
| @see writeToStream | |||
| */ | |||
| explicit CustomTypeface (InputStream& serialisedTypefaceStream); | |||
| @@ -116,7 +121,7 @@ public: | |||
| NOTE! Since this class was written, support was added for loading real font files from | |||
| memory, so for most people, using Typeface::createSystemTypefaceFor() to load a real font | |||
| is more appropriate than using this class to store it in a proprietory format. | |||
| is more appropriate than using this class to store it in a proprietary format. | |||
| */ | |||
| bool writeToStream (OutputStream& outputStream); | |||
| @@ -169,7 +169,7 @@ public: | |||
| */ | |||
| static const String& getDefaultSansSerifFontName(); | |||
| /** Returns a typeface font family that represents the default sans-serif font. | |||
| /** Returns a typeface font family that represents the default serif font. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| serif font" - it's not the actual font family of this font. | |||
| @@ -178,7 +178,7 @@ public: | |||
| */ | |||
| static const String& getDefaultSerifFontName(); | |||
| /** Returns a typeface font family that represents the default sans-serif font. | |||
| /** Returns a typeface font family that represents the default monospaced font. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| monospaced font" - it's not the actual font family of this font. | |||
| @@ -187,10 +187,10 @@ public: | |||
| */ | |||
| static const String& getDefaultMonospacedFontName(); | |||
| /** Returns a typeface font style that represents the default sans-serif font. | |||
| /** Returns a font style name that represents the default style. | |||
| Note that this method just returns a generic placeholder string that means "the default | |||
| font style" - it's not the actual font style of this font. | |||
| font style" - it's not the actual name of the font style of any particular font. | |||
| @see setTypefaceStyle | |||
| */ | |||
| @@ -436,9 +436,7 @@ void Path::addRectangle (const float x, const float y, | |||
| data.elements [numElements++] = closeSubPathMarker; | |||
| } | |||
| void Path::addRoundedRectangle (const float x, const float y, | |||
| const float w, const float h, | |||
| float csx, float csy) | |||
| void Path::addRoundedRectangle (float x, float y, float w, float h, float csx, float csy) | |||
| { | |||
| addRoundedRectangle (x, y, w, h, csx, csy, true, true, true, true); | |||
| } | |||
| @@ -498,9 +496,7 @@ void Path::addRoundedRectangle (const float x, const float y, const float w, con | |||
| closeSubPath(); | |||
| } | |||
| void Path::addRoundedRectangle (const float x, const float y, | |||
| const float w, const float h, | |||
| float cs) | |||
| void Path::addRoundedRectangle (float x, float y, float w, float h, float cs) | |||
| { | |||
| addRoundedRectangle (x, y, w, h, cs, cs); | |||
| } | |||
| @@ -527,15 +523,19 @@ void Path::addQuadrilateral (const float x1, const float y1, | |||
| closeSubPath(); | |||
| } | |||
| void Path::addEllipse (const float x, const float y, | |||
| const float w, const float h) | |||
| void Path::addEllipse (float x, float y, float w, float h) | |||
| { | |||
| const float hw = w * 0.5f; | |||
| addEllipse (Rectangle<float> (x, y, w, h)); | |||
| } | |||
| void Path::addEllipse (Rectangle<float> area) | |||
| { | |||
| const float hw = area.getWidth() * 0.5f; | |||
| const float hw55 = hw * 0.55f; | |||
| const float hh = h * 0.5f; | |||
| const float hh = area.getHeight() * 0.5f; | |||
| const float hh55 = hh * 0.55f; | |||
| const float cx = x + hw; | |||
| const float cy = y + hh; | |||
| const float cx = area.getX() + hw; | |||
| const float cy = area.getY() + hh; | |||
| startNewSubPath (cx, cy - hh); | |||
| cubicTo (cx + hw55, cy - hh, cx + hw, cy - hh55, cx + hw, cy); | |||
| @@ -391,13 +391,17 @@ public: | |||
| float x4, float y4); | |||
| /** Adds an ellipse to the path. | |||
| The shape is added as a new sub-path. (Any currently open paths will be left open). | |||
| @see addArc | |||
| */ | |||
| void addEllipse (float x, float y, float width, float height); | |||
| /** Adds an ellipse to the path. | |||
| The shape is added as a new sub-path. (Any currently open paths will be left open). | |||
| @see addArc | |||
| */ | |||
| void addEllipse (Rectangle<float> area); | |||
| /** Adds an elliptical arc to the current path. | |||
| Note that when specifying the start and end angles, the curve will be drawn either clockwise | |||
| @@ -29,7 +29,7 @@ class CoreGraphicsImage : public ImagePixelData | |||
| { | |||
| public: | |||
| CoreGraphicsImage (const Image::PixelFormat format, const int w, const int h, const bool clearImage) | |||
| : ImagePixelData (format, w, h) | |||
| : ImagePixelData (format, w, h), cachedImageRef (0) | |||
| { | |||
| pixelStride = format == Image::RGB ? 3 : ((format == Image::ARGB) ? 4 : 1); | |||
| lineStride = (pixelStride * jmax (1, width) + 3) & ~3; | |||
| @@ -47,11 +47,13 @@ public: | |||
| ~CoreGraphicsImage() | |||
| { | |||
| freeCachedImageRef(); | |||
| CGContextRelease (context); | |||
| } | |||
| LowLevelGraphicsContext* createLowLevelContext() override | |||
| { | |||
| freeCachedImageRef(); | |||
| sendDataChangeMessage(); | |||
| return new CoreGraphicsContext (context, height, 1.0f); | |||
| } | |||
| @@ -64,7 +66,10 @@ public: | |||
| bitmap.pixelStride = pixelStride; | |||
| if (mode != Image::BitmapData::readOnly) | |||
| { | |||
| freeCachedImageRef(); | |||
| sendDataChangeMessage(); | |||
| } | |||
| } | |||
| ImagePixelData* clone() override | |||
| @@ -77,6 +82,27 @@ public: | |||
| ImageType* createType() const override { return new NativeImageType(); } | |||
| //============================================================================== | |||
| static CGImageRef getCachedImageRef (const Image& juceImage, CGColorSpaceRef colourSpace) | |||
| { | |||
| CoreGraphicsImage* const cgim = dynamic_cast<CoreGraphicsImage*> (juceImage.getPixelData()); | |||
| if (cgim != nullptr && cgim->cachedImageRef != 0) | |||
| { | |||
| CGImageRetain (cgim->cachedImageRef); | |||
| return cgim->cachedImageRef; | |||
| } | |||
| CGImageRef ref = createImage (juceImage, colourSpace, false); | |||
| if (cgim != nullptr) | |||
| { | |||
| CGImageRetain (ref); | |||
| cgim->cachedImageRef = ref; | |||
| } | |||
| return ref; | |||
| } | |||
| static CGImageRef createImage (const Image& juceImage, CGColorSpaceRef colourSpace, const bool mustOutliveSource) | |||
| { | |||
| const Image::BitmapData srcData (juceImage, Image::BitmapData::readOnly); | |||
| @@ -106,10 +132,20 @@ public: | |||
| //============================================================================== | |||
| CGContextRef context; | |||
| CGImageRef cachedImageRef; | |||
| HeapBlock<uint8> imageData; | |||
| int pixelStride, lineStride; | |||
| private: | |||
| void freeCachedImageRef() | |||
| { | |||
| if (cachedImageRef != 0) | |||
| { | |||
| CGImageRelease (cachedImageRef); | |||
| cachedImageRef = 0; | |||
| } | |||
| } | |||
| static CGBitmapInfo getCGImageFlags (const Image::PixelFormat& format) | |||
| { | |||
| #if JUCE_BIG_ENDIAN | |||
| @@ -454,7 +490,7 @@ void CoreGraphicsContext::drawImage (const Image& sourceImage, const AffineTrans | |||
| { | |||
| const int iw = sourceImage.getWidth(); | |||
| const int ih = sourceImage.getHeight(); | |||
| CGImageRef image = CoreGraphicsImage::createImage (sourceImage, rgbColourSpace, false); | |||
| CGImageRef image = CoreGraphicsImage::getCachedImageRef (sourceImage, rgbColourSpace); | |||
| CGContextSaveGState (context); | |||
| CGContextSetAlpha (context, state->fillType.getOpacity()); | |||
| @@ -1280,15 +1280,25 @@ Typeface::Ptr Font::getDefaultTypefaceForFont (const Font& font) | |||
| } | |||
| #if JUCE_CORETEXT_AVAILABLE | |||
| static bool containsNoMemoryTypefaces (const AttributedString& text) | |||
| static bool canAllTypefacesBeUsedInLayout (const AttributedString& text) | |||
| { | |||
| const int numCharacterAttributes = text.getNumAttributes(); | |||
| for (int i = 0; i < numCharacterAttributes; ++i) | |||
| { | |||
| if (const Font* const f = text.getAttribute (i)->getFont()) | |||
| { | |||
| if (OSXTypeface* tf = dynamic_cast<OSXTypeface*> (f->getTypeface())) | |||
| { | |||
| if (tf->isMemoryFont) | |||
| return false; | |||
| } | |||
| else if (dynamic_cast<CustomTypeface*> (f->getTypeface()) != nullptr) | |||
| { | |||
| return false; | |||
| } | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| @@ -1299,7 +1309,7 @@ bool TextLayout::createNativeLayout (const AttributedString& text) | |||
| #if JUCE_CORETEXT_AVAILABLE | |||
| // Seems to be an unfathomable bug in CoreText which prevents the layout working with | |||
| // typefaces that were loaded from memory, so have to fallback if we hit any of those.. | |||
| if (containsNoMemoryTypefaces (text)) | |||
| if (canAllTypefacesBeUsedInLayout (text)) | |||
| { | |||
| CoreTextTypeLayout::createLayout (*this, text); | |||
| return true; | |||
| @@ -91,7 +91,7 @@ ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const C | |||
| while (target != nullptr) | |||
| { | |||
| Array <CommandID> commandIDs; | |||
| Array<CommandID> commandIDs; | |||
| target->getAllCommands (commandIDs); | |||
| if (commandIDs.contains (commandID)) | |||
| @@ -113,7 +113,7 @@ ApplicationCommandTarget* ApplicationCommandTarget::getTargetForCommand (const C | |||
| if (target != nullptr) | |||
| { | |||
| Array <CommandID> commandIDs; | |||
| Array<CommandID> commandIDs; | |||
| target->getAllCommands (commandIDs); | |||
| if (commandIDs.contains (commandID)) | |||
| @@ -134,7 +134,7 @@ public: | |||
| Your target should add all the command IDs that it handles to the array that is | |||
| passed-in. | |||
| */ | |||
| virtual void getAllCommands (Array <CommandID>& commands) = 0; | |||
| virtual void getAllCommands (Array<CommandID>& commands) = 0; | |||
| /** This must provide details about one of the commands that this target can perform. | |||
| @@ -659,7 +659,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||
| bool wasFullscreen = false; | |||
| bool wasMinimised = false; | |||
| ComponentBoundsConstrainer* currentConstainer = nullptr; | |||
| ComponentBoundsConstrainer* currentConstrainer = nullptr; | |||
| Rectangle<int> oldNonFullScreenBounds; | |||
| int oldRenderingEngine = -1; | |||
| @@ -669,7 +669,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||
| wasFullscreen = peer->isFullScreen(); | |||
| wasMinimised = peer->isMinimised(); | |||
| currentConstainer = peer->getConstrainer(); | |||
| currentConstrainer = peer->getConstrainer(); | |||
| oldNonFullScreenBounds = peer->getNonFullScreenBounds(); | |||
| oldRenderingEngine = peer->getCurrentRenderingEngine(); | |||
| @@ -720,7 +720,7 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo) | |||
| peer->setAlwaysOnTop (true); | |||
| #endif | |||
| peer->setConstrainer (currentConstainer); | |||
| peer->setConstrainer (currentConstrainer); | |||
| repaint(); | |||
| internalHierarchyChanged(); | |||
| @@ -234,6 +234,17 @@ void ModalComponentManager::bringModalComponentsToFront (bool topOneShouldGrabFo | |||
| } | |||
| } | |||
| bool ModalComponentManager::cancelAllModalComponents() | |||
| { | |||
| const int numModal = getNumModalComponents(); | |||
| for (int i = numModal; --i >= 0;) | |||
| if (Component* const c = getModalComponent(i)) | |||
| c->exitModalState (0); | |||
| return numModal > 0; | |||
| } | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| class ModalComponentManager::ReturnValueRetriever : public ModalComponentManager::Callback | |||
| { | |||
| @@ -107,6 +107,11 @@ public: | |||
| /** Brings any modal components to the front. */ | |||
| void bringModalComponentsToFront (bool topOneShouldGrabFocus = true); | |||
| /** Calls exitModalState (0) on any components that are currently modal. | |||
| @returns true if any components were modal; false if nothing needed cancelling | |||
| */ | |||
| bool cancelAllModalComponents(); | |||
| #if JUCE_MODAL_LOOPS_PERMITTED | |||
| /** Runs the event loop until the currently topmost modal component is dismissed, and | |||
| returns the exit code for that component. | |||
| @@ -349,6 +349,11 @@ public: | |||
| if (! carryOn) | |||
| break; | |||
| } | |||
| // paths that finish back at their start position often seem to be | |||
| // left without a 'z', so need to be closed explicitly.. | |||
| if (path.getCurrentPosition() == subpathStart) | |||
| path.closeSubPath(); | |||
| } | |||
| private: | |||
| @@ -203,7 +203,7 @@ void FileChooserDialogBox::okButtonPressed() | |||
| { | |||
| AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, | |||
| TRANS("File already exists"), | |||
| TRANS("There's already a file called: FLMN") | |||
| TRANS("There's already a file called: FLNM") | |||
| .replace ("FLNM", content->chooserComponent.getSelectedFile(0).getFullPathName()) | |||
| + "\n\n" | |||
| + TRANS("Are you sure you want to overwrite it?"), | |||
| @@ -68,6 +68,13 @@ void FilenameComponent::resized() | |||
| getLookAndFeel().layoutFilenameComponent (*this, &filenameBox, browseButton); | |||
| } | |||
| KeyboardFocusTraverser* FilenameComponent::createFocusTraverser() | |||
| { | |||
| // This prevents the sub-components from grabbing focus if the | |||
| // FilenameComponent has been set to refuse focus. | |||
| return getWantsKeyboardFocus() ? Component::createFocusTraverser() : nullptr; | |||
| } | |||
| void FilenameComponent::setBrowseButtonText (const String& newBrowseButtonText) | |||
| { | |||
| browseButtonText = newBrowseButtonText; | |||
| @@ -205,6 +205,8 @@ public: | |||
| void fileDragEnter (const StringArray&, int, int) override; | |||
| /** @internal */ | |||
| void fileDragExit (const StringArray&) override; | |||
| /** @internal */ | |||
| KeyboardFocusTraverser* createFocusTraverser() override; | |||
| private: | |||
| //============================================================================== | |||
| @@ -71,6 +71,23 @@ public: | |||
| /** Returns the position of the caret, relative to the component's origin. */ | |||
| virtual Rectangle<int> getCaretRectangle() = 0; | |||
| /** A set of possible on-screen keyboard types, for use in the | |||
| getKeyboardType() method. | |||
| */ | |||
| enum VirtualKeyboardType | |||
| { | |||
| textKeyboard = 0, | |||
| numericKeyboard, | |||
| urlKeyboard, | |||
| emailAddressKeyboard, | |||
| phoneNumberKeyboard | |||
| }; | |||
| /** Returns the target's preference for the type of keyboard that would be most appropriate. | |||
| This may be ignored, depending on the capabilities of the OS. | |||
| */ | |||
| virtual VirtualKeyboardType getKeyboardType() { return textKeyboard; } | |||
| }; | |||
| @@ -452,8 +452,7 @@ void LookAndFeel_V2::drawAlertBox (Graphics& g, AlertWindow& alert, | |||
| colour = alert.getAlertType() == AlertWindow::InfoIcon ? (uint32) 0x605555ff : (uint32) 0x40b69900; | |||
| character = alert.getAlertType() == AlertWindow::InfoIcon ? 'i' : '?'; | |||
| icon.addEllipse ((float) iconRect.getX(), (float) iconRect.getY(), | |||
| (float) iconRect.getWidth(), (float) iconRect.getHeight()); | |||
| icon.addEllipse (iconRect.toFloat()); | |||
| } | |||
| GlyphArrangement ga; | |||
| @@ -1183,13 +1182,11 @@ void LookAndFeel_V2::drawLabel (Graphics& g, Label& label) | |||
| g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); | |||
| g.setFont (font); | |||
| g.drawFittedText (label.getText(), | |||
| label.getHorizontalBorderSize(), | |||
| label.getVerticalBorderSize(), | |||
| label.getWidth() - 2 * label.getHorizontalBorderSize(), | |||
| label.getHeight() - 2 * label.getVerticalBorderSize(), | |||
| label.getJustificationType(), | |||
| jmax (1, (int) (label.getHeight() / font.getHeight())), | |||
| Rectangle<int> textArea (label.getBorderSize().subtractedFrom (label.getLocalBounds())); | |||
| g.drawFittedText (label.getText(), textArea, label.getJustificationType(), | |||
| jmax (1, (int) (textArea.getHeight() / font.getHeight())), | |||
| label.getMinimumHorizontalScale()); | |||
| g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); | |||
| @@ -30,8 +30,8 @@ LookAndFeel_V3::LookAndFeel_V3() | |||
| setColour (TextButton::buttonColourId, textButtonColour); | |||
| setColour (ComboBox::buttonColourId, textButtonColour); | |||
| setColour (TextEditor::outlineColourId, Colours::transparentBlack); | |||
| setColour (TabbedButtonBar::tabOutlineColourId, Colour (0xff999999)); | |||
| setColour (TabbedComponent::outlineColourId, Colour (0xff999999)); | |||
| setColour (TabbedButtonBar::tabOutlineColourId, Colour (0x66000000)); | |||
| setColour (TabbedComponent::outlineColourId, Colour (0x66000000)); | |||
| setColour (Slider::trackColourId, Colour (0xbbffffff)); | |||
| setColour (Slider::thumbColourId, Colour (0xffddddff)); | |||
| setColour (BubbleComponent::backgroundColourId, Colour (0xeeeeeedd)); | |||
| @@ -102,9 +102,9 @@ public: | |||
| //============================================================================== | |||
| #if JUCE_DUMP_MOUSE_EVENTS | |||
| #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " desc << " #" << source.getIndex() \ | |||
| #define JUCE_MOUSE_EVENT_DBG(desc) DBG ("Mouse " << desc << " #" << index \ | |||
| << ": " << screenPosToLocalPos (comp, screenPos).toString() \ | |||
| << " - Comp: " << String::toHexString ((int) &comp)); | |||
| << " - Comp: " << String::toHexString ((pointer_sized_int) &comp)); | |||
| #else | |||
| #define JUCE_MOUSE_EVENT_DBG(desc) | |||
| #endif | |||
| @@ -91,7 +91,7 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); | |||
| METHOD (hasFocus, "hasFocus", "()Z") \ | |||
| METHOD (invalidate, "invalidate", "(IIII)V") \ | |||
| METHOD (containsPoint, "containsPoint", "(II)Z") \ | |||
| METHOD (showKeyboard, "showKeyboard", "(Z)V") \ | |||
| METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | |||
| METHOD (createGLView, "createGLView", "()L" JUCE_ANDROID_ACTIVITY_CLASSPATH "$OpenGLView;") \ | |||
| DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | |||
| @@ -378,14 +378,30 @@ public: | |||
| handleFocusLoss(); | |||
| } | |||
| void textInputRequired (const Point<int>&) override | |||
| static const char* getVirtualKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept | |||
| { | |||
| view.callVoidMethod (ComponentPeerView.showKeyboard, true); | |||
| switch (type) | |||
| { | |||
| case TextInputTarget::textKeyboard: return "text"; | |||
| case TextInputTarget::numericKeyboard: return "number"; | |||
| case TextInputTarget::urlKeyboard: return "textUri"; | |||
| case TextInputTarget::emailAddressKeyboard: return "textEmailAddress"; | |||
| case TextInputTarget::phoneNumberKeyboard: return "phone"; | |||
| default: jassertfalse; break; | |||
| } | |||
| return "text"; | |||
| } | |||
| void textInputRequired (Point<int>, TextInputTarget& target) override | |||
| { | |||
| view.callVoidMethod (ComponentPeerView.showKeyboard, | |||
| javaString (getVirtualKeyboardType (target.getKeyboardType())).get()); | |||
| } | |||
| void dismissPendingTextInput() override | |||
| { | |||
| view.callVoidMethod (ComponentPeerView.showKeyboard, false); | |||
| view.callVoidMethod (ComponentPeerView.showKeyboard, javaString ("").get()); | |||
| } | |||
| //============================================================================== | |||
| @@ -168,7 +168,7 @@ public: | |||
| void viewFocusLoss(); | |||
| bool isFocused() const override; | |||
| void grabFocus() override; | |||
| void textInputRequired (const Point<int>&) override; | |||
| void textInputRequired (Point<int>, TextInputTarget&) override; | |||
| BOOL textViewReplaceCharacters (Range<int>, const String&); | |||
| void updateHiddenTextContent (TextInputTarget*); | |||
| @@ -702,10 +702,7 @@ void UIViewComponentPeer::toFront (bool makeActiveWindow) | |||
| void UIViewComponentPeer::toBehind (ComponentPeer* other) | |||
| { | |||
| UIViewComponentPeer* const otherPeer = dynamic_cast <UIViewComponentPeer*> (other); | |||
| jassert (otherPeer != nullptr); // wrong type of window? | |||
| if (otherPeer != nullptr) | |||
| if (UIViewComponentPeer* const otherPeer = dynamic_cast<UIViewComponentPeer*> (other)) | |||
| { | |||
| if (isSharedWindow) | |||
| { | |||
| @@ -716,6 +713,10 @@ void UIViewComponentPeer::toBehind (ComponentPeer* other) | |||
| // don't know how to do this | |||
| } | |||
| } | |||
| else | |||
| { | |||
| jassertfalse; // wrong type of window? | |||
| } | |||
| } | |||
| void UIViewComponentPeer::setIcon (const Image& /*newIcon*/) | |||
| @@ -828,12 +829,28 @@ void UIViewComponentPeer::grabFocus() | |||
| } | |||
| } | |||
| void UIViewComponentPeer::textInputRequired (const Point<int>&) | |||
| void UIViewComponentPeer::textInputRequired (Point<int>, TextInputTarget&) | |||
| { | |||
| } | |||
| static UIKeyboardType getUIKeyboardType (TextInputTarget::VirtualKeyboardType type) noexcept | |||
| { | |||
| switch (type) | |||
| { | |||
| case TextInputTarget::textKeyboard: return UIKeyboardTypeAlphabet; | |||
| case TextInputTarget::numericKeyboard: return UIKeyboardTypeNumbersAndPunctuation; | |||
| case TextInputTarget::urlKeyboard: return UIKeyboardTypeURL; | |||
| case TextInputTarget::emailAddressKeyboard: return UIKeyboardTypeEmailAddress; | |||
| case TextInputTarget::phoneNumberKeyboard: return UIKeyboardTypePhonePad; | |||
| default: jassertfalse; break; | |||
| } | |||
| return UIKeyboardTypeDefault; | |||
| } | |||
| void UIViewComponentPeer::updateHiddenTextContent (TextInputTarget* target) | |||
| { | |||
| view->hiddenTextView.keyboardType = getUIKeyboardType (target->getKeyboardType()); | |||
| view->hiddenTextView.text = juceStringToNS (target->getTextInRange (Range<int> (0, target->getHighlightedRegion().getStart()))); | |||
| view->hiddenTextView.selectedRange = NSMakeRange (target->getHighlightedRegion().getStart(), 0); | |||
| } | |||
| @@ -189,7 +189,7 @@ private: | |||
| - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex | |||
| { | |||
| owner->buttonClicked (buttonIndex); | |||
| owner->buttonClicked ((int) buttonIndex); | |||
| alertView.hidden = true; | |||
| } | |||
| @@ -1217,7 +1217,7 @@ public: | |||
| } | |||
| } | |||
| void textInputRequired (const Point<int>&) override {} | |||
| void textInputRequired (Point<int>, TextInputTarget&) override {} | |||
| void repaint (const Rectangle<int>& area) override | |||
| { | |||
| @@ -1327,6 +1327,7 @@ public: | |||
| default: | |||
| #if JUCE_USE_XSHM | |||
| if (XSHMHelpers::isShmAvailable()) | |||
| { | |||
| ScopedXLock xlock; | |||
| if (event.xany.type == XShmGetEventBase (display)) | |||
| @@ -1902,7 +1903,8 @@ private: | |||
| for (const Rectangle<int>* i = originalRepaintRegion.begin(), * const e = originalRepaintRegion.end(); i != e; ++i) | |||
| { | |||
| #if JUCE_USE_XSHM | |||
| ++shmPaintsPending; | |||
| if (XSHMHelpers::isShmAvailable()) | |||
| ++shmPaintsPending; | |||
| #endif | |||
| static_cast<XBitmapImage*> (image.getPixelData()) | |||
| @@ -3045,7 +3047,7 @@ void Desktop::Displays::findDisplays (float masterScale) | |||
| screens[j].height) * masterScale; | |||
| d.isMain = (index == 0); | |||
| d.scale = masterScale; | |||
| d.dpi = getDisplayDPI (index); | |||
| d.dpi = getDisplayDPI (0); // (all screens share the same DPI) | |||
| displays.add (d); | |||
| } | |||
| @@ -1136,8 +1136,7 @@ public: | |||
| bool isFocused() const override | |||
| { | |||
| return isSharedWindow ? this == currentlyFocusedPeer | |||
| : [window isKeyWindow]; | |||
| return this == currentlyFocusedPeer; | |||
| } | |||
| void grabFocus() override | |||
| @@ -1151,7 +1150,7 @@ public: | |||
| } | |||
| } | |||
| void textInputRequired (const Point<int>&) override {} | |||
| void textInputRequired (Point<int>, TextInputTarget&) override {} | |||
| //============================================================================== | |||
| void repaint (const Rectangle<int>& area) override | |||
| @@ -877,7 +877,7 @@ public: | |||
| shouldDeactivateTitleBar = oldDeactivate; | |||
| } | |||
| void textInputRequired (const Point<int>&) override | |||
| void textInputRequired (Point<int>, TextInputTarget&) override | |||
| { | |||
| if (! hasCreatedCaret) | |||
| { | |||
| @@ -902,10 +902,15 @@ public: | |||
| void performAnyPendingRepaintsNow() override | |||
| { | |||
| MSG m; | |||
| if (component.isVisible() | |||
| && (PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE) || isUsingUpdateLayeredWindow())) | |||
| handlePaintMessage(); | |||
| if (component.isVisible()) | |||
| { | |||
| WeakReference<Component> localRef (&component); | |||
| MSG m; | |||
| if (isUsingUpdateLayeredWindow() || PeekMessage (&m, hwnd, WM_PAINT, WM_PAINT, PM_REMOVE)) | |||
| if (localRef != nullptr) // (the PeekMessage call can dispatch messages, which may delete this comp) | |||
| handlePaintMessage(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| @@ -74,7 +74,7 @@ ChoicePropertyComponent::ChoicePropertyComponent (const String& name) | |||
| ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl, | |||
| const String& name, | |||
| const StringArray& choiceList, | |||
| const Array <var>& correspondingValues) | |||
| const Array<var>& correspondingValues) | |||
| : PropertyComponent (name), | |||
| choices (choiceList), | |||
| isCustomClass (false) | |||
| @@ -70,7 +70,7 @@ public: | |||
| ChoicePropertyComponent (const Value& valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array <var>& correspondingValues); | |||
| const Array<var>& correspondingValues); | |||
| /** Destructor. */ | |||
| ~ChoicePropertyComponent(); | |||
| @@ -49,7 +49,7 @@ ComboBox::ComboBox (const String& name) | |||
| noChoicesMessage (TRANS("(no choices)")) | |||
| { | |||
| setRepaintsOnMouseActivity (true); | |||
| ComboBox::lookAndFeelChanged(); | |||
| lookAndFeelChanged(); | |||
| currentId.addListener (this); | |||
| } | |||
| @@ -410,12 +410,22 @@ void ComboBox::enablementChanged() | |||
| repaint(); | |||
| } | |||
| void ComboBox::colourChanged() | |||
| { | |||
| lookAndFeelChanged(); | |||
| } | |||
| void ComboBox::parentHierarchyChanged() | |||
| { | |||
| lookAndFeelChanged(); | |||
| } | |||
| void ComboBox::lookAndFeelChanged() | |||
| { | |||
| repaint(); | |||
| { | |||
| ScopedPointer <Label> newLabel (getLookAndFeel().createComboBoxTextBox (*this)); | |||
| ScopedPointer<Label> newLabel (getLookAndFeel().createComboBoxTextBox (*this)); | |||
| jassert (newLabel != nullptr); | |||
| if (label != nullptr) | |||
| @@ -446,11 +456,6 @@ void ComboBox::lookAndFeelChanged() | |||
| resized(); | |||
| } | |||
| void ComboBox::colourChanged() | |||
| { | |||
| lookAndFeelChanged(); | |||
| } | |||
| //============================================================================== | |||
| bool ComboBox::keyPressed (const KeyPress& key) | |||
| { | |||
| @@ -393,6 +393,8 @@ public: | |||
| bool keyPressed (const KeyPress&) override; | |||
| /** @internal */ | |||
| void valueChanged (Value&) override; | |||
| /** @internal */ | |||
| void parentHierarchyChanged() override; | |||
| // These methods' bool parameters have changed: see their new method signatures. | |||
| JUCE_DEPRECATED (void clear (bool)); | |||
| @@ -28,8 +28,7 @@ Label::Label (const String& name, const String& labelText) | |||
| lastTextValue (labelText), | |||
| font (15.0f), | |||
| justification (Justification::centredLeft), | |||
| horizontalBorderSize (5), | |||
| verticalBorderSize (1), | |||
| border (1, 5, 1, 5), | |||
| minimumHorizontalScale (0.7f), | |||
| editSingleClick (false), | |||
| editDoubleClick (false), | |||
| @@ -123,12 +122,11 @@ void Label::setJustificationType (Justification newJustification) | |||
| } | |||
| } | |||
| void Label::setBorderSize (int h, int v) | |||
| void Label::setBorderSize (BorderSize<int> newBorder) | |||
| { | |||
| if (horizontalBorderSize != h || verticalBorderSize != v) | |||
| if (border != newBorder) | |||
| { | |||
| horizontalBorderSize = h; | |||
| verticalBorderSize = v; | |||
| border = newBorder; | |||
| repaint(); | |||
| } | |||
| } | |||
| @@ -191,7 +189,12 @@ void Label::componentVisibilityChanged (Component& component) | |||
| //============================================================================== | |||
| void Label::textWasEdited() {} | |||
| void Label::textWasChanged() {} | |||
| void Label::editorShown (TextEditor*) {} | |||
| void Label::editorShown (TextEditor* textEditor) | |||
| { | |||
| Component::BailOutChecker checker (this); | |||
| listeners.callChecked (checker, &LabelListener::editorShown, this, *textEditor); | |||
| } | |||
| void Label::editorAboutToBeHidden (TextEditor*) | |||
| { | |||
| @@ -325,7 +328,7 @@ void Label::mouseDoubleClick (const MouseEvent& e) | |||
| void Label::resized() | |||
| { | |||
| if (editor != nullptr) | |||
| editor->setBoundsInset (BorderSize<int> (0)); | |||
| editor->setBounds (getLocalBounds()); | |||
| } | |||
| void Label::focusGained (FocusChangeType cause) | |||
| @@ -446,3 +449,5 @@ void Label::textEditorFocusLost (TextEditor& ed) | |||
| { | |||
| textEditorTextChanged (ed); | |||
| } | |||
| void Label::Listener::editorShown (Label*, TextEditor&) {} | |||
| @@ -74,7 +74,7 @@ public: | |||
| You can call Value::referTo() on this object to make the label read and control | |||
| a Value object that you supply. | |||
| */ | |||
| Value& getTextValue() { return textValue; } | |||
| Value& getTextValue() noexcept { return textValue; } | |||
| //============================================================================== | |||
| /** Changes the font to use to draw the text. | |||
| @@ -109,25 +109,21 @@ public: | |||
| //============================================================================== | |||
| /** Sets the style of justification to be used for positioning the text. | |||
| (The default is Justification::centredLeft) | |||
| */ | |||
| void setJustificationType (Justification justification); | |||
| /** Returns the type of justification, as set in setJustificationType(). */ | |||
| Justification getJustificationType() const noexcept { return justification; } | |||
| Justification getJustificationType() const noexcept { return justification; } | |||
| /** Changes the gap that is left between the edge of the component and the text. | |||
| /** Changes the border that is left between the edge of the component and the text. | |||
| By default there's a small gap left at the sides of the component to allow for | |||
| the drawing of the border, but you can change this if necessary. | |||
| */ | |||
| void setBorderSize (int horizontalBorder, int verticalBorder); | |||
| /** Returns the size of the horizontal gap being left around the text. */ | |||
| int getHorizontalBorderSize() const noexcept { return horizontalBorderSize; } | |||
| void setBorderSize (BorderSize<int> newBorderSize); | |||
| /** Returns the size of the vertical gap being left around the text. */ | |||
| int getVerticalBorderSize() const noexcept { return verticalBorderSize; } | |||
| /** Returns the size of the border to be left around the text. */ | |||
| BorderSize<int> getBorderSize() const noexcept { return border; } | |||
| /** Makes this label "stick to" another component. | |||
| @@ -152,16 +148,17 @@ public: | |||
| Returns false if the label is above the other component. This is only relevent if | |||
| attachToComponent() has been called. | |||
| */ | |||
| bool isAttachedOnLeft() const noexcept { return leftOfOwnerComp; } | |||
| bool isAttachedOnLeft() const noexcept { return leftOfOwnerComp; } | |||
| /** Specifies the minimum amount that the font can be squashed horizantally before it starts | |||
| /** Specifies the minimum amount that the font can be squashed horizontally before it starts | |||
| using ellipsis. | |||
| @see Graphics::drawFittedText | |||
| */ | |||
| void setMinimumHorizontalScale (float newScale); | |||
| float getMinimumHorizontalScale() const noexcept { return minimumHorizontalScale; } | |||
| /** Specifies the amount that the font can be squashed horizontally. */ | |||
| float getMinimumHorizontalScale() const noexcept { return minimumHorizontalScale; } | |||
| //============================================================================== | |||
| /** | |||
| @@ -182,6 +179,9 @@ public: | |||
| /** Called when a Label's text has changed. */ | |||
| virtual void labelTextChanged (Label* labelThatHasChanged) = 0; | |||
| /** Called when a Label goes into editing mode and displays a TextEditor. */ | |||
| virtual void editorShown (Label*, TextEditor& textEditorShown); | |||
| }; | |||
| /** Registers a listener that will be called when the label's text changes. */ | |||
| @@ -228,7 +228,6 @@ public: | |||
| bool isEditable() const noexcept { return editSingleClick || editDoubleClick; } | |||
| /** Makes the editor appear as if the label had been clicked by the user. | |||
| @see textWasEdited, setEditable | |||
| */ | |||
| void showEditor(); | |||
| @@ -327,7 +326,7 @@ private: | |||
| ScopedPointer<TextEditor> editor; | |||
| ListenerList<Listener> listeners; | |||
| WeakReference<Component> ownerComponent; | |||
| int horizontalBorderSize, verticalBorderSize; | |||
| BorderSize<int> border; | |||
| float minimumHorizontalScale; | |||
| bool editSingleClick; | |||
| bool editDoubleClick; | |||
| @@ -623,7 +623,7 @@ void TableHeaderComponent::mouseDrag (const MouseEvent& e) | |||
| minWidthOnRight += columns.getUnchecked (i)->minimumWidth; | |||
| const Rectangle<int> currentPos (getColumnPosition (getIndexOfColumnId (columnIdBeingResized, true))); | |||
| w = jmax (ci->minimumWidth, jmin (w, getWidth() - minWidthOnRight - currentPos.getX())); | |||
| w = jmax (ci->minimumWidth, jmin (w, lastDeliberateWidth - minWidthOnRight - currentPos.getX())); | |||
| } | |||
| setColumnWidth (columnIdBeingResized, w); | |||
| @@ -2059,7 +2059,7 @@ void TextEditor::focusGained (FocusChangeType) | |||
| if (ComponentPeer* const peer = getPeer()) | |||
| if (! isReadOnly()) | |||
| peer->textInputRequired (peer->globalToLocal (getScreenPosition())); | |||
| peer->textInputRequired (peer->globalToLocal (getScreenPosition()), *this); | |||
| } | |||
| void TextEditor::focusLost (FocusChangeType) | |||
| @@ -568,6 +568,9 @@ public: | |||
| */ | |||
| void setInputFilter (InputFilter* newFilter, bool takeOwnership); | |||
| /** Returns the current InputFilter, as set by setInputFilter(). */ | |||
| InputFilter* getInputFilter() const noexcept { return inputFilter; } | |||
| /** Sets limits on the characters that can be entered. | |||
| This is just a shortcut that passes an instance of the LengthAndCharacterRestriction | |||
| class to setInputFilter(). | |||
| @@ -276,7 +276,7 @@ public: | |||
| This may cause things like a virtual on-screen keyboard to appear, depending | |||
| on the OS. | |||
| */ | |||
| virtual void textInputRequired (const Point<int>& position) = 0; | |||
| virtual void textInputRequired (Point<int> position, TextInputTarget&) = 0; | |||
| /** If there's some kind of OS input-method in progress, this should dismiss it. */ | |||
| virtual void dismissPendingTextInput(); | |||
| @@ -27,7 +27,6 @@ class TopLevelWindowManager : private Timer, | |||
| private DeletedAtShutdown | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| TopLevelWindowManager() : currentActive (nullptr) | |||
| { | |||
| } | |||
| @@ -48,33 +47,15 @@ public: | |||
| { | |||
| startTimer (jmin (1731, getTimerInterval() * 2)); | |||
| TopLevelWindow* active = nullptr; | |||
| TopLevelWindow* newActive = findCurrentlyActiveWindow(); | |||
| if (Process::isForegroundProcess()) | |||
| { | |||
| active = currentActive; | |||
| Component* const c = Component::getCurrentlyFocusedComponent(); | |||
| TopLevelWindow* tlw = dynamic_cast <TopLevelWindow*> (c); | |||
| if (tlw == nullptr && c != nullptr) | |||
| tlw = c->findParentComponentOfClass<TopLevelWindow>(); | |||
| if (tlw != nullptr) | |||
| active = tlw; | |||
| } | |||
| if (active != currentActive) | |||
| if (newActive != currentActive) | |||
| { | |||
| currentActive = active; | |||
| currentActive = newActive; | |||
| for (int i = windows.size(); --i >= 0;) | |||
| { | |||
| TopLevelWindow* const tlw = windows.getUnchecked (i); | |||
| tlw->setWindowActive (isWindowActive (tlw)); | |||
| i = jmin (i, windows.size() - 1); | |||
| } | |||
| if (TopLevelWindow* tlw = windows[i]) | |||
| tlw->setWindowActive (isWindowActive (tlw)); | |||
| Desktop::getInstance().triggerFocusCallback(); | |||
| } | |||
| @@ -101,7 +82,7 @@ public: | |||
| deleteInstance(); | |||
| } | |||
| Array <TopLevelWindow*> windows; | |||
| Array<TopLevelWindow*> windows; | |||
| private: | |||
| TopLevelWindow* currentActive; | |||
| @@ -119,6 +100,26 @@ private: | |||
| && tlw->isShowing(); | |||
| } | |||
| TopLevelWindow* findCurrentlyActiveWindow() const | |||
| { | |||
| if (Process::isForegroundProcess()) | |||
| { | |||
| Component* const focusedComp = Component::getCurrentlyFocusedComponent(); | |||
| TopLevelWindow* w = dynamic_cast<TopLevelWindow*> (focusedComp); | |||
| if (w == nullptr && focusedComp != nullptr) | |||
| w = focusedComp->findParentComponentOfClass<TopLevelWindow>(); | |||
| if (w == nullptr) | |||
| w = currentActive; | |||
| if (w != nullptr && w->isShowing()) | |||
| return w; | |||
| } | |||
| return nullptr; | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE (TopLevelWindowManager) | |||
| }; | |||
| @@ -187,12 +188,11 @@ bool TopLevelWindow::isUsingNativeTitleBar() const noexcept | |||
| void TopLevelWindow::visibilityChanged() | |||
| { | |||
| if (isShowing() | |||
| && (getPeer()->getStyleFlags() & (ComponentPeer::windowIsTemporary | |||
| | ComponentPeer::windowIgnoresKeyPresses)) == 0) | |||
| { | |||
| toFront (true); | |||
| } | |||
| if (isShowing()) | |||
| if (ComponentPeer* p = getPeer()) | |||
| if ((p->getStyleFlags() & (ComponentPeer::windowIsTemporary | |||
| | ComponentPeer::windowIgnoresKeyPresses)) == 0) | |||
| toFront (true); | |||
| } | |||
| void TopLevelWindow::parentHierarchyChanged() | |||
| @@ -155,7 +155,7 @@ private: | |||
| bool useDropShadow, useNativeTitleBar, isCurrentlyActive; | |||
| ScopedPointer<DropShadower> shadower; | |||
| void setWindowActive (bool isNowActive); | |||
| void setWindowActive (bool); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TopLevelWindow) | |||
| }; | |||
| @@ -1645,7 +1645,7 @@ void CodeEditorComponent::State::restoreState (CodeEditorComponent& editor) cons | |||
| CodeEditorComponent::State::State (const String& s) | |||
| { | |||
| StringArray tokens; | |||
| tokens.addTokens (s, ":", String::empty); | |||
| tokens.addTokens (s, ":", StringRef()); | |||
| lastTopLine = tokens[0].getIntValue(); | |||
| lastCaretPos = tokens[1].getIntValue(); | |||
| @@ -119,7 +119,7 @@ static bool askToOverwriteFile (const File& newFile) | |||
| { | |||
| return AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, | |||
| TRANS("File already exists"), | |||
| TRANS("There's already a file called: FLMN") | |||
| TRANS("There's already a file called: FLNM") | |||
| .replace ("FLNM", newFile.getFullPathName()) | |||
| + "\n\n" | |||
| + TRANS("Are you sure you want to overwrite it?"), | |||
| @@ -288,7 +288,7 @@ namespace LiveConstantEditor | |||
| @endcode | |||
| */ | |||
| #define JUCE_LIVE_CONSTANT(initialValue) \ | |||
| (LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue)) | |||
| (juce::LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue)) | |||
| #else | |||
| #define JUCE_LIVE_CONSTANT(initialValue) \ | |||
| (initialValue) | |||