@@ -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; } | |||
@@ -29,161 +29,77 @@ extern AudioProcessor* JUCE_CALLTYPE createPluginFilter(); | |||
//============================================================================== | |||
/** | |||
A class that can be used to run a simple standalone application containing your filter. | |||
An object that creates and plays a standalone instance of an AudioProcessor. | |||
Just create one of these objects in your JUCEApplicationBase::initialise() method, and | |||
let it do its work. It will create your filter object using the same createPluginFilter() function | |||
that the other plugin wrappers use. | |||
The object will create your processor using the same createPluginFilter() | |||
function that the other plugin wrappers use, and will run it through the | |||
computer's audio/MIDI devices using AudioDeviceManager and AudioProcessorPlayer. | |||
*/ | |||
class StandaloneFilterWindow : public DocumentWindow, | |||
public ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||
class StandalonePluginHolder | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a window with a given title and colour. | |||
/** Creates an instance of the default plugin. | |||
The settings object can be a PropertySet that the class should use to | |||
store its settings - the object that is passed-in will be owned by this | |||
class and deleted automatically when no longer needed. (It can also be null) | |||
*/ | |||
StandaloneFilterWindow (const String& title, | |||
Colour backgroundColour, | |||
PropertySet* settingsToUse) | |||
: DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), | |||
settings (settingsToUse), | |||
optionsButton ("options") | |||
StandalonePluginHolder (PropertySet* settingsToUse) | |||
: settings (settingsToUse) | |||
{ | |||
setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); | |||
Component::addAndMakeVisible (optionsButton); | |||
optionsButton.addListener (this); | |||
optionsButton.setTriggeredOnMouseDown (true); | |||
createFilter(); | |||
if (filter == nullptr) | |||
{ | |||
jassertfalse // Your filter didn't create correctly! In a standalone app that's not too great. | |||
JUCEApplicationBase::quit(); | |||
} | |||
filter->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, | |||
JucePlugin_MaxNumOutputChannels, | |||
44100, 512); | |||
deviceManager = new AudioDeviceManager(); | |||
deviceManager->addAudioCallback (&player); | |||
deviceManager->addMidiInputCallback (String::empty, &player); | |||
player.setProcessor (filter); | |||
ScopedPointer<XmlElement> savedState; | |||
if (settings != nullptr) | |||
savedState = settings->getXmlValue ("audioSetup"); | |||
deviceManager->initialise (filter->getNumInputChannels(), | |||
filter->getNumOutputChannels(), | |||
savedState, | |||
true); | |||
if (settings != nullptr) | |||
{ | |||
MemoryBlock data; | |||
if (data.fromBase64Encoding (settings->getValue ("filterState")) | |||
&& data.getSize() > 0) | |||
{ | |||
filter->setStateInformation (data.getData(), (int) data.getSize()); | |||
} | |||
} | |||
setContentOwned (filter->createEditorIfNeeded(), true); | |||
if (settings != nullptr) | |||
{ | |||
const int x = settings->getIntValue ("windowX", -100); | |||
const int y = settings->getIntValue ("windowY", -100); | |||
if (x != -100 && y != -100) | |||
setBoundsConstrained (juce::Rectangle<int> (x, y, getWidth(), getHeight())); | |||
else | |||
centreWithSize (getWidth(), getHeight()); | |||
} | |||
else | |||
{ | |||
centreWithSize (getWidth(), getHeight()); | |||
} | |||
createPlugin(); | |||
setupAudioDevices(); | |||
reloadPluginState(); | |||
startPlaying(); | |||
} | |||
~StandaloneFilterWindow() | |||
~StandalonePluginHolder() | |||
{ | |||
if (settings != nullptr) | |||
{ | |||
settings->setValue ("windowX", getX()); | |||
settings->setValue ("windowY", getY()); | |||
if (deviceManager != nullptr) | |||
{ | |||
ScopedPointer<XmlElement> xml (deviceManager->createStateXml()); | |||
settings->setValue ("audioSetup", xml); | |||
} | |||
} | |||
deviceManager->removeMidiInputCallback (String::empty, &player); | |||
deviceManager->removeAudioCallback (&player); | |||
deviceManager = nullptr; | |||
if (settings != nullptr && filter != nullptr) | |||
{ | |||
MemoryBlock data; | |||
filter->getStateInformation (data); | |||
settings->setValue ("filterState", data.toBase64Encoding()); | |||
} | |||
deleteFilter(); | |||
deletePlugin(); | |||
shutDownAudioDevices(); | |||
} | |||
//============================================================================== | |||
AudioProcessor* getAudioProcessor() const noexcept { return filter; } | |||
AudioDeviceManager* getDeviceManager() const noexcept { return deviceManager; } | |||
void createFilter() | |||
void createPlugin() | |||
{ | |||
AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Standalone); | |||
filter = createPluginFilter(); | |||
processor = createPluginFilter(); | |||
jassert (processor != nullptr); // Your createPluginFilter() function must return a valid object! | |||
AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Undefined); | |||
processor->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, | |||
JucePlugin_MaxNumOutputChannels, | |||
44100, 512); | |||
} | |||
/** Deletes and re-creates the filter and its UI. */ | |||
void resetFilter() | |||
void deletePlugin() | |||
{ | |||
deleteFilter(); | |||
createFilter(); | |||
if (filter != nullptr) | |||
{ | |||
if (deviceManager != nullptr) | |||
player.setProcessor (filter); | |||
stopPlaying(); | |||
processor = nullptr; | |||
} | |||
setContentOwned (filter->createEditorIfNeeded(), true); | |||
} | |||
static String getFilePatterns (const String& fileSuffix) | |||
{ | |||
if (fileSuffix.isEmpty()) | |||
return String(); | |||
if (settings != nullptr) | |||
settings->removeValue ("filterState"); | |||
return (fileSuffix.startsWithChar ('.') ? "*" : "*.") + fileSuffix; | |||
} | |||
/** Pops up a dialog letting the user save the filter's state to a file. */ | |||
void saveState() | |||
//============================================================================== | |||
/** Pops up a dialog letting the user save the processor's state to a file. */ | |||
void askUserToSaveState (const String& fileSuffix = String()) | |||
{ | |||
FileChooser fc (TRANS("Save current state"), | |||
settings != nullptr ? File (settings->getValue ("lastStateFile")) | |||
: File::nonexistent); | |||
: File::getSpecialLocation (File::userDocumentsDirectory), | |||
getFilePatterns (fileSuffix)); | |||
if (fc.browseForFileToSave (true)) | |||
{ | |||
MemoryBlock data; | |||
filter->getStateInformation (data); | |||
processor->getStateInformation (data); | |||
if (! fc.getResult().replaceWithData (data.getData(), data.getSize())) | |||
{ | |||
@@ -194,12 +110,13 @@ public: | |||
} | |||
} | |||
/** Pops up a dialog letting the user re-load the filter's state from a file. */ | |||
void loadState() | |||
/** Pops up a dialog letting the user re-load the processor's state from a file. */ | |||
void askUserToLoadState (const String& fileSuffix = String()) | |||
{ | |||
FileChooser fc (TRANS("Load a saved state"), | |||
settings != nullptr ? File (settings->getValue ("lastStateFile")) | |||
: File::nonexistent); | |||
: File::getSpecialLocation (File::userDocumentsDirectory), | |||
getFilePatterns (fileSuffix)); | |||
if (fc.browseForFileToOpen()) | |||
{ | |||
@@ -207,7 +124,7 @@ public: | |||
if (fc.getResult().loadFileAsData (data)) | |||
{ | |||
filter->setStateInformation (data.getData(), (int) data.getSize()); | |||
processor->setStateInformation (data.getData(), (int) data.getSize()); | |||
} | |||
else | |||
{ | |||
@@ -218,20 +135,33 @@ public: | |||
} | |||
} | |||
/** Shows the audio properties dialog box modally. */ | |||
virtual void showAudioSettingsDialog() | |||
//============================================================================== | |||
void startPlaying() | |||
{ | |||
player.setProcessor (processor); | |||
} | |||
void stopPlaying() | |||
{ | |||
player.setProcessor (nullptr); | |||
} | |||
//============================================================================== | |||
/** Shows an audio properties dialog box modally. */ | |||
void showAudioSettingsDialog() | |||
{ | |||
DialogWindow::LaunchOptions o; | |||
o.content.setOwned (new AudioDeviceSelectorComponent (*deviceManager, | |||
filter->getNumInputChannels(), | |||
filter->getNumInputChannels(), | |||
filter->getNumOutputChannels(), | |||
filter->getNumOutputChannels(), | |||
true, false, true, false)); | |||
o.content.setOwned (new AudioDeviceSelectorComponent (deviceManager, | |||
processor->getNumInputChannels(), | |||
processor->getNumInputChannels(), | |||
processor->getNumOutputChannels(), | |||
processor->getNumOutputChannels(), | |||
true, false, | |||
true, false)); | |||
o.content->setSize (500, 450); | |||
o.dialogTitle = TRANS("Audio Settings"); | |||
o.dialogBackgroundColour = Colours::lightgrey; | |||
o.dialogBackgroundColour = Colour (0xfff0f0f0); | |||
o.escapeKeyTriggersCloseButton = true; | |||
o.useNativeTitleBar = true; | |||
o.resizable = false; | |||
@@ -239,66 +169,214 @@ public: | |||
o.launchAsync(); | |||
} | |||
//============================================================================== | |||
/** @internal */ | |||
void closeButtonPressed() override | |||
void saveAudioDeviceState() | |||
{ | |||
JUCEApplicationBase::quit(); | |||
if (settings != nullptr) | |||
{ | |||
ScopedPointer<XmlElement> xml (deviceManager.createStateXml()); | |||
settings->setValue ("audioSetup", xml); | |||
} | |||
} | |||
/** @internal */ | |||
void buttonClicked (Button*) override | |||
void reloadAudioDeviceState() | |||
{ | |||
if (filter != nullptr) | |||
ScopedPointer<XmlElement> savedState; | |||
if (settings != nullptr) | |||
savedState = settings->getXmlValue ("audioSetup"); | |||
deviceManager.initialise (processor->getNumInputChannels(), | |||
processor->getNumOutputChannels(), | |||
savedState, | |||
true); | |||
} | |||
//============================================================================== | |||
void savePluginState() | |||
{ | |||
if (settings != nullptr && processor != nullptr) | |||
{ | |||
PopupMenu m; | |||
m.addItem (1, TRANS("Audio Settings...")); | |||
m.addSeparator(); | |||
m.addItem (2, TRANS("Save current state...")); | |||
m.addItem (3, TRANS("Load a saved state...")); | |||
m.addSeparator(); | |||
m.addItem (4, TRANS("Reset to default state")); | |||
switch (m.showAt (&optionsButton)) | |||
{ | |||
case 1: showAudioSettingsDialog(); break; | |||
case 2: saveState(); break; | |||
case 3: loadState(); break; | |||
case 4: resetFilter(); break; | |||
default: break; | |||
} | |||
MemoryBlock data; | |||
processor->getStateInformation (data); | |||
settings->setValue ("filterState", data.toBase64Encoding()); | |||
} | |||
} | |||
/** @internal */ | |||
void resized() override | |||
void reloadPluginState() | |||
{ | |||
DocumentWindow::resized(); | |||
optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); | |||
if (settings != nullptr) | |||
{ | |||
MemoryBlock data; | |||
if (data.fromBase64Encoding (settings->getValue ("filterState")) && data.getSize() > 0) | |||
processor->setStateInformation (data.getData(), (int) data.getSize()); | |||
} | |||
} | |||
private: | |||
//============================================================================== | |||
ScopedPointer<PropertySet> settings; | |||
ScopedPointer<AudioProcessor> filter; | |||
ScopedPointer<AudioDeviceManager> deviceManager; | |||
ScopedPointer<AudioProcessor> processor; | |||
AudioDeviceManager deviceManager; | |||
AudioProcessorPlayer player; | |||
TextButton optionsButton; | |||
void deleteFilter() | |||
private: | |||
void setupAudioDevices() | |||
{ | |||
player.setProcessor (nullptr); | |||
deviceManager.addAudioCallback (&player); | |||
deviceManager.addMidiInputCallback (String::empty, &player); | |||
reloadAudioDeviceState(); | |||
} | |||
void shutDownAudioDevices() | |||
{ | |||
saveAudioDeviceState(); | |||
deviceManager.removeMidiInputCallback (String::empty, &player); | |||
deviceManager.removeAudioCallback (&player); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandalonePluginHolder) | |||
}; | |||
//============================================================================== | |||
/** | |||
A class that can be used to run a simple standalone application containing your filter. | |||
Just create one of these objects in your JUCEApplicationBase::initialise() method, and | |||
let it do its work. It will create your filter object using the same createPluginFilter() function | |||
that the other plugin wrappers use. | |||
*/ | |||
class StandaloneFilterWindow : public DocumentWindow, | |||
public ButtonListener // (can't use Button::Listener due to VC2005 bug) | |||
{ | |||
public: | |||
//============================================================================== | |||
/** Creates a window with a given title and colour. | |||
The settings object can be a PropertySet that the class should use to | |||
store its settings - the object that is passed-in will be owned by this | |||
class and deleted automatically when no longer needed. (It can also be null) | |||
*/ | |||
StandaloneFilterWindow (const String& title, | |||
Colour backgroundColour, | |||
PropertySet* settingsToUse) | |||
: DocumentWindow (title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), | |||
optionsButton ("options") | |||
{ | |||
setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); | |||
if (filter != nullptr && getContentComponent() != nullptr) | |||
Component::addAndMakeVisible (optionsButton); | |||
optionsButton.addListener (this); | |||
optionsButton.setTriggeredOnMouseDown (true); | |||
pluginHolder = new StandalonePluginHolder (settingsToUse); | |||
createEditorComp(); | |||
if (PropertySet* props = pluginHolder->settings) | |||
{ | |||
filter->editorBeingDeleted (dynamic_cast <AudioProcessorEditor*> (getContentComponent())); | |||
const int x = props->getIntValue ("windowX", -100); | |||
const int y = props->getIntValue ("windowY", -100); | |||
if (x != -100 && y != -100) | |||
setBoundsConstrained (juce::Rectangle<int> (x, y, getWidth(), getHeight())); | |||
else | |||
centreWithSize (getWidth(), getHeight()); | |||
} | |||
else | |||
{ | |||
centreWithSize (getWidth(), getHeight()); | |||
} | |||
} | |||
~StandaloneFilterWindow() | |||
{ | |||
if (PropertySet* props = pluginHolder->settings) | |||
{ | |||
props->setValue ("windowX", getX()); | |||
props->setValue ("windowY", getY()); | |||
} | |||
pluginHolder->stopPlaying(); | |||
deleteEditorComp(); | |||
pluginHolder = nullptr; | |||
} | |||
//============================================================================== | |||
AudioProcessor* getAudioProcessor() const noexcept { return pluginHolder->processor; } | |||
AudioDeviceManager& getDeviceManager() const noexcept { return pluginHolder->deviceManager; } | |||
void createEditorComp() | |||
{ | |||
setContentOwned (getAudioProcessor()->createEditorIfNeeded(), true); | |||
} | |||
void deleteEditorComp() | |||
{ | |||
if (AudioProcessorEditor* ed = dynamic_cast<AudioProcessorEditor*> (getContentComponent())) | |||
{ | |||
pluginHolder->processor->editorBeingDeleted (ed); | |||
clearContentComponent(); | |||
} | |||
} | |||
filter = nullptr; | |||
/** Deletes and re-creates the plugin, resetting it to its default state. */ | |||
void resetToDefaultState() | |||
{ | |||
pluginHolder->stopPlaying(); | |||
deleteEditorComp(); | |||
pluginHolder->deletePlugin(); | |||
if (PropertySet* props = pluginHolder->settings) | |||
props->removeValue ("filterState"); | |||
pluginHolder->createPlugin(); | |||
createEditorComp(); | |||
pluginHolder->startPlaying(); | |||
} | |||
//============================================================================== | |||
void closeButtonPressed() override | |||
{ | |||
JUCEApplicationBase::quit(); | |||
} | |||
void buttonClicked (Button*) override | |||
{ | |||
PopupMenu m; | |||
m.addItem (1, TRANS("Audio Settings...")); | |||
m.addSeparator(); | |||
m.addItem (2, TRANS("Save current state...")); | |||
m.addItem (3, TRANS("Load a saved state...")); | |||
m.addSeparator(); | |||
m.addItem (4, TRANS("Reset to default state")); | |||
switch (m.showAt (&optionsButton)) | |||
{ | |||
case 1: pluginHolder->showAudioSettingsDialog(); break; | |||
case 2: pluginHolder->askUserToSaveState(); break; | |||
case 3: pluginHolder->askUserToLoadState(); break; | |||
case 4: resetToDefaultState(); break; | |||
default: break; | |||
} | |||
} | |||
void resized() override | |||
{ | |||
DocumentWindow::resized(); | |||
optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); | |||
} | |||
ScopedPointer<StandalonePluginHolder> pluginHolder; | |||
private: | |||
//============================================================================== | |||
TextButton optionsButton; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow) | |||
}; | |||
#endif // JUCE_STANDALONEFILTERWINDOW_H_INCLUDED |
@@ -1052,7 +1052,8 @@ public: | |||
Timer::callPendingTimersSynchronously(); | |||
for (int i = ComponentPeer::getNumPeers(); --i >= 0;) | |||
ComponentPeer::getPeer (i)->performAnyPendingRepaintsNow(); | |||
if (ComponentPeer* p = ComponentPeer::getPeer(i)) | |||
p->performAnyPendingRepaintsNow(); | |||
recursionCheck = false; | |||
} | |||
@@ -279,9 +279,9 @@ public: | |||
} | |||
//============================================================================== | |||
void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override { beginEdit ((Steinberg::uint32) index); } | |||
void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override { performEdit ((Steinberg::uint32) index, (double) newValue); } | |||
void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override { endEdit ((Steinberg::uint32) index); } | |||
void audioProcessorParameterChangeGestureBegin (AudioProcessor*, int index) override { beginEdit ((Vst::ParamID) index); } | |||
void audioProcessorParameterChanged (AudioProcessor*, int index, float newValue) override { performEdit ((Vst::ParamID) index, (double) newValue); } | |||
void audioProcessorParameterChangeGestureEnd (AudioProcessor*, int index) override { endEdit ((Vst::ParamID) index); } | |||
void audioProcessorChanged (AudioProcessor*) override | |||
{ | |||
@@ -788,60 +788,66 @@ public: | |||
(int) htonl (bank->content.data.size))); | |||
} | |||
void loadVST3PresetFile (const char* data, int size) | |||
bool loadVST3PresetFile (const char* data, int size) | |||
{ | |||
// At offset 4 there's a little-endian version number which seems to typically be 1 | |||
// At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" | |||
const int chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); | |||
const char* const chunkList = data + chunkListOffset; | |||
jassert (memcmp (chunkList, "List", 4) == 0); | |||
const int entryCount = (int) ByteOrder::littleEndianInt (chunkList + 4); | |||
jassert (entryCount > 0); | |||
if (size < 48) | |||
return false; | |||
for (int i = 0; i < entryCount; ++i) | |||
{ | |||
const char* const entry = chunkList + 8 + 20 * i; | |||
// At offset 4 there's a little-endian version number which seems to typically be 1 | |||
// At offset 8 there's 32 bytes the SDK calls "ASCII-encoded class id" | |||
const int chunkListOffset = (int) ByteOrder::littleEndianInt (data + 40); | |||
jassert (memcmp (data + chunkListOffset, "List", 4) == 0); | |||
const int entryCount = (int) ByteOrder::littleEndianInt (data + chunkListOffset + 4); | |||
jassert (entryCount > 0); | |||
if (memcmp (entry, "Comp", 4) == 0) | |||
{ | |||
// "Comp" entries seem to contain the data. | |||
juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (entry + 4); | |||
juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (entry + 12); | |||
for (int i = 0; i < entryCount; ++i) | |||
{ | |||
const int entryOffset = chunkListOffset + 8 + 20 * i; | |||
#if JUCE_32BIT | |||
jassert (chunkOffset <= (juce::uint64) 0xffffffff); | |||
#endif | |||
jassert (chunkSize <= 0x7fffffff); | |||
if (entryOffset + 20 > size) | |||
return false; | |||
loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); | |||
} | |||
} | |||
} | |||
if (memcmp (data + entryOffset, "Comp", 4) == 0) | |||
{ | |||
// "Comp" entries seem to contain the data. | |||
juce::uint64 chunkOffset = ByteOrder::littleEndianInt64 (data + entryOffset + 4); | |||
juce::uint64 chunkSize = ByteOrder::littleEndianInt64 (data + entryOffset + 12); | |||
bool loadVST2CompatibleState (const char* data, int size) | |||
{ | |||
if (size < 4) | |||
return false; | |||
if ('VstW' == htonl (*(juce::int32*) data)) | |||
{ | |||
loadVST2VstWBlock (data, size); | |||
return true; | |||
} | |||
if (memcmp (data, "VST3", 4) == 0) | |||
{ | |||
// In Cubase 5, when loading VST3 .vstpreset files, | |||
// we get the whole content of the files to load. | |||
// In Cubase 7 we get just the contents within and | |||
// we go directly to the loadVST2VstW codepath instead. | |||
loadVST3PresetFile (data, size); | |||
return true; | |||
} | |||
return false; | |||
} | |||
if (chunkOffset + chunkSize > size) | |||
{ | |||
jassertfalse; | |||
return false; | |||
} | |||
loadVST2VstWBlock (data + chunkOffset, (int) chunkSize); | |||
} | |||
} | |||
return true; | |||
} | |||
bool loadVST2CompatibleState (const char* data, int size) | |||
{ | |||
if (size < 4) | |||
return false; | |||
if (htonl (*(juce::int32*) data) == 'VstW') | |||
{ | |||
loadVST2VstWBlock (data, size); | |||
return true; | |||
} | |||
if (memcmp (data, "VST3", 4) == 0) | |||
{ | |||
// In Cubase 5, when loading VST3 .vstpreset files, | |||
// we get the whole content of the files to load. | |||
// In Cubase 7 we get just the contents within and | |||
// we go directly to the loadVST2VstW codepath instead. | |||
return loadVST3PresetFile (data, size); | |||
} | |||
return false; | |||
} | |||
#endif | |||
bool loadStateData (const void* data, int size) | |||
@@ -885,14 +891,12 @@ public: | |||
for (;;) | |||
{ | |||
Steinberg::int32 bytesRead = 0; | |||
const Steinberg::tresult status = state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead); | |||
if (state->read (buffer, (Steinberg::int32) bytesPerBlock, &bytesRead) == kResultTrue && bytesRead > 0) | |||
{ | |||
allData.write (buffer, bytesRead); | |||
continue; | |||
} | |||
if (bytesRead <= 0 || (status != kResultTrue && ! getHostType().isWavelab())) | |||
break; | |||
break; | |||
allData.write (buffer, bytesRead); | |||
} | |||
} | |||
@@ -1099,10 +1103,12 @@ public: | |||
Steinberg::int32 counter = 0; | |||
FOREACH_CAST (IPtr<Vst::Bus>, Vst::AudioBus, bus, list) | |||
{ | |||
if (counter < numBusses) | |||
bus->setArrangement (arrangement[counter]); | |||
counter++; | |||
} | |||
ENDFOR | |||
return kResultTrue; | |||
@@ -1114,6 +1120,8 @@ public: | |||
tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, | |||
Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override | |||
{ | |||
(void) inputs; (void) outputs; | |||
#if JucePlugin_MaxNumInputChannels > 0 | |||
if (setBusArrangementFor (audioInputs, inputs, numIns) != kResultTrue) | |||
return kResultFalse; | |||
@@ -1238,8 +1246,8 @@ public: | |||
const int numMidiEventsComingIn = midiBuffer.getNumEvents(); | |||
#endif | |||
const int numInputChans = data.inputs != nullptr ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = data.outputs != nullptr ? (int) data.outputs[0].numChannels : 0; | |||
const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
int totalChans = 0; | |||
@@ -1255,7 +1263,13 @@ public: | |||
++totalChans; | |||
} | |||
AudioSampleBuffer buffer (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); | |||
AudioSampleBuffer buffer; | |||
if (totalChans != 0) | |||
buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); | |||
else if (getHostType().isWavelab() | |||
&& pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels() > 0) | |||
return kResultFalse; | |||
{ | |||
const ScopedLock sl (pluginInstance->getCallbackLock()); | |||
@@ -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 | |||
{ | |||
@@ -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: | |||
@@ -75,11 +75,10 @@ class AudioDeviceSelectorComponent::MidiInputSelectorComponentListBox : public | |||
private ListBoxModel | |||
{ | |||
public: | |||
MidiInputSelectorComponentListBox (AudioDeviceManager& dm, | |||
const String& noItemsMessage_) | |||
MidiInputSelectorComponentListBox (AudioDeviceManager& dm, const String& noItems) | |||
: ListBox (String::empty, nullptr), | |||
deviceManager (dm), | |||
noItemsMessage (noItemsMessage_) | |||
noItemsMessage (noItems) | |||
{ | |||
items = MidiInput::getDevices(); | |||
@@ -87,15 +86,12 @@ public: | |||
setOutlineThickness (1); | |||
} | |||
int getNumRows() | |||
int getNumRows() override | |||
{ | |||
return items.size(); | |||
} | |||
void paintListBoxItem (int row, | |||
Graphics& g, | |||
int width, int height, | |||
bool rowIsSelected) | |||
void paintListBoxItem (int row, Graphics& g, int width, int height, bool rowIsSelected) override | |||
{ | |||
if (isPositiveAndBelow (row, items.size())) | |||
{ | |||
@@ -118,7 +114,7 @@ public: | |||
} | |||
} | |||
void listBoxItemClicked (int row, const MouseEvent& e) | |||
void listBoxItemClicked (int row, const MouseEvent& e) override | |||
{ | |||
selectRow (row); | |||
@@ -126,12 +122,12 @@ public: | |||
flipEnablement (row); | |||
} | |||
void listBoxItemDoubleClicked (int row, const MouseEvent&) | |||
void listBoxItemDoubleClicked (int row, const MouseEvent&) override | |||
{ | |||
flipEnablement (row); | |||
} | |||
void returnKeyPressed (int row) | |||
void returnKeyPressed (int row) override | |||
{ | |||
flipEnablement (row); | |||
} | |||
@@ -342,7 +342,7 @@ public: | |||
} | |||
private: | |||
Array <MinMaxValue> data; | |||
Array<MinMaxValue> data; | |||
int peakLevel; | |||
void ensureSize (const int thumbSamples) | |||
@@ -414,7 +414,7 @@ public: | |||
} | |||
private: | |||
Array <MinMaxValue> data; | |||
Array<MinMaxValue> data; | |||
double cachedStart, cachedTimePerPixel; | |||
int numChannelsCached, numSamplesCached; | |||
bool cacheNeedsRefilling; | |||
@@ -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(); | |||
} | |||
@@ -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) | |||