@@ -37,3 +37,5 @@ | |||
#if ! JucePlugin_Build_Standalone | |||
#include "modules/juce_audio_plugin_client/utility/juce_PluginUtilities.cpp" | |||
#endif | |||
#include "modules/juce_audio_processors/processors/juce_AudioProcessor_export.cpp" |
@@ -481,6 +481,17 @@ namespace FloatVectorHelpers | |||
#endif | |||
} | |||
//============================================================================== | |||
namespace | |||
{ | |||
#if JUCE_USE_VDSP_FRAMEWORK | |||
// This casts away constness to account for slightly different vDSP function signatures | |||
// in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. | |||
template <typename ValueType> | |||
ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast<ValueType*> (arg); } | |||
#endif | |||
} | |||
//============================================================================== | |||
void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | |||
{ | |||
@@ -568,10 +579,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int | |||
const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | |||
} | |||
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept | |||
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept | |||
{ | |||
#if JUCE_USE_VDSP_FRAMEWORK | |||
vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
#else | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
@@ -579,10 +590,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am | |||
#endif | |||
} | |||
void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||
void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept | |||
{ | |||
#if JUCE_USE_VDSP_FRAMEWORK | |||
vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
#else | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
@@ -990,7 +1001,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||
void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | |||
{ | |||
#if JUCE_USE_SSE_INTRINSICS | |||
const int mxcsr = _mm_getcsr(); | |||
const unsigned int mxcsr = _mm_getcsr(); | |||
_mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | |||
#endif | |||
} | |||
@@ -66,10 +66,10 @@ public: | |||
static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||
/** Adds a fixed value to each source value and stores it in the destination array. */ | |||
static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; | |||
static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; | |||
/** Adds a fixed value to each source value and stores it in the destination array. */ | |||
static void JUCE_CALLTYPE add (double* dest, double* src, double amount, int numValues) noexcept; | |||
static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; | |||
/** Adds the source values to the destination values. */ | |||
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
@@ -59,7 +59,6 @@ public: | |||
countdown = 0; | |||
} | |||
//============================================================================== | |||
/** Set a new target value. */ | |||
void setValue (FloatType newValue) noexcept | |||
{ | |||
@@ -75,7 +74,6 @@ public: | |||
} | |||
} | |||
//============================================================================== | |||
/** Compute the next value. */ | |||
FloatType getNextValue() noexcept | |||
{ | |||
@@ -87,6 +85,18 @@ public: | |||
return currentValue; | |||
} | |||
/** Returns true if the current value is currently being interpolated. */ | |||
bool isSmoothing() const noexcept | |||
{ | |||
return countdown > 0; | |||
} | |||
/** Returns the target value towards which the smoothed value is currently moving. */ | |||
FloatType getTargetValue() const noexcept | |||
{ | |||
return target; | |||
} | |||
private: | |||
//============================================================================== | |||
FloatType currentValue, target, step; | |||
@@ -309,6 +309,31 @@ uint8* MidiMessage::allocateSpace (int bytes) | |||
return preallocatedData.asBytes; | |||
} | |||
String MidiMessage::getDescription() const | |||
{ | |||
if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||
if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||
if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); | |||
if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); | |||
if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); | |||
if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); | |||
if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); | |||
if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); | |||
if (isMetaEvent()) return "Meta event"; | |||
if (isController()) | |||
{ | |||
String name (MidiMessage::getControllerName (getControllerNumber())); | |||
if (name.isEmpty()) | |||
name = String (getControllerNumber()); | |||
return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); | |||
} | |||
return String::toHexString (getRawData(), getRawDataSize()); | |||
} | |||
int MidiMessage::getChannel() const noexcept | |||
{ | |||
const uint8* const data = getRawData(); | |||
@@ -125,6 +125,12 @@ public: | |||
*/ | |||
int getRawDataSize() const noexcept { return size; } | |||
//============================================================================== | |||
/** Returns a human-readable description of the midi message as a string, | |||
for example "Note On C#3 Velocity 120 Channel 1". | |||
*/ | |||
String getDescription() const; | |||
//============================================================================== | |||
/** Returns the timestamp associated with this message. | |||
@@ -324,7 +324,7 @@ void MPEInstrument::noteOff (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOffVelocity) | |||
{ | |||
if (notes.empty() || ! isNoteChannel (midiChannel)) | |||
if (notes.isEmpty() || ! isNoteChannel (midiChannel)) | |||
return; | |||
const ScopedLock sl (lock); | |||
@@ -375,7 +375,7 @@ void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, M | |||
{ | |||
dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | |||
if (notes.empty()) | |||
if (notes.isEmpty()) | |||
return; | |||
if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | |||
@@ -103,7 +103,7 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
int midiEventPos; | |||
MidiMessage m; | |||
const ScopedLock sl (renderAudioLock); | |||
const ScopedLock sl (noteStateLock); | |||
while (numSamples > 0) | |||
{ | |||
@@ -147,7 +147,7 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
{ | |||
if (sampleRate != newRate) | |||
{ | |||
const ScopedLock sl (renderAudioLock); | |||
const ScopedLock sl (noteStateLock); | |||
instrument->releaseAllNotes(); | |||
sampleRate = newRate; | |||
} | |||
@@ -179,11 +179,10 @@ protected: | |||
//============================================================================== | |||
/** @internal */ | |||
ScopedPointer<MPEInstrument> instrument; | |||
/** @internal */ | |||
CriticalSection renderAudioLock; | |||
private: | |||
//============================================================================== | |||
CriticalSection noteStateLock; | |||
double sampleRate; | |||
int minimumSubBlockSize; | |||
@@ -34,6 +34,7 @@ MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||
MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | |||
{ | |||
zones = other.zones; | |||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||
return *this; | |||
} | |||
@@ -100,8 +100,9 @@ public: | |||
/** Returns the current number of MPE zones. */ | |||
int getNumZones() const noexcept; | |||
/** Returns a pointer to the MPE zone at the given index, | |||
or nullptr if there is no such zone. | |||
/** Returns a pointer to the MPE zone at the given index, or nullptr if there | |||
is no such zone. Zones are sorted by insertion order (most recently added | |||
zone last). | |||
*/ | |||
MPEZone* getZoneByIndex (int index) const noexcept; | |||
@@ -30,7 +30,7 @@ | |||
/** | |||
A type of AudioSource that takes an input source and changes its sample rate. | |||
@see AudioSource | |||
@see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||
*/ | |||
class JUCE_API ResamplingAudioSource : public AudioSource | |||
{ | |||
@@ -171,6 +171,10 @@ namespace CoreMidiHelpers | |||
static StringArray findDevices (const bool forInput) | |||
{ | |||
// It seems that OSX can be a bit picky about the thread that's first used to | |||
// search for devices. It's safest to use the message thread for calling this. | |||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
const ItemCount num = forInput ? MIDIGetNumberOfSources() | |||
: MIDIGetNumberOfDestinations(); | |||
StringArray s; | |||
@@ -29,7 +29,7 @@ namespace FlacNamespace | |||
#if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | |||
#undef VERSION | |||
#define VERSION "1.2.1" | |||
#define VERSION "1.3.1" | |||
#define FLAC__NO_DLL 1 | |||
@@ -646,6 +646,24 @@ namespace WavFileHelpers | |||
return true; | |||
} | |||
static String getStringFromWindows1252Codepage (const uint8* data, size_t num) | |||
{ | |||
HeapBlock<juce_wchar> unicode (num + 1); | |||
for (size_t i = 0; i < num; ++i) | |||
unicode[i] = CharacterFunctions::getUnicodeCharFromWindows1252Codepage (data[i]); | |||
unicode[num] = 0; | |||
return CharPointer_UTF32 (unicode); | |||
} | |||
static String getStringFromData (const MemoryBlock& mb) | |||
{ | |||
return CharPointer_UTF8::isValidString ((const char*) mb.getData(), (int) mb.getSize()) | |||
? mb.toString() | |||
: getStringFromWindows1252Codepage ((const uint8*) mb.getData(), mb.getSize()); | |||
} | |||
static void addToMetadata (StringPairArray& values, InputStream& input, int64 chunkEnd) | |||
{ | |||
while (input.getPosition() < chunkEnd) | |||
@@ -664,7 +682,7 @@ namespace WavFileHelpers | |||
{ | |||
MemoryBlock mb; | |||
input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | |||
values.set (types[i], mb.toString()); | |||
values.set (types[i], getStringFromData (mb)); | |||
break; | |||
} | |||
} | |||
@@ -166,6 +166,9 @@ public: | |||
checkCoInitialiseCalled(); | |||
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
startSampleInFile, numSamples, lengthInSamples); | |||
const int stride = numChannels * sizeof (int16); | |||
while (numSamples > 0) | |||
@@ -297,7 +300,7 @@ private: | |||
sampleRate = inputFormat->nSamplesPerSec; | |||
numChannels = inputFormat->nChannels; | |||
bitsPerSample = inputFormat->wBitsPerSample; | |||
bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; | |||
lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | |||
} | |||
} | |||
@@ -79,9 +79,17 @@ public: | |||
jassert (processor != nullptr); // Your createPluginFilter() function must return a valid object! | |||
AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::wrapperType_Undefined); | |||
processor->setPlayConfigDetails (JucePlugin_MaxNumInputChannels, | |||
JucePlugin_MaxNumOutputChannels, | |||
44100, 512); | |||
// try to disable sidechain and aux buses | |||
const int numInBuses = processor->busArrangement.inputBuses.size(); | |||
const int numOutBuses = processor->busArrangement.inputBuses.size(); | |||
for (int busIdx = 1; busIdx < numInBuses; ++busIdx) | |||
processor->setPreferredBusArrangement (true, busIdx, AudioChannelSet::disabled()); | |||
for (int busIdx = 1; busIdx < numOutBuses; ++busIdx) | |||
processor->setPreferredBusArrangement (false, busIdx, AudioChannelSet::disabled()); | |||
processor->setRateAndBufferSizeDetails(44100, 512); | |||
} | |||
virtual void deletePlugin() | |||
@@ -258,7 +258,7 @@ public: | |||
info.stepCount = 1; | |||
info.defaultNormalizedValue = 0.0f; | |||
info.unitId = Vst::kRootUnitId; | |||
info.flags = Vst::ParameterInfo::kIsBypass; | |||
info.flags = Vst::ParameterInfo::kIsBypass | Vst::ParameterInfo::kCanAutomate; | |||
} | |||
virtual ~BypassParam() {} | |||
@@ -45,6 +45,7 @@ public: | |||
Ardour, | |||
CakewalkSonar8, | |||
CakewalkSonarGeneric, | |||
DaVinciResolve, | |||
DigidesignProTools, | |||
DigitalPerformer, | |||
FinalCut, | |||
@@ -84,18 +85,21 @@ public: | |||
bool isAbletonLive() const noexcept { return type == AbletonLive6 || type == AbletonLive7 || type == AbletonLive8 || type == AbletonLiveGeneric; } | |||
bool isAdobeAudition() const noexcept { return type == AdobeAudition; } | |||
bool isArdour() const noexcept { return type == Ardour; } | |||
bool isDigitalPerformer() const noexcept { return type == DigitalPerformer; } | |||
bool isCubase() const noexcept { return type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase5Bridged || type == SteinbergCubase6 || type == SteinbergCubase7 || type == SteinbergCubase8 || type == SteinbergCubaseGeneric; } | |||
bool isCubase7orLater() const noexcept { return isCubase() && ! (type == SteinbergCubase4 || type == SteinbergCubase5 || type == SteinbergCubase6); } | |||
bool isCubaseBridged() const noexcept { return type == SteinbergCubase5Bridged; } | |||
bool isLogic() const noexcept { return type == AppleLogic; } | |||
bool isDaVinciResolve() const noexcept { return type == DaVinciResolve; } | |||
bool isDigitalPerformer() const noexcept { return type == DigitalPerformer; } | |||
bool isFinalCut() const noexcept { return type == FinalCut; } | |||
bool isFruityLoops() const noexcept { return type == FruityLoops; } | |||
bool isLogic() const noexcept { return type == AppleLogic; } | |||
bool isNuendo() const noexcept { return type == SteinbergNuendo3 || type == SteinbergNuendo4 || type == SteinbergNuendo5 || type == SteinbergNuendoGeneric; } | |||
bool isPremiere() const noexcept { return type == AdobePremierePro; } | |||
bool isProTools() const noexcept { return type == DigidesignProTools; } | |||
bool isPyramix() const noexcept { return type == MergingPyramix; } | |||
bool isReceptor() const noexcept { return type == MuseReceptorGeneric; } | |||
bool isReaper() const noexcept { return type == Reaper; } | |||
bool isRenoise() const noexcept { return type == Renoise; } | |||
bool isSamplitude() const noexcept { return type == MagixSamplitude; } | |||
bool isSonar() const noexcept { return type == CakewalkSonar8 || type == CakewalkSonarGeneric; } | |||
bool isSteinbergTestHost() const noexcept { return type == SteinbergTestHost; } | |||
@@ -106,8 +110,6 @@ public: | |||
bool isWaveBurner() const noexcept { return type == WaveBurner; } | |||
bool isWavelab() const noexcept { return isWavelabLegacy() || type == SteinbergWavelab7 || type == SteinbergWavelab8 || type == SteinbergWavelabGeneric; } | |||
bool isWavelabLegacy() const noexcept { return type == SteinbergWavelab5 || type == SteinbergWavelab6; } | |||
bool isRenoise() const noexcept { return type == Renoise; } | |||
bool isProTools() const noexcept { return type == DigidesignProTools; } | |||
//============================================================================== | |||
const char* getHostDescription() const noexcept | |||
@@ -123,6 +125,7 @@ public: | |||
case AppleLogic: return "Apple Logic"; | |||
case CakewalkSonar8: return "Cakewalk Sonar 8"; | |||
case CakewalkSonarGeneric: return "Cakewalk Sonar"; | |||
case DaVinciResolve: return "DaVinci Resolve"; | |||
case DigidesignProTools: return "ProTools"; | |||
case DigitalPerformer: return "DigitalPerformer"; | |||
case FinalCut: return "Final Cut"; | |||
@@ -203,6 +206,7 @@ private: | |||
if (hostPath.containsIgnoreCase ("Tracktion 3")) return Tracktion3; | |||
if (hostFilename.containsIgnoreCase ("Tracktion")) return TracktionGeneric; | |||
if (hostFilename.containsIgnoreCase ("Renoise")) return Renoise; | |||
if (hostFilename.containsIgnoreCase ("Resolve")) return DaVinciResolve; | |||
#elif JUCE_WINDOWS | |||
if (hostFilename.containsIgnoreCase ("Live 6.")) return AbletonLive6; | |||
@@ -241,6 +245,7 @@ private: | |||
if (hostPath.containsIgnoreCase ("Merging Technologies")) return MergingPyramix; | |||
if (hostFilename.startsWithIgnoreCase ("Sam")) return MagixSamplitude; | |||
if (hostFilename.containsIgnoreCase ("Renoise")) return Renoise; | |||
if (hostFilename.containsIgnoreCase ("Resolve")) return DaVinciResolve; | |||
#elif JUCE_LINUX | |||
if (hostFilename.containsIgnoreCase ("Ardour")) return Ardour; | |||
@@ -30,7 +30,7 @@ | |||
/** | |||
The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | |||
@see AudioFormatManager | |||
@see AudioPluginFormatManager | |||
*/ | |||
class JUCE_API AudioPluginFormat | |||
{ | |||
@@ -135,6 +135,20 @@ AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const | |||
return static_cast<ChannelType> (bit); | |||
} | |||
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||
{ | |||
int idx = 0; | |||
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||
{ | |||
if (static_cast<ChannelType> (bit) == type) | |||
return idx; | |||
idx++; | |||
} | |||
return -1; | |||
} | |||
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||
{ | |||
Array<ChannelType> result; | |||
@@ -166,6 +166,10 @@ public: | |||
/** Returns the type of one of the channels in the set, by index. */ | |||
ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||
/** Returns the index for a particular channel-type. | |||
Will return -1 if the this set does not contain a channel of this type. */ | |||
int getChannelIndexForType (ChannelType type) const noexcept; | |||
/** Returns a string containing a whitespace-separated list of speaker types | |||
corresponding to each channel. For example in a 5.1 arrangement, | |||
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||
@@ -22,48 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
static ThreadLocalValue<AudioProcessor::WrapperType> wrapperTypeBeingCreated; | |||
void JUCE_CALLTYPE AudioProcessor::setTypeOfNextNewPlugin (AudioProcessor::WrapperType type) | |||
{ | |||
wrapperTypeBeingCreated = type; | |||
} | |||
AudioProcessor::AudioProcessor() | |||
: wrapperType (wrapperTypeBeingCreated.get()), | |||
playHead (nullptr), | |||
currentSampleRate (0), | |||
blockSize (0), | |||
latencySamples (0), | |||
#if JUCE_DEBUG | |||
textRecursionCheck (false), | |||
#endif | |||
suspended (false), | |||
nonRealtime (false), | |||
processingPrecision (singlePrecision) | |||
{ | |||
#ifdef JucePlugin_PreferredChannelConfigurations | |||
const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||
#else | |||
const short channelConfigs[][2] = { {2, 2} }; | |||
#endif | |||
#if ! JucePlugin_IsMidiEffect | |||
#if ! JucePlugin_IsSynth | |||
busArrangement.inputBuses.add (AudioProcessorBus ("Input", AudioChannelSet::canonicalChannelSet (channelConfigs[0][0]))); | |||
#endif | |||
busArrangement.outputBuses.add (AudioProcessorBus ("Output", AudioChannelSet::canonicalChannelSet (channelConfigs[0][1]))); | |||
#ifdef JucePlugin_PreferredChannelConfigurations | |||
#if ! JucePlugin_IsSynth | |||
AudioProcessor::setPreferredBusArrangement (true, 0, AudioChannelSet::stereo()); | |||
#endif | |||
AudioProcessor::setPreferredBusArrangement (false, 0, AudioChannelSet::stereo()); | |||
#endif | |||
#endif | |||
updateSpeakerFormatStrings(); | |||
} | |||
AudioProcessor::~AudioProcessor() | |||
{ | |||
#if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
@@ -425,90 +383,6 @@ bool AudioProcessor::isInputChannelStereoPair (int index) const { return isS | |||
bool AudioProcessor::isOutputChannelStereoPair (int index) const { return isStereoPair (busArrangement.outputBuses, index); } | |||
//============================================================================== | |||
bool AudioProcessor::setPreferredBusArrangement (bool isInput, int busIndex, const AudioChannelSet& preferredSet) | |||
{ | |||
const int oldNumInputs = getTotalNumInputChannels(); | |||
const int oldNumOutputs = getTotalNumOutputChannels(); | |||
Array<AudioProcessorBus>& buses = isInput ? busArrangement.inputBuses : busArrangement.outputBuses; | |||
const int numBuses = buses.size(); | |||
if (! isPositiveAndBelow (busIndex, numBuses)) | |||
return false; | |||
AudioProcessorBus& bus = buses.getReference (busIndex); | |||
#ifdef JucePlugin_PreferredChannelConfigurations | |||
// the user is using the deprecated way to specify channel configurations | |||
if (numBuses > 0 && busIndex == 0) | |||
{ | |||
const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||
const int numChannelConfigs = sizeof (channelConfigs) / sizeof (*channelConfigs); | |||
// we need the main bus in the opposite direction | |||
Array<AudioProcessorBus>& oppositeBuses = isInput ? busArrangement.outputBuses : busArrangement.inputBuses; | |||
AudioProcessorBus* oppositeBus = (busIndex < oppositeBuses.size()) ? &oppositeBuses.getReference (0) : nullptr; | |||
// get the target number of channels | |||
const int mainBusNumChannels = preferredSet.size(); | |||
const int mainBusOppositeChannels = (oppositeBus != nullptr) ? oppositeBus->channels.size() : 0; | |||
const int dir = isInput ? 0 : 1; | |||
// find a compatible channel configuration on the opposite bus which is the closest match | |||
// to the current number of channels on that bus | |||
int distance = std::numeric_limits<int>::max(); | |||
int bestConfiguration = -1; | |||
for (int i = 0; i < numChannelConfigs; ++i) | |||
{ | |||
// is the configuration compatible with the preferred set | |||
if (channelConfigs[i][dir] == mainBusNumChannels) | |||
{ | |||
const int configChannels = channelConfigs[i][dir^1]; | |||
const int channelDifference = std::abs (configChannels - mainBusOppositeChannels); | |||
if (channelDifference < distance) | |||
{ | |||
distance = channelDifference; | |||
bestConfiguration = configChannels; | |||
// we can exit if we found a perfect match | |||
if (distance == 0) | |||
break; | |||
} | |||
} | |||
} | |||
// unable to find a good configuration | |||
if (bestConfiguration == -1) | |||
return false; | |||
// did the number of channels change on the opposite bus? | |||
if (mainBusOppositeChannels != bestConfiguration && oppositeBus != nullptr) | |||
{ | |||
// if the channels on the opposite bus are the same as the preferred set | |||
// then also copy over the layout information. If not, then assume | |||
// a cononical channel layout | |||
if (bestConfiguration == mainBusNumChannels) | |||
oppositeBus->channels = preferredSet; | |||
else | |||
oppositeBus->channels = AudioChannelSet::canonicalChannelSet (bestConfiguration); | |||
} | |||
} | |||
#endif | |||
bus.channels = preferredSet; | |||
if (oldNumInputs != getTotalNumInputChannels() || oldNumOutputs != getTotalNumOutputChannels()) | |||
{ | |||
updateSpeakerFormatStrings(); | |||
numChannelsChanged(); | |||
} | |||
return true; | |||
} | |||
void AudioProcessor::disableNonMainBuses (bool isInput) | |||
{ | |||
const Array<AudioProcessorBus>& buses = (isInput ? busArrangement.inputBuses : busArrangement.outputBuses); | |||
@@ -1126,6 +1126,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
return false; | |||
} | |||
bool AudioProcessorGraph::removeNode (Node* node) | |||
{ | |||
if (node != nullptr) | |||
return removeNode (node->nodeId); | |||
jassertfalse; | |||
return false; | |||
} | |||
//============================================================================== | |||
const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | |||
const int sourceChannelIndex, | |||
@@ -183,6 +183,12 @@ public: | |||
*/ | |||
bool removeNode (uint32 nodeId); | |||
/** Deletes a node within the graph which has the specified ID. | |||
This will also delete any connections that are attached to this node. | |||
*/ | |||
bool removeNode (Node* node); | |||
//============================================================================== | |||
/** Returns the number of connections in the graph. */ | |||
int getNumConnections() const { return connections.size(); } | |||
@@ -149,6 +149,7 @@ public: | |||
private: | |||
struct Pimpl; | |||
friend struct ContainerDeletePolicy<Pimpl>; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderAttachment) | |||
}; | |||
@@ -172,6 +173,7 @@ public: | |||
private: | |||
struct Pimpl; | |||
friend struct ContainerDeletePolicy<Pimpl>; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ComboBoxAttachment) | |||
}; | |||
@@ -195,6 +197,7 @@ public: | |||
private: | |||
struct Pimpl; | |||
friend struct ContainerDeletePolicy<Pimpl>; | |||
ScopedPointer<Pimpl> pimpl; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonAttachment) | |||
}; | |||
@@ -223,7 +223,7 @@ public: | |||
} | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool empty() const noexcept | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
@@ -126,6 +126,12 @@ public: | |||
return numUsed; | |||
} | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns a pointer to the object at this index in the array. | |||
If the index is out-of-range, this will return a null pointer, (and | |||
@@ -149,6 +149,12 @@ public: | |||
return numUsed; | |||
} | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns a pointer to the object at this index in the array. | |||
If the index is out-of-range, this will return a null pointer, (and | |||
@@ -136,6 +136,12 @@ public: | |||
return data.size(); | |||
} | |||
/** Returns true if the set is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept | |||
{ | |||
return size() == 0; | |||
} | |||
/** Returns one of the elements in the set. | |||
If the index passed in is beyond the range of valid elements, this | |||
@@ -1362,7 +1362,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
{ | |||
ScopedPointer<FunctionCall> f (new FunctionCall (location)); | |||
f->object = new UnqualifiedName (location, "typeof"); | |||
f->arguments.add (parseExpression()); | |||
f->arguments.add (parseUnary()); | |||
return f.release(); | |||
} | |||
@@ -1,5 +1,3 @@ | |||
private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); | |||
@Override | |||
public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults) | |||
{ | |||
@@ -136,6 +136,8 @@ public class JuceAppActivity extends Activity | |||
} | |||
} | |||
private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback); | |||
$$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the introjucer! | |||
//============================================================================== | |||
@@ -524,7 +524,9 @@ bool File::isOnHardDisk() const | |||
if (fullPath.toLowerCase()[0] <= 'b' && fullPath[1] == ':') | |||
return n != DRIVE_REMOVABLE; | |||
return n != DRIVE_CDROM && n != DRIVE_REMOTE; | |||
return n != DRIVE_CDROM | |||
&& n != DRIVE_REMOTE | |||
&& n != DRIVE_NO_ROOT_DIR; | |||
} | |||
bool File::isOnRemovableDrive() const | |||
@@ -50,6 +50,7 @@ | |||
//============================================================================== | |||
#include <memory> | |||
#include <vector> // included before platform defs to provide a definition of _LIBCPP_VERSION | |||
#include "juce_CompilerSupport.h" | |||
@@ -215,12 +215,6 @@ public: | |||
CharacterFunctions::copyAll (*this, src); | |||
} | |||
/** Copies a source string to this pointer, advancing this pointer as it goes. */ | |||
void writeAll (const CharPointer_ASCII src) noexcept | |||
{ | |||
strcpy (data, src.data); | |||
} | |||
/** Copies a source string to this pointer, advancing this pointer as it goes. | |||
The maxDestBytes parameter specifies the maximum number of bytes that can be written | |||
to the destination buffer before stopping. | |||
@@ -162,3 +162,16 @@ double CharacterFunctions::mulexp10 (const double value, int exponent) noexcept | |||
return negative ? (value / result) : (value * result); | |||
} | |||
juce_wchar CharacterFunctions::getUnicodeCharFromWindows1252Codepage (const uint8 c) noexcept | |||
{ | |||
if (c < 0x80 || c >= 0xa0) | |||
return (juce_wchar) c; | |||
static const uint16 lookup[] = { 0x20AC, 0x0007, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, | |||
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x0007, 0x017D, 0x0007, | |||
0x0007, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, | |||
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x0007, 0x017E, 0x0178 }; | |||
return (juce_wchar) lookup[c - 0x80]; | |||
} |
@@ -123,6 +123,9 @@ public: | |||
/** Returns 0 to 16 for '0' to 'F", or -1 for characters that aren't a legal hex digit. */ | |||
static int getHexDigitValue (juce_wchar digit) noexcept; | |||
/** Converts a byte of Windows 1252 codepage to unicode. */ | |||
static juce_wchar getUnicodeCharFromWindows1252Codepage (uint8 windows1252Char) noexcept; | |||
//============================================================================== | |||
/** Parses a character string to read a floating-point number. | |||
Note that this will advance the pointer that is passed in, leaving it at | |||
@@ -1387,6 +1387,10 @@ String String::replaceCharacter (const juce_wchar charToReplace, const juce_wcha | |||
String String::replaceCharacters (StringRef charactersToReplace, StringRef charactersToInsertInstead) const | |||
{ | |||
// Each character in the first string must have a matching one in the | |||
// second, so the two strings must be the same length. | |||
jassert (charactersToReplace.length() == charactersToInsertInstead.length()); | |||
StringCreationHelper builder (text); | |||
for (;;) | |||
@@ -118,7 +118,10 @@ public: | |||
//============================================================================== | |||
/** Returns the number of strings in the array */ | |||
inline int size() const noexcept { return strings.size(); }; | |||
inline int size() const noexcept { return strings.size(); } | |||
/** Returns true if the array is empty, false otherwise. */ | |||
inline bool isEmpty() const noexcept { return size() == 0; } | |||
/** Returns one of the strings from the array. | |||
@@ -136,15 +136,16 @@ namespace TimeHelpers | |||
static inline int daysFromJan1 (int year, int month) noexcept | |||
{ | |||
const short dayOfYear[] = { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, | |||
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; | |||
const short dayOfYear[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, | |||
0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }; | |||
return dayOfYear [(isLeapYear (year) ? 12 : 0) + month]; | |||
} | |||
static inline int64 daysFromYear0 (int year) noexcept | |||
{ | |||
return 365 * (year - 1) + (year / 400) - (year / 100) + (year / 4); | |||
--year; | |||
return 365 * year + (year / 400) - (year / 100) + (year / 4); | |||
} | |||
static inline int64 daysFrom1970 (int year) noexcept | |||
@@ -213,18 +214,9 @@ Time::Time (const int year, | |||
t.tm_sec = seconds; | |||
t.tm_isdst = -1; | |||
const int64 time = useLocalTime ? (int64) mktime (&t) | |||
: TimeHelpers::mktime_utc (t); | |||
if (time >= 0) | |||
{ | |||
millisSinceEpoch = 1000 * time + milliseconds; | |||
} | |||
else | |||
{ | |||
jassertfalse; // trying to create a date that is beyond the range that mktime supports! | |||
millisSinceEpoch = 0; | |||
} | |||
millisSinceEpoch = 1000 * (useLocalTime ? (int64) mktime (&t) | |||
: TimeHelpers::mktime_utc (t)) | |||
+ milliseconds; | |||
} | |||
Time::~Time() noexcept | |||
@@ -458,8 +450,8 @@ String Time::getUTCOffsetString (bool includeSemiColon) const | |||
String Time::toISO8601 (bool includeDividerCharacters) const | |||
{ | |||
return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%02.03f" | |||
: "%04d%02d%02dT%02d%02d%02.03f", | |||
return String::formatted (includeDividerCharacters ? "%04d-%02d-%02dT%02d:%02d:%06.03f" | |||
: "%04d%02d%02dT%02d%02d%06.03f", | |||
getYear(), | |||
getMonth() + 1, | |||
getDayOfMonth(), | |||
@@ -542,9 +534,7 @@ Time Time::fromISO8601 (StringRef iso) noexcept | |||
return Time(); | |||
} | |||
Time result (year, month - 1, day, hours, minutes, 0, 0, false); | |||
result.millisSinceEpoch += milliseconds; | |||
return result; | |||
return Time (year, month - 1, day, hours, minutes, 0, milliseconds, false); | |||
} | |||
String Time::getMonthName (const bool threeLetterVersion) const | |||
@@ -661,6 +651,14 @@ public: | |||
expect (Time::fromISO8601 ("2016-02-16T15:03:57.999-02:30") == Time (2016, 1, 16, 17, 33, 57, 999, false)); | |||
expect (Time::fromISO8601 ("20160216T150357.999-0230") == Time (2016, 1, 16, 17, 33, 57, 999, false)); | |||
expect (Time (1970, 0, 1, 0, 0, 0, 0, false) == Time (0)); | |||
expect (Time (2106, 1, 7, 6, 28, 15, 0, false) == Time (4294967295000)); | |||
expect (Time (2007, 10, 7, 1, 7, 20, 0, false) == Time (1194397640000)); | |||
expect (Time (2038, 0, 19, 3, 14, 7, 0, false) == Time (2147483647000)); | |||
expect (Time (2016, 2, 7, 11, 20, 8, 0, false) == Time (1457349608000)); | |||
expect (Time (1969, 11, 31, 23, 59, 59, 0, false) == Time (-1000)); | |||
expect (Time (1901, 11, 13, 20, 45, 53, 0, false) == Time (-2147483647000)); | |||
expect (Time (1982, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1983, 1, 1, 12, 0, 0, 0, true)); | |||
expect (Time (1970, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (1971, 1, 1, 12, 0, 0, 0, true)); | |||
expect (Time (2038, 1, 1, 12, 0, 0, 0, true) + RelativeTime::days (365) == Time (2039, 1, 1, 12, 0, 0, 0, true)); | |||
@@ -60,7 +60,7 @@ private: | |||
const int day = date & 31; | |||
const int hours = time >> 11; | |||
const int minutes = (time >> 5) & 63; | |||
const int seconds = (time & 31) << 1; | |||
const int seconds = (int) ((time & 31) << 1); | |||
return Time (year, month, day, hours, minutes, seconds); | |||
} | |||
@@ -357,13 +357,13 @@ void MessageManager::doPlatformSpecificShutdown() | |||
bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* const message) | |||
{ | |||
if (LinuxErrorHandling::errorOccurred) | |||
return false; | |||
if (InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
if (! LinuxErrorHandling::errorOccurred) | |||
{ | |||
queue->postMessage (message); | |||
return true; | |||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
{ | |||
queue->postMessage (message); | |||
return true; | |||
} | |||
} | |||
return false; | |||
@@ -389,16 +389,16 @@ bool MessageManager::dispatchNextMessageOnSystemQueue (bool returnIfNoPendingMes | |||
break; | |||
} | |||
InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating(); | |||
jassert (queue != nullptr); | |||
if (queue->dispatchNextEvent()) | |||
return true; | |||
if (InternalMessageQueue* queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
{ | |||
if (queue->dispatchNextEvent()) | |||
return true; | |||
if (returnIfNoPendingMessages) | |||
break; | |||
if (returnIfNoPendingMessages) | |||
break; | |||
queue->sleepUntilEvent (2000); | |||
queue->sleepUntilEvent (2000); | |||
} | |||
} | |||
return false; | |||
@@ -104,7 +104,7 @@ bool FillType::operator!= (const FillType& other) const | |||
void FillType::setColour (Colour newColour) noexcept | |||
{ | |||
gradient = nullptr; | |||
image = Image::null; | |||
image = Image(); | |||
colour = newColour; | |||
} | |||
@@ -116,7 +116,7 @@ void FillType::setGradient (const ColourGradient& newGradient) | |||
} | |||
else | |||
{ | |||
image = Image::null; | |||
image = Image(); | |||
gradient = new ColourGradient (newGradient); | |||
colour = Colours::black; | |||
} | |||
@@ -47,7 +47,7 @@ public: | |||
return item->image; | |||
} | |||
return Image::null; | |||
return Image(); | |||
} | |||
void addImageToCache (const Image& image, const int64 hashCode) | |||
@@ -128,7 +128,7 @@ Image ImageCache::getFromHashCode (const int64 hashCode) | |||
if (Pimpl::getInstanceWithoutCreating() != nullptr) | |||
return Pimpl::getInstanceWithoutCreating()->getFromHashCode (hashCode); | |||
return Image::null; | |||
return Image(); | |||
} | |||
void ImageCache::addImageToCache (const Image& image, const int64 hashCode) | |||
@@ -44,6 +44,12 @@ | |||
#import <QuartzCore/QuartzCore.h> | |||
#elif JUCE_WINDOWS | |||
// get rid of some warnings in Window's own headers | |||
#ifdef JUCE_MSVC | |||
#pragma warning (push) | |||
#pragma warning (disable : 4458) | |||
#endif | |||
#if JUCE_MINGW && JUCE_USE_DIRECTWRITE | |||
#warning "DirectWrite not currently implemented with mingw..." | |||
#undef JUCE_USE_DIRECTWRITE | |||
@@ -62,6 +68,10 @@ | |||
#include <malloc.h> | |||
#endif | |||
#ifdef JUCE_MSVC | |||
#pragma warning (pop) | |||
#endif | |||
#elif JUCE_IOS | |||
#import <QuartzCore/QuartzCore.h> | |||
#import <CoreText/CoreText.h> | |||
@@ -863,7 +863,7 @@ Image juce_loadWithCoreImage (InputStream& input) | |||
} | |||
} | |||
return Image::null; | |||
return Image(); | |||
} | |||
#endif | |||
@@ -245,9 +245,9 @@ namespace CoreTextTypeLayout | |||
{ | |||
switch (text.getReadingDirection()) | |||
{ | |||
case AttributedString::ReadingDirection::rightToLeft: return kCTWritingDirectionRightToLeft; | |||
case AttributedString::ReadingDirection::leftToRight: return kCTWritingDirectionLeftToRight; | |||
default: return kCTWritingDirectionNatural; | |||
case AttributedString::rightToLeft: return kCTWritingDirectionRightToLeft; | |||
case AttributedString::leftToRight: return kCTWritingDirectionLeftToRight; | |||
default: return kCTWritingDirectionNatural; | |||
} | |||
} | |||
@@ -438,6 +438,15 @@ struct Component::ComponentHelpers | |||
return Desktop::getInstance().getDisplays().getMainDisplay().userArea; | |||
} | |||
static void releaseAllCachedImageResources (Component& c) | |||
{ | |||
if (CachedComponentImage* cached = c.getCachedComponentImage()) | |||
cached->releaseResources(); | |||
for (int i = c.getNumChildComponents(); --i >= 0;) | |||
releaseAllCachedImageResources (*c.getChildComponent (i)); | |||
} | |||
}; | |||
//============================================================================== | |||
@@ -528,8 +537,7 @@ void Component::setVisible (bool shouldBeVisible) | |||
if (! shouldBeVisible) | |||
{ | |||
if (cachedImage != nullptr) | |||
cachedImage->releaseResources(); | |||
ComponentHelpers::releaseAllCachedImageResources (*this); | |||
if (currentlyFocusedComponent == this || isParentOf (currentlyFocusedComponent)) | |||
{ | |||
@@ -813,7 +821,7 @@ public: | |||
bool invalidateAll() override { validArea.clear(); return true; } | |||
bool invalidate (const Rectangle<int>& area) override { validArea.subtract (area); return true; } | |||
void releaseResources() override { image = Image::null; } | |||
void releaseResources() override { image = Image(); } | |||
private: | |||
Image image; | |||
@@ -1547,8 +1555,7 @@ Component* Component::removeChildComponent (const int index, bool sendParentEven | |||
childComponentList.remove (index); | |||
child->parentComponent = nullptr; | |||
if (child->cachedImage != nullptr) | |||
child->cachedImage->releaseResources(); | |||
ComponentHelpers::releaseAllCachedImageResources (*child); | |||
// (NB: there are obscure situations where child->isShowing() = false, but it still has the focus) | |||
if (currentlyFocusedComponent == child || child->isParentOf (currentlyFocusedComponent)) | |||
@@ -190,7 +190,7 @@ void DrawableShape::strokeChanged() | |||
strokePath.clear(); | |||
const float extraAccuracy = 4.0f; | |||
if (dashLengths.empty()) | |||
if (dashLengths.isEmpty()) | |||
strokeType.createStrokedPath (strokePath, path, AffineTransform(), extraAccuracy); | |||
else | |||
strokeType.createDashedStroke (strokePath, path, dashLengths.getRawDataPointer(), | |||
@@ -905,7 +905,7 @@ private: | |||
} | |||
}; | |||
GetFillTypeOp op = { this, &path, opacity }; | |||
GetFillTypeOp op = { this, &path, opacity, FillType() }; | |||
if (topLevelXml.applyOperationToChildWithID (urlID, op)) | |||
return op.fillType; | |||
@@ -153,7 +153,7 @@ public: | |||
file = newFile; | |||
fileSize = newFileSize; | |||
modTime = newModTime; | |||
icon = Image::null; | |||
icon = Image(); | |||
isDirectory = fileInfo != nullptr && fileInfo->isDirectory; | |||
repaint(); | |||
@@ -57,7 +57,7 @@ void ImagePreviewComponent::timerCallback() | |||
{ | |||
stopTimer(); | |||
currentThumbnail = Image::null; | |||
currentThumbnail = Image(); | |||
currentDetails.clear(); | |||
repaint(); | |||
@@ -48,11 +48,7 @@ | |||
#import <IOKit/pwr_mgt/IOPMLib.h> | |||
#if JUCE_SUPPORT_CARBON | |||
#define Point CarbonDummyPointName | |||
#define Component CarbonDummyCompName | |||
#import <Carbon/Carbon.h> // still needed for SetSystemUIMode() | |||
#undef Point | |||
#undef Component | |||
#endif | |||
//============================================================================== | |||
@@ -54,6 +54,7 @@ Viewport::Viewport (const String& name) | |||
Viewport::~Viewport() | |||
{ | |||
setScrollOnDragEnabled (false); | |||
deleteOrRemoveContentComp(); | |||
} | |||
@@ -180,6 +181,104 @@ void Viewport::componentMovedOrResized (Component&, bool, bool) | |||
updateVisibleArea(); | |||
} | |||
//============================================================================== | |||
typedef AnimatedPosition<AnimatedPositionBehaviours::ContinuousWithMomentum> ViewportDragPosition; | |||
struct Viewport::DragToScrollListener : private MouseListener, | |||
private ViewportDragPosition::Listener | |||
{ | |||
DragToScrollListener (Viewport& v) | |||
: viewport (v), numTouches (0), isDragging (false) | |||
{ | |||
viewport.contentHolder.addMouseListener (this, true); | |||
offsetX.addListener (this); | |||
offsetY.addListener (this); | |||
} | |||
~DragToScrollListener() | |||
{ | |||
viewport.contentHolder.removeMouseListener (this); | |||
} | |||
void positionChanged (ViewportDragPosition&, double) override | |||
{ | |||
viewport.setViewPosition (originalViewPos - Point<int> ((int) offsetX.getPosition(), | |||
(int) offsetY.getPosition())); | |||
} | |||
void mouseDown (const MouseEvent&) override | |||
{ | |||
++numTouches; | |||
} | |||
void mouseDrag (const MouseEvent& e) override | |||
{ | |||
if (numTouches == 1) | |||
{ | |||
Point<float> totalOffset = e.getOffsetFromDragStart().toFloat(); | |||
if (! isDragging && totalOffset.getDistanceFromOrigin() > 8.0f) | |||
{ | |||
isDragging = true; | |||
originalViewPos = viewport.getViewPosition(); | |||
offsetX.setPosition (0.0); | |||
offsetX.beginDrag(); | |||
offsetY.setPosition (0.0); | |||
offsetY.beginDrag(); | |||
} | |||
if (isDragging) | |||
{ | |||
offsetX.drag (totalOffset.x); | |||
offsetY.drag (totalOffset.y); | |||
} | |||
} | |||
} | |||
void mouseUp (const MouseEvent&) override | |||
{ | |||
if (--numTouches == 0) | |||
{ | |||
offsetX.endDrag(); | |||
offsetY.endDrag(); | |||
isDragging = false; | |||
} | |||
jassert (numTouches >= 0); | |||
} | |||
Viewport& viewport; | |||
ViewportDragPosition offsetX, offsetY; | |||
Point<int> originalViewPos; | |||
int numTouches; | |||
bool isDragging; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DragToScrollListener) | |||
}; | |||
void Viewport::setScrollOnDragEnabled (bool shouldScrollOnDrag) | |||
{ | |||
if (isScrollOnDragEnabled() != shouldScrollOnDrag) | |||
{ | |||
if (shouldScrollOnDrag) | |||
dragToScrollListener = new DragToScrollListener (*this); | |||
else | |||
dragToScrollListener = nullptr; | |||
} | |||
} | |||
bool Viewport::isScrollOnDragEnabled() const noexcept | |||
{ | |||
return dragToScrollListener != nullptr; | |||
} | |||
bool Viewport::isCurrentlyScrollingOnDrag() const noexcept | |||
{ | |||
return dragToScrollListener != nullptr && dragToScrollListener->isDragging; | |||
} | |||
//============================================================================== | |||
void Viewport::lookAndFeelChanged() | |||
{ | |||
if (! customScrollBarThickness) | |||
@@ -241,6 +241,17 @@ public: | |||
ScrollBar* getHorizontalScrollBar() noexcept { return &horizontalScrollBar; } | |||
/** Enables or disables drag-to-scroll functionality in the viewport. */ | |||
void setScrollOnDragEnabled (bool shouldScrollOnDrag); | |||
/** Returns true if drag-to-scroll functionality is enabled. */ | |||
bool isScrollOnDragEnabled() const noexcept; | |||
/** Returns true if the user is currently dragging-to-scroll. | |||
@see setScrollOnDragEnabled | |||
*/ | |||
bool isCurrentlyScrollingOnDrag() const noexcept; | |||
//============================================================================== | |||
/** @internal */ | |||
void resized() override; | |||
@@ -271,6 +282,11 @@ private: | |||
Component contentHolder; | |||
ScrollBar verticalScrollBar, horizontalScrollBar; | |||
struct DragToScrollListener; | |||
friend struct DragToScrollListener; | |||
friend struct ContainerDeletePolicy<DragToScrollListener>; | |||
ScopedPointer<DragToScrollListener> dragToScrollListener; | |||
Point<int> viewportPosToCompPos (Point<int>) const; | |||
void updateVisibleArea(); | |||
@@ -265,13 +265,12 @@ void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*isM | |||
const int fontHeight = roundToInt (font.getHeight() * 0.6f); | |||
const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2)); | |||
const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2)); | |||
const int textWidth = button.getWidth() - leftIndent - rightIndent; | |||
g.drawFittedText (button.getButtonText(), | |||
leftIndent, | |||
yIndent, | |||
button.getWidth() - leftIndent - rightIndent, | |||
button.getHeight() - yIndent * 2, | |||
Justification::centred, 2); | |||
if (textWidth > 0) | |||
g.drawFittedText (button.getButtonText(), | |||
leftIndent, yIndent, textWidth, button.getHeight() - yIndent * 2, | |||
Justification::centred, 2); | |||
} | |||
void LookAndFeel_V2::drawTickBox (Graphics& g, Component& component, | |||
@@ -22,120 +22,68 @@ | |||
============================================================================== | |||
*/ | |||
//============================================================================== | |||
namespace PopupMenuSettings | |||
{ | |||
const int scrollZone = 24; | |||
const int borderSize = 2; | |||
const int timerInterval = 50; | |||
const int dismissCommandId = 0x6287345f; | |||
const int sectionHeaderID = 0x4734a34f; | |||
static bool menuWasHiddenBecauseOfAppChange = false; | |||
} | |||
class PopupMenu::Item | |||
//============================================================================== | |||
struct PopupMenu::HelperClasses | |||
{ | |||
public: | |||
Item() : itemID (0), isActive (true), isSeparator (true), isTicked (false), | |||
usesColour (false), commandManager (nullptr) | |||
{} | |||
Item (const int itemId, | |||
const String& name, | |||
const bool active, | |||
const bool ticked, | |||
Drawable* drawable, | |||
const Colour colour, | |||
const bool useColour, | |||
CustomComponent* const custom, | |||
const PopupMenu* const sub, | |||
ApplicationCommandManager* const manager) | |||
: itemID (itemId), text (name), textColour (colour), | |||
isActive (active), isSeparator (false), isTicked (ticked), | |||
usesColour (useColour), iconDrawable (drawable), | |||
customComp (custom), subMenu (createCopyIfNotNull (sub)), commandManager (manager) | |||
{ | |||
if (commandManager != nullptr && itemID != 0) | |||
{ | |||
String shortcutKey; | |||
const Array<KeyPress> keyPresses (commandManager->getKeyMappings() | |||
->getKeyPressesAssignedToCommand (itemID)); | |||
for (int i = 0; i < keyPresses.size(); ++i) | |||
{ | |||
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons()); | |||
if (shortcutKey.isNotEmpty()) | |||
shortcutKey << ", "; | |||
if (key.length() == 1 && key[0] < 128) | |||
shortcutKey << "shortcut: '" << key << '\''; | |||
else | |||
shortcutKey << key; | |||
} | |||
class MouseSourceState; | |||
class MenuWindow; | |||
shortcutKey = shortcutKey.trim(); | |||
static bool canBeTriggered (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.itemID != 0 && ! item.isSectionHeader; } | |||
static bool hasActiveSubMenu (const PopupMenu::Item& item) noexcept { return item.isEnabled && item.subMenu != nullptr && item.subMenu->items.size() > 0; } | |||
static const Colour* getColour (const PopupMenu::Item& item) noexcept { return item.colour != Colour (0x00000000) ? &item.colour : nullptr; } | |||
static bool hasSubMenu (const PopupMenu::Item& item) noexcept { return item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); } | |||
if (shortcutKey.isNotEmpty()) | |||
text << "<end>" << shortcutKey; | |||
} | |||
//============================================================================== | |||
struct HeaderItemComponent : public PopupMenu::CustomComponent | |||
{ | |||
HeaderItemComponent (const String& name) : PopupMenu::CustomComponent (false) | |||
{ | |||
setName (name); | |||
} | |||
Item (const Item& other) | |||
: itemID (other.itemID), | |||
text (other.text), | |||
textColour (other.textColour), | |||
isActive (other.isActive), | |||
isSeparator (other.isSeparator), | |||
isTicked (other.isTicked), | |||
usesColour (other.usesColour), | |||
iconDrawable (other.iconDrawable != nullptr ? other.iconDrawable->createCopy() : nullptr), | |||
customComp (other.customComp), | |||
subMenu (createCopyIfNotNull (other.subMenu.get())), | |||
commandManager (other.commandManager) | |||
{} | |||
bool canBeTriggered() const noexcept { return isActive && itemID != 0 && itemID != PopupMenuSettings::sectionHeaderID; } | |||
bool hasActiveSubMenu() const noexcept { return isActive && subMenu != nullptr && subMenu->items.size() > 0; } | |||
//============================================================================== | |||
const int itemID; | |||
String text; | |||
const Colour textColour; | |||
const bool isActive, isSeparator, isTicked, usesColour; | |||
ScopedPointer<Drawable> iconDrawable; | |||
ReferenceCountedObjectPtr<CustomComponent> customComp; | |||
ScopedPointer<PopupMenu> subMenu; | |||
ApplicationCommandManager* const commandManager; | |||
void paint (Graphics& g) override | |||
{ | |||
getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); | |||
} | |||
private: | |||
Item& operator= (const Item&); | |||
void getIdealSize (int& idealWidth, int& idealHeight) override | |||
{ | |||
getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); | |||
idealHeight += idealHeight / 2; | |||
idealWidth += idealWidth / 4; | |||
} | |||
JUCE_LEAK_DETECTOR (Item) | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (HeaderItemComponent) | |||
}; | |||
//============================================================================== | |||
struct PopupMenu::HelperClasses | |||
struct ItemComponent : public Component | |||
{ | |||
class MouseSourceState; | |||
class MenuWindow; | |||
//============================================================================== | |||
class ItemComponent : public Component | |||
{ | |||
public: | |||
ItemComponent (const PopupMenu::Item& info, int standardItemHeight, MenuWindow& parent) | |||
: itemInfo (info), | |||
ItemComponent (const PopupMenu::Item& i, int standardItemHeight, MenuWindow& parent) | |||
: item (i), | |||
customComp (i.customComponent), | |||
isHighlighted (false) | |||
{ | |||
addAndMakeVisible (itemInfo.customComp); | |||
if (item.isSectionHeader) | |||
customComp = new HeaderItemComponent (item.text); | |||
addAndMakeVisible (customComp); | |||
parent.addAndMakeVisible (this); | |||
updateShortcutKeyDescription(); | |||
int itemW = 80; | |||
int itemH = 16; | |||
getIdealSize (itemW, itemH, standardItemHeight); | |||
@@ -146,45 +94,33 @@ public: | |||
~ItemComponent() | |||
{ | |||
removeChildComponent (itemInfo.customComp); | |||
removeChildComponent (customComp); | |||
} | |||
void getIdealSize (int& idealWidth, int& idealHeight, const int standardItemHeight) | |||
{ | |||
if (itemInfo.customComp != nullptr) | |||
itemInfo.customComp->getIdealSize (idealWidth, idealHeight); | |||
if (customComp != nullptr) | |||
customComp->getIdealSize (idealWidth, idealHeight); | |||
else | |||
getLookAndFeel().getIdealPopupMenuItemSize (itemInfo.text, | |||
itemInfo.isSeparator, | |||
getLookAndFeel().getIdealPopupMenuItemSize (getTextForMeasurement(), | |||
item.isSeparator, | |||
standardItemHeight, | |||
idealWidth, idealHeight); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
if (itemInfo.customComp == nullptr) | |||
{ | |||
String mainText (itemInfo.text); | |||
String endText; | |||
const int endIndex = mainText.indexOf ("<end>"); | |||
if (endIndex >= 0) | |||
{ | |||
endText = mainText.substring (endIndex + 5).trim(); | |||
mainText = mainText.substring (0, endIndex); | |||
} | |||
getLookAndFeel() | |||
.drawPopupMenuItem (g, getLocalBounds(), | |||
itemInfo.isSeparator, | |||
itemInfo.isActive, | |||
isHighlighted, | |||
itemInfo.isTicked, | |||
itemInfo.subMenu != nullptr && (itemInfo.itemID == 0 || itemInfo.subMenu->getNumItems() > 0), | |||
mainText, endText, | |||
itemInfo.iconDrawable, | |||
itemInfo.usesColour ? &(itemInfo.textColour) : nullptr); | |||
} | |||
if (customComp == nullptr) | |||
getLookAndFeel().drawPopupMenuItem (g, getLocalBounds(), | |||
item.isSeparator, | |||
item.isEnabled, | |||
isHighlighted, | |||
item.isTicked, | |||
hasSubMenu (item), | |||
item.text, | |||
item.shortcutKeyDescription, | |||
item.image, | |||
getColour (item)); | |||
} | |||
void resized() override | |||
@@ -195,24 +131,57 @@ public: | |||
void setHighlighted (bool shouldBeHighlighted) | |||
{ | |||
shouldBeHighlighted = shouldBeHighlighted && itemInfo.isActive; | |||
shouldBeHighlighted = shouldBeHighlighted && item.isEnabled; | |||
if (isHighlighted != shouldBeHighlighted) | |||
{ | |||
isHighlighted = shouldBeHighlighted; | |||
if (itemInfo.customComp != nullptr) | |||
itemInfo.customComp->setHighlighted (shouldBeHighlighted); | |||
if (customComp != nullptr) | |||
customComp->setHighlighted (shouldBeHighlighted); | |||
repaint(); | |||
} | |||
} | |||
PopupMenu::Item itemInfo; | |||
PopupMenu::Item item; | |||
private: | |||
// NB: we use a copy of the one from the item info in case we're using our own section comp | |||
ReferenceCountedObjectPtr<CustomComponent> customComp; | |||
bool isHighlighted; | |||
void updateShortcutKeyDescription() | |||
{ | |||
if (item.commandManager != nullptr && item.itemID != 0) | |||
{ | |||
String shortcutKey; | |||
const Array<KeyPress> keyPresses (item.commandManager->getKeyMappings() | |||
->getKeyPressesAssignedToCommand (item.itemID)); | |||
for (int i = 0; i < keyPresses.size(); ++i) | |||
{ | |||
const String key (keyPresses.getReference(i).getTextDescriptionWithIcons()); | |||
if (shortcutKey.isNotEmpty()) | |||
shortcutKey << ", "; | |||
if (key.length() == 1 && key[0] < 128) | |||
shortcutKey << "shortcut: '" << key << '\''; | |||
else | |||
shortcutKey << key; | |||
} | |||
item.shortcutKeyDescription = shortcutKey.trim(); | |||
} | |||
} | |||
String getTextForMeasurement() const | |||
{ | |||
return item.shortcutKeyDescription.isNotEmpty() ? item.text + " " + item.shortcutKeyDescription | |||
: item.text; | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent) | |||
}; | |||
@@ -744,43 +713,43 @@ public: | |||
for (int i = items.size(); --i >= 0;) | |||
{ | |||
ItemComponent* const m = items.getUnchecked(i); | |||
if (m != nullptr | |||
&& m->itemInfo.itemID == itemID | |||
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) | |||
if (ItemComponent* const m = items.getUnchecked(i)) | |||
{ | |||
const int currentY = m->getY(); | |||
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight()) | |||
if (m->item.itemID == itemID | |||
&& windowPos.getHeight() > PopupMenuSettings::scrollZone * 4) | |||
{ | |||
if (wantedY < 0) | |||
wantedY = jlimit (PopupMenuSettings::scrollZone, | |||
jmax (PopupMenuSettings::scrollZone, | |||
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), | |||
currentY); | |||
const int currentY = m->getY(); | |||
const Rectangle<int> mon (Desktop::getInstance().getDisplays() | |||
.getDisplayContaining (windowPos.getPosition()).userArea); | |||
if (wantedY > 0 || currentY < 0 || m->getBottom() > windowPos.getHeight()) | |||
{ | |||
if (wantedY < 0) | |||
wantedY = jlimit (PopupMenuSettings::scrollZone, | |||
jmax (PopupMenuSettings::scrollZone, | |||
windowPos.getHeight() - (PopupMenuSettings::scrollZone + m->getHeight())), | |||
currentY); | |||
int deltaY = wantedY - currentY; | |||
const Rectangle<int> mon (Desktop::getInstance().getDisplays() | |||
.getDisplayContaining (windowPos.getPosition()).userArea); | |||
windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), | |||
jmin (windowPos.getHeight(), mon.getHeight())); | |||
int deltaY = wantedY - currentY; | |||
const int newY = jlimit (mon.getY(), | |||
mon.getBottom() - windowPos.getHeight(), | |||
windowPos.getY() + deltaY); | |||
windowPos.setSize (jmin (windowPos.getWidth(), mon.getWidth()), | |||
jmin (windowPos.getHeight(), mon.getHeight())); | |||
deltaY -= newY - windowPos.getY(); | |||
const int newY = jlimit (mon.getY(), | |||
mon.getBottom() - windowPos.getHeight(), | |||
windowPos.getY() + deltaY); | |||
childYOffset -= deltaY; | |||
windowPos.setPosition (windowPos.getX(), newY); | |||
deltaY -= newY - windowPos.getY(); | |||
updateYPositions(); | |||
} | |||
childYOffset -= deltaY; | |||
windowPos.setPosition (windowPos.getX(), newY); | |||
break; | |||
updateYPositions(); | |||
} | |||
break; | |||
} | |||
} | |||
} | |||
} | |||
@@ -877,9 +846,9 @@ public: | |||
activeSubMenu = nullptr; | |||
if (childComp != nullptr | |||
&& childComp->itemInfo.hasActiveSubMenu()) | |||
&& hasActiveSubMenu (childComp->item)) | |||
{ | |||
activeSubMenu = new HelperClasses::MenuWindow (*(childComp->itemInfo.subMenu), this, | |||
activeSubMenu = new HelperClasses::MenuWindow (*(childComp->item.subMenu), this, | |||
options.withTargetScreenArea (childComp->getScreenBounds()) | |||
.withMinimumWidth (0) | |||
.withTargetComponent (nullptr), | |||
@@ -897,11 +866,11 @@ public: | |||
void triggerCurrentlyHighlightedItem() | |||
{ | |||
if (currentChild != nullptr | |||
&& currentChild->itemInfo.canBeTriggered() | |||
&& (currentChild->itemInfo.customComp == nullptr | |||
|| currentChild->itemInfo.customComp->isTriggeredAutomatically())) | |||
&& canBeTriggered (currentChild->item) | |||
&& (currentChild->item.customComponent == nullptr | |||
|| currentChild->item.customComponent->isTriggeredAutomatically())) | |||
{ | |||
dismissMenu (¤tChild->itemInfo); | |||
dismissMenu (¤tChild->item); | |||
} | |||
} | |||
@@ -917,7 +886,7 @@ public: | |||
if (ItemComponent* mic = items.getUnchecked ((start + items.size()) % items.size())) | |||
{ | |||
if (mic->itemInfo.canBeTriggered() || mic->itemInfo.hasActiveSubMenu()) | |||
if (canBeTriggered (mic->item) || hasActiveSubMenu (mic->item)) | |||
{ | |||
setCurrentlyHighlightedChild (mic); | |||
break; | |||
@@ -973,7 +942,7 @@ public: | |||
if (! window.windowIsStillValid()) | |||
return; | |||
startTimer (PopupMenuSettings::timerInterval); | |||
startTimerHz (20); | |||
handleMousePosition (e.getScreenPosition()); | |||
} | |||
@@ -1175,9 +1144,8 @@ private: | |||
}; | |||
//============================================================================== | |||
class NormalComponentWrapper : public PopupMenu::CustomComponent | |||
struct NormalComponentWrapper : public PopupMenu::CustomComponent | |||
{ | |||
public: | |||
NormalComponentWrapper (Component* const comp, const int w, const int h, | |||
const bool triggerMenuItemAutomaticallyWhenClicked) | |||
: PopupMenu::CustomComponent (triggerMenuItemAutomaticallyWhenClicked), | |||
@@ -1198,38 +1166,11 @@ public: | |||
child->setBounds (getLocalBounds()); | |||
} | |||
private: | |||
const int width, height; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NormalComponentWrapper) | |||
}; | |||
//============================================================================== | |||
class HeaderItemComponent : public PopupMenu::CustomComponent | |||
{ | |||
public: | |||
HeaderItemComponent (const String& name) | |||
: PopupMenu::CustomComponent (false) | |||
{ | |||
setName (name); | |||
} | |||
void paint (Graphics& g) override | |||
{ | |||
getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); | |||
} | |||
void getIdealSize (int& idealWidth, int& idealHeight) override | |||
{ | |||
getLookAndFeel().getIdealPopupMenuItemSize (getName(), false, -1, idealWidth, idealHeight); | |||
idealHeight += idealHeight / 2; | |||
idealWidth += idealWidth / 4; | |||
} | |||
private: | |||
JUCE_LEAK_DETECTOR (HeaderItemComponent) | |||
}; | |||
}; | |||
//============================================================================== | |||
@@ -1282,14 +1223,70 @@ void PopupMenu::clear() | |||
items.clear(); | |||
} | |||
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) | |||
//============================================================================== | |||
PopupMenu::Item::Item() noexcept | |||
: itemID (0), | |||
commandManager (nullptr), | |||
colour (0x00000000), | |||
isEnabled (true), | |||
isTicked (false), | |||
isSeparator (false), | |||
isSectionHeader (false) | |||
{ | |||
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the id | |||
// for an item.. | |||
} | |||
items.add (new Item (itemResultID, itemText, isActive, isTicked, nullptr, | |||
Colours::black, false, nullptr, nullptr, nullptr)); | |||
PopupMenu::Item::Item (const Item& other) | |||
: text (other.text), | |||
itemID (other.itemID), | |||
subMenu (createCopyIfNotNull (other.subMenu.get())), | |||
image (other.image != nullptr ? other.image->createCopy() : nullptr), | |||
customComponent (other.customComponent), | |||
commandManager (other.commandManager), | |||
shortcutKeyDescription (other.shortcutKeyDescription), | |||
colour (other.colour), | |||
isEnabled (other.isEnabled), | |||
isTicked (other.isTicked), | |||
isSeparator (other.isSeparator), | |||
isSectionHeader (other.isSectionHeader) | |||
{ | |||
} | |||
PopupMenu::Item& PopupMenu::Item::operator= (const Item& other) | |||
{ | |||
text = other.text; | |||
itemID = other.itemID; | |||
subMenu = createCopyIfNotNull (other.subMenu.get()); | |||
image = (other.image != nullptr ? other.image->createCopy() : nullptr); | |||
customComponent = other.customComponent; | |||
commandManager = other.commandManager; | |||
shortcutKeyDescription = other.shortcutKeyDescription; | |||
colour = other.colour; | |||
isEnabled = other.isEnabled; | |||
isTicked = other.isTicked; | |||
isSeparator = other.isSeparator; | |||
isSectionHeader = other.isSectionHeader; | |||
return *this; | |||
} | |||
void PopupMenu::addItem (const Item& newItem) | |||
{ | |||
// An ID of 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the ID for an item.. | |||
jassert (newItem.itemID != 0 | |||
|| newItem.isSeparator || newItem.isSectionHeader | |||
|| newItem.subMenu != nullptr); | |||
items.add (new Item (newItem)); | |||
} | |||
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked) | |||
{ | |||
Item i; | |||
i.text = itemText; | |||
i.itemID = itemResultID; | |||
i.isEnabled = isActive; | |||
i.isTicked = isTicked; | |||
addItem (i); | |||
} | |||
static Drawable* createDrawableFromImage (const Image& im) | |||
@@ -1306,23 +1303,18 @@ static Drawable* createDrawableFromImage (const Image& im) | |||
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, const Image& iconToUse) | |||
{ | |||
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the id | |||
// for an item.. | |||
items.add (new Item (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse), | |||
Colours::black, false, nullptr, nullptr, nullptr)); | |||
addItem (itemResultID, itemText, isActive, isTicked, createDrawableFromImage (iconToUse)); | |||
} | |||
void PopupMenu::addItem (int itemResultID, const String& itemText, bool isActive, bool isTicked, Drawable* iconToUse) | |||
{ | |||
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the id | |||
// for an item.. | |||
items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, | |||
Colours::black, false, nullptr, nullptr, nullptr)); | |||
Item i; | |||
i.text = itemText; | |||
i.itemID = itemResultID; | |||
i.isEnabled = isActive; | |||
i.isTicked = isTicked; | |||
i.image = iconToUse; | |||
addItem (i); | |||
} | |||
void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, | |||
@@ -1337,53 +1329,59 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, | |||
ApplicationCommandInfo info (*registeredInfo); | |||
ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info); | |||
items.add (new Item (commandID, | |||
displayName.isNotEmpty() ? displayName | |||
: info.shortName, | |||
target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0, | |||
(info.flags & ApplicationCommandInfo::isTicked) != 0, | |||
iconToUse, | |||
Colours::black, | |||
false, | |||
nullptr, nullptr, | |||
commandManager)); | |||
Item i; | |||
i.text = displayName.isNotEmpty() ? displayName : info.shortName; | |||
i.itemID = (int) commandID; | |||
i.commandManager = commandManager; | |||
i.isEnabled = target != nullptr && (info.flags & ApplicationCommandInfo::isDisabled) == 0; | |||
i.isTicked = (info.flags & ApplicationCommandInfo::isTicked) != 0; | |||
i.image = iconToUse; | |||
addItem (i); | |||
} | |||
} | |||
void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, | |||
bool isActive, bool isTicked, Drawable* iconToUse) | |||
{ | |||
jassert (itemResultID != 0); // 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the id | |||
// for an item.. | |||
items.add (new Item (itemResultID, itemText, isActive, isTicked, iconToUse, | |||
itemTextColour, true, nullptr, nullptr, nullptr)); | |||
Item i; | |||
i.text = itemText; | |||
i.itemID = itemResultID; | |||
i.colour = itemTextColour; | |||
i.isEnabled = isActive; | |||
i.isTicked = isTicked; | |||
i.image = iconToUse; | |||
addItem (i); | |||
} | |||
void PopupMenu::addColouredItem (int itemResultID, const String& itemText, Colour itemTextColour, | |||
bool isActive, bool isTicked, const Image& iconToUse) | |||
{ | |||
addColouredItem (itemResultID, itemText, itemTextColour, isActive, isTicked, createDrawableFromImage (iconToUse)); | |||
Item i; | |||
i.text = itemText; | |||
i.itemID = itemResultID; | |||
i.colour = itemTextColour; | |||
i.isEnabled = isActive; | |||
i.isTicked = isTicked; | |||
i.image = createDrawableFromImage (iconToUse); | |||
addItem (i); | |||
} | |||
void PopupMenu::addCustomItem (int itemID, CustomComponent* cc, const PopupMenu* subMenu) | |||
void PopupMenu::addCustomItem (int itemResultID, CustomComponent* cc, const PopupMenu* subMenu) | |||
{ | |||
jassert (itemID != 0); // 0 is used as a return value to indicate that the user | |||
// didn't pick anything, so you shouldn't use it as the id | |||
// for an item.. | |||
items.add (new Item (itemID, String::empty, true, false, nullptr, | |||
Colours::black, false, cc, subMenu, nullptr)); | |||
Item i; | |||
i.itemID = itemResultID; | |||
i.customComponent = cc; | |||
i.subMenu = createCopyIfNotNull (subMenu); | |||
addItem (i); | |||
} | |||
void PopupMenu::addCustomItem (int itemResultID, Component* customComponent, int idealWidth, int idealHeight, | |||
bool triggerMenuItemAutomaticallyWhenClicked, const PopupMenu* subMenu) | |||
{ | |||
items.add (new Item (itemResultID, String::empty, true, false, nullptr, Colours::black, false, | |||
new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, | |||
triggerMenuItemAutomaticallyWhenClicked), | |||
subMenu, nullptr)); | |||
addCustomItem (itemResultID, | |||
new HelperClasses::NormalComponentWrapper (customComponent, idealWidth, idealHeight, | |||
triggerMenuItemAutomaticallyWhenClicked), | |||
subMenu); | |||
} | |||
void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive) | |||
@@ -1400,19 +1398,32 @@ void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, | |||
void PopupMenu::addSubMenu (const String& subMenuName, const PopupMenu& subMenu, bool isActive, | |||
Drawable* iconToUse, bool isTicked, int itemResultID) | |||
{ | |||
items.add (new Item (itemResultID, subMenuName, isActive && (itemResultID != 0 || subMenu.getNumItems() > 0), isTicked, | |||
iconToUse, Colours::black, false, nullptr, &subMenu, nullptr)); | |||
Item i; | |||
i.text = subMenuName; | |||
i.itemID = itemResultID; | |||
i.subMenu = new PopupMenu (subMenu); | |||
i.isEnabled = isActive && (itemResultID != 0 || subMenu.getNumItems() > 0); | |||
i.isTicked = isTicked; | |||
i.image = iconToUse; | |||
addItem (i); | |||
} | |||
void PopupMenu::addSeparator() | |||
{ | |||
if (items.size() > 0 && ! items.getLast()->isSeparator) | |||
items.add (new Item()); | |||
{ | |||
Item i; | |||
i.isSeparator = true; | |||
addItem (i); | |||
} | |||
} | |||
void PopupMenu::addSectionHeader (const String& title) | |||
{ | |||
addCustomItem (PopupMenuSettings::sectionHeaderID, new HelperClasses::HeaderItemComponent (title)); | |||
Item i; | |||
i.text = title; | |||
i.isSectionHeader = true; | |||
addItem (i); | |||
} | |||
//============================================================================== | |||
@@ -1486,9 +1497,8 @@ Component* PopupMenu::createWindow (const Options& options, | |||
//============================================================================== | |||
// This invokes any command manager commands and deletes the menu window when it is dismissed | |||
class PopupMenuCompletionCallback : public ModalComponentManager::Callback | |||
struct PopupMenuCompletionCallback : public ModalComponentManager::Callback | |||
{ | |||
public: | |||
PopupMenuCompletionCallback() | |||
: managerOfChosenCommand (nullptr), | |||
prevFocused (Component::getCurrentlyFocusedComponent()), | |||
@@ -1524,7 +1534,6 @@ public: | |||
ScopedPointer<Component> component; | |||
WeakReference<Component> prevFocused, prevTopLevel; | |||
private: | |||
JUCE_DECLARE_NON_COPYABLE (PopupMenuCompletionCallback) | |||
}; | |||
@@ -1651,10 +1660,8 @@ bool PopupMenu::containsCommandItem (const int commandID) const | |||
const Item& mi = *items.getUnchecked (i); | |||
if ((mi.itemID == commandID && mi.commandManager != nullptr) | |||
|| (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID))) | |||
{ | |||
|| (mi.subMenu != nullptr && mi.subMenu->containsCommandItem (commandID))) | |||
return true; | |||
} | |||
} | |||
return false; | |||
@@ -1671,7 +1678,7 @@ bool PopupMenu::containsAnyActiveItems() const noexcept | |||
if (mi.subMenu->containsAnyActiveItems()) | |||
return true; | |||
} | |||
else if (mi.isActive) | |||
else if (mi.isEnabled) | |||
{ | |||
return true; | |||
} | |||
@@ -1708,7 +1715,7 @@ void PopupMenu::CustomComponent::triggerMenuItem() | |||
{ | |||
if (HelperClasses::MenuWindow* const pmw = mic->findParentComponentOfClass<HelperClasses::MenuWindow>()) | |||
{ | |||
pmw->dismissMenu (&mic->itemInfo); | |||
pmw->dismissMenu (&mic->item); | |||
} | |||
else | |||
{ | |||
@@ -1725,53 +1732,21 @@ void PopupMenu::CustomComponent::triggerMenuItem() | |||
} | |||
//============================================================================== | |||
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) | |||
: subMenu (nullptr), | |||
itemId (0), | |||
isSeparator (false), | |||
isTicked (false), | |||
isEnabled (false), | |||
isCustomComponent (false), | |||
isSectionHeader (false), | |||
customColour (nullptr), | |||
menu (m), | |||
index (0) | |||
{ | |||
} | |||
PopupMenu::MenuItemIterator::~MenuItemIterator() | |||
{ | |||
} | |||
PopupMenu::MenuItemIterator::MenuItemIterator (const PopupMenu& m) : menu (m), index (0) {} | |||
PopupMenu::MenuItemIterator::~MenuItemIterator() {} | |||
bool PopupMenu::MenuItemIterator::next() | |||
{ | |||
if (index >= menu.items.size()) | |||
return false; | |||
const Item* const item = menu.items.getUnchecked (index); | |||
++index; | |||
if (item->isSeparator && index >= menu.items.size()) // (avoid showing a separator at the end) | |||
return false; | |||
const Item* const item = menu.items.getUnchecked (index++); | |||
itemName = item->customComp != nullptr ? item->customComp->getName() : item->text; | |||
subMenu = item->subMenu; | |||
itemId = item->itemID; | |||
isSeparator = item->isSeparator; | |||
isTicked = item->isTicked; | |||
isEnabled = item->isActive; | |||
isSectionHeader = dynamic_cast<HelperClasses::HeaderItemComponent*> (static_cast<CustomComponent*> (item->customComp)) != nullptr; | |||
isCustomComponent = (! isSectionHeader) && item->customComp != nullptr; | |||
customColour = item->usesColour ? &(item->textColour) : nullptr; | |||
icon = item->iconDrawable; | |||
commandManager = item->commandManager; | |||
return true; | |||
return ! (item->isSeparator && index >= menu.items.size()); // (avoid showing a separator at the end) | |||
} | |||
void PopupMenu::MenuItemIterator::addItemTo (PopupMenu& targetMenu) | |||
const PopupMenu::Item& PopupMenu::MenuItemIterator::getItem() const noexcept | |||
{ | |||
targetMenu.items.add (new Item (itemId, itemName, isEnabled, isTicked, icon != nullptr ? icon->createCopy() : nullptr, | |||
customColour != nullptr ? *customColour : Colours::black, | |||
customColour != nullptr, nullptr, subMenu, commandManager)); | |||
jassert (isPositiveAndBelow (index - 1, menu.items.size())); | |||
return *menu.items.getUnchecked (index - 1); | |||
} |
@@ -104,6 +104,71 @@ public: | |||
/** Resets the menu, removing all its items. */ | |||
void clear(); | |||
/** Describes a popup menu item. */ | |||
struct JUCE_API Item | |||
{ | |||
/** Creates a null item. | |||
You'll need to set some fields after creating an Item before you | |||
can add it to a PopupMenu | |||
*/ | |||
Item() noexcept; | |||
/** Creates a copy of an item. */ | |||
Item (const Item&); | |||
/** Creates a copy of an item. */ | |||
Item& operator= (const Item&); | |||
/** The menu item's name. */ | |||
String text; | |||
/** The menu item's ID. This can not be 0 if you want the item to be triggerable! */ | |||
int itemID; | |||
/** A sub-menu, or nullptr if there isn't one. */ | |||
ScopedPointer<PopupMenu> subMenu; | |||
/** A drawable to use as an icon, or nullptr if there isn't one. */ | |||
ScopedPointer<Drawable> image; | |||
/** A custom component for the item to display, or nullptr if there isn't one. */ | |||
ReferenceCountedObjectPtr<CustomComponent> customComponent; | |||
/** A command manager to use to automatically invoke the command, or nullptr if none is specified. */ | |||
ApplicationCommandManager* commandManager; | |||
/** An optional string describing the shortcut key for this item. | |||
This is only used for displaying at the right-hand edge of a menu item - the | |||
menu won't attempt to actually catch or process the key. If you supply a | |||
commandManager parameter then the menu will attempt to fill-in this field | |||
automatically. | |||
*/ | |||
String shortcutKeyDescription; | |||
/** A colour to use to draw the menu text. | |||
By default this is transparent black, which means that the LookAndFeel should choose the colour. | |||
*/ | |||
Colour colour; | |||
/** True if this menu item is enabled. */ | |||
bool isEnabled; | |||
/** True if this menu item should have a tick mark next to it. */ | |||
bool isTicked; | |||
/** True if this menu item is a separator line. */ | |||
bool isSeparator; | |||
/** True if this menu item is a section header. */ | |||
bool isSectionHeader; | |||
}; | |||
/** Adds an item to the menu. | |||
You can call this method for full control over the item that is added, or use the other | |||
addItem helper methods if you want to pass arguments rather than creating an Item object. | |||
*/ | |||
void addItem (const Item& newItem); | |||
/** Appends a new text item for this menu to show. | |||
@param itemResultID the number that will be returned from the show() method | |||
@@ -274,7 +339,6 @@ public: | |||
int itemResultID = 0); | |||
/** Appends a separator to the menu, to help break it up into sections. | |||
The menu class is smart enough not to display separators at the top or bottom | |||
of the menu, and it will replace mutliple adjacent separators with a single | |||
one, so your code can be quite free and easy about adding these, and it'll | |||
@@ -283,14 +347,12 @@ public: | |||
void addSeparator(); | |||
/** Adds a non-clickable text item to the menu. | |||
This is a bold-font items which can be used as a header to separate the items | |||
into named groups. | |||
*/ | |||
void addSectionHeader (const String& title); | |||
/** Returns the number of items that the menu currently contains. | |||
(This doesn't count separators). | |||
*/ | |||
int getNumItems() const noexcept; | |||
@@ -486,21 +548,10 @@ public: | |||
*/ | |||
bool next(); | |||
/** Adds an item to the target menu which has all the properties of this item. */ | |||
void addItemTo (PopupMenu& targetMenu); | |||
//============================================================================== | |||
String itemName; | |||
const PopupMenu* subMenu; | |||
int itemId; | |||
bool isSeparator; | |||
bool isTicked; | |||
bool isEnabled; | |||
bool isCustomComponent; | |||
bool isSectionHeader; | |||
const Colour* customColour; | |||
const Drawable* icon; | |||
ApplicationCommandManager* commandManager; | |||
/** Returns a reference to the description of the current item. | |||
It is only valid to call this after next() has returned true! | |||
*/ | |||
const Item& getItem() const noexcept; | |||
private: | |||
//============================================================================== | |||
@@ -621,7 +672,6 @@ public: | |||
private: | |||
//============================================================================== | |||
JUCE_PUBLIC_IN_DLL_BUILD (class Item) | |||
JUCE_PUBLIC_IN_DLL_BUILD (struct HelperClasses) | |||
friend struct HelperClasses; | |||
friend class MenuBarComponent; | |||
@@ -86,7 +86,7 @@ public: | |||
*/ | |||
void startDragging (const var& sourceDescription, | |||
Component* sourceComponent, | |||
Image dragImage = Image::null, | |||
Image dragImage = Image(), | |||
bool allowDraggingToOtherJuceWindows = false, | |||
const Point<int>* imageOffsetFromMouse = nullptr); | |||
@@ -351,7 +351,7 @@ bool juce_areThereAnyAlwaysOnTopWindows() | |||
//============================================================================== | |||
Image juce_createIconForFile (const File&) | |||
{ | |||
return Image::null; | |||
return Image(); | |||
} | |||
//============================================================================== | |||
@@ -2568,7 +2568,7 @@ private: | |||
else if (Time::getApproximateMillisecondCounter() > lastTimeImageUsed + 3000) | |||
{ | |||
stopTimer(); | |||
image = Image::null; | |||
image = Image(); | |||
} | |||
} | |||
@@ -4080,7 +4080,7 @@ void MouseCursor::showInAllWindows() const | |||
//============================================================================== | |||
Image juce_createIconForFile (const File& /* file */) | |||
{ | |||
return Image::null; | |||
return Image(); | |||
} | |||
//============================================================================== | |||
@@ -181,16 +181,17 @@ public: | |||
void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo, | |||
const int topLevelMenuId, const int topLevelIndex) | |||
{ | |||
NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf ("<end>", false, true)); | |||
const PopupMenu::Item& i = iter.getItem(); | |||
NSString* text = juceStringToNS (i.text); | |||
if (text == nil) | |||
text = nsEmptyString(); | |||
if (iter.isSeparator) | |||
if (i.isSeparator) | |||
{ | |||
[menuToAddTo addItem: [NSMenuItem separatorItem]]; | |||
} | |||
else if (iter.isSectionHeader) | |||
else if (i.isSectionHeader) | |||
{ | |||
NSMenuItem* item = [menuToAddTo addItemWithTitle: text | |||
action: nil | |||
@@ -198,9 +199,9 @@ public: | |||
[item setEnabled: false]; | |||
} | |||
else if (iter.subMenu != nullptr) | |||
else if (i.subMenu != nullptr) | |||
{ | |||
if (iter.itemName == recentItemsMenuName) | |||
if (i.text == recentItemsMenuName) | |||
{ | |||
if (recent == nullptr) | |||
recent = new RecentFilesMenuItem(); | |||
@@ -219,10 +220,10 @@ public: | |||
action: nil | |||
keyEquivalent: nsEmptyString()]; | |||
[item setTag: iter.itemId]; | |||
[item setEnabled: iter.isEnabled]; | |||
[item setTag: i.itemID]; | |||
[item setEnabled: i.isEnabled]; | |||
NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex, false); | |||
NSMenu* sub = createMenu (*i.subMenu, i.text, topLevelMenuId, topLevelIndex, false); | |||
[menuToAddTo setSubmenu: sub forItem: item]; | |||
[sub release]; | |||
} | |||
@@ -232,19 +233,19 @@ public: | |||
action: @selector (menuItemInvoked:) | |||
keyEquivalent: nsEmptyString()]; | |||
[item setTag: iter.itemId]; | |||
[item setEnabled: iter.isEnabled]; | |||
[item setState: iter.isTicked ? NSOnState : NSOffState]; | |||
[item setTag: i.itemID]; | |||
[item setEnabled: i.isEnabled]; | |||
[item setState: i.isTicked ? NSOnState : NSOffState]; | |||
[item setTarget: (id) callback]; | |||
NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) iter.commandManager]]; | |||
NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_uint) (void*) i.commandManager]]; | |||
[info addObject: [NSNumber numberWithInt: topLevelIndex]]; | |||
[item setRepresentedObject: info]; | |||
if (iter.commandManager != nullptr) | |||
if (i.commandManager != nullptr) | |||
{ | |||
const Array<KeyPress> keyPresses (iter.commandManager->getKeyMappings() | |||
->getKeyPressesAssignedToCommand (iter.itemId)); | |||
const Array<KeyPress> keyPresses (i.commandManager->getKeyMappings() | |||
->getKeyPressesAssignedToCommand (i.itemID)); | |||
if (keyPresses.size() > 0) | |||
{ | |||
@@ -69,7 +69,6 @@ public: | |||
view (nil), | |||
isSharedWindow (viewToAttachTo != nil), | |||
fullScreen (false), | |||
insideDrawRect (false), | |||
#if USE_COREGRAPHICS_RENDERING | |||
usingCoreGraphics (true), | |||
#else | |||
@@ -793,6 +792,37 @@ public: | |||
displayScale = (float) screen.backingScaleFactor; | |||
#endif | |||
#if USE_COREGRAPHICS_RENDERING && JUCE_COREGRAPHICS_RENDER_WITH_MULTIPLE_PAINT_CALLS | |||
// This option invokes a separate paint call for each rectangle of the clip region. | |||
// It's a long story, but this is a basically a workaround for a CGContext not having | |||
// a way of finding whether a rectangle falls within its clip region | |||
if (usingCoreGraphics) | |||
{ | |||
const NSRect* rects = nullptr; | |||
NSInteger numRects = 0; | |||
[view getRectsBeingDrawn: &rects count: &numRects]; | |||
if (numRects > 1) | |||
{ | |||
for (int i = 0; i < numRects; ++i) | |||
{ | |||
NSRect rect = rects[i]; | |||
CGContextSaveGState (cg); | |||
CGContextClipToRect (cg, CGRectMake (rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); | |||
drawRect (cg, rect, displayScale); | |||
CGContextRestoreGState (cg); | |||
} | |||
return; | |||
} | |||
} | |||
#endif | |||
drawRect (cg, r, displayScale); | |||
} | |||
void drawRect (CGContextRef cg, NSRect r, float displayScale) | |||
{ | |||
#if USE_COREGRAPHICS_RENDERING | |||
if (usingCoreGraphics) | |||
{ | |||
@@ -872,9 +902,7 @@ public: | |||
void invokePaint (LowLevelGraphicsContext& context) | |||
{ | |||
lastRepaintTime = Time::getCurrentTime(); | |||
insideDrawRect = true; | |||
handlePaint (context); | |||
insideDrawRect = false; | |||
} | |||
void performAnyPendingRepaintsNow() override | |||
@@ -1283,7 +1311,7 @@ public: | |||
//============================================================================== | |||
NSWindow* window; | |||
NSView* view; | |||
bool isSharedWindow, fullScreen, insideDrawRect; | |||
bool isSharedWindow, fullScreen; | |||
bool usingCoreGraphics, isZooming, textWasInserted; | |||
String stringBeingComposed; | |||
NSNotificationCenter* notificationCenter; | |||
@@ -531,7 +531,7 @@ namespace IconConverters | |||
} | |||
} | |||
return Image::null; | |||
return Image(); | |||
} | |||
HICON createHICONFromImage (const Image& image, const BOOL isIcon, int hotspotX, int hotspotY) | |||
@@ -1186,7 +1186,7 @@ private: | |||
void timerCallback() override | |||
{ | |||
stopTimer(); | |||
image = Image::null; | |||
image = Image(); | |||
} | |||
private: | |||
@@ -70,7 +70,7 @@ public: | |||
if (DragAndDropContainer* const dnd = DragAndDropContainer::findParentDragContainerFor (this)) | |||
{ | |||
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image::null, true); | |||
dnd->startDragging (Toolbar::toolbarDragDescriptor, getParentComponent(), Image(), true); | |||
if (ToolbarItemComponent* const tc = getToolbarItemComponent()) | |||
{ | |||
@@ -230,7 +230,7 @@ void CallOutBox::updatePosition (const Rectangle<int>& newAreaToPointTo, const R | |||
void CallOutBox::refreshPath() | |||
{ | |||
repaint(); | |||
background = Image::null; | |||
background = Image(); | |||
outline.clear(); | |||
const float gap = 4.5f; | |||
@@ -125,7 +125,7 @@ public: | |||
if (lastHue != h) | |||
{ | |||
lastHue = h; | |||
colours = Image::null; | |||
colours = Image(); | |||
repaint(); | |||
} | |||
@@ -134,7 +134,7 @@ public: | |||
void resized() override | |||
{ | |||
colours = Image::null; | |||
colours = Image(); | |||
updateMarker(); | |||
} | |||
@@ -176,13 +176,16 @@ public: | |||
static void keyChosen (int result, ChangeKeyButton* button) | |||
{ | |||
if (result != 0 && button != nullptr && button->currentKeyEntryWindow != nullptr) | |||
if (button != nullptr && button->currentKeyEntryWindow != nullptr) | |||
{ | |||
button->currentKeyEntryWindow->setVisible (false); | |||
button->setNewKey (button->currentKeyEntryWindow->lastPress, false); | |||
} | |||
if (result != 0) | |||
{ | |||
button->currentKeyEntryWindow->setVisible (false); | |||
button->setNewKey (button->currentKeyEntryWindow->lastPress, false); | |||
} | |||
button->currentKeyEntryWindow = nullptr; | |||
button->currentKeyEntryWindow = nullptr; | |||
} | |||
} | |||
void assignNewKey() | |||