| @@ -24,8 +24,9 @@ | |||
| namespace FloatVectorHelpers | |||
| { | |||
| #define JUCE_INCREMENT_SRC_DEST dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_SRC_DEST dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_SRC1_SRC2_DEST dest += (16 / sizeof (*dest)); src1 += (16 / sizeof (*dest)); src2 += (16 / sizeof (*dest)); | |||
| #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| static bool sse2Present = false; | |||
| @@ -122,6 +123,17 @@ namespace FloatVectorHelpers | |||
| } \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| { \ | |||
| Mode::ParallelType (&loadSrc1) (const Mode::Type* v) = FloatVectorHelpers::isAligned (src1) ? Mode::loadA : Mode::loadU; \ | |||
| Mode::ParallelType (&loadSrc2) (const Mode::Type* v) = FloatVectorHelpers::isAligned (src2) ? Mode::loadA : Mode::loadU; \ | |||
| void (&storeDst) (Mode::Type* dest, Mode::ParallelType a) = FloatVectorHelpers::isAligned (dest) ? Mode::storeA : Mode::storeU; \ | |||
| JUCE_VEC_LOOP_TWO_SOURCES (vecOp, loadSrc1, loadSrc2, storeDst, locals, increment); \ | |||
| } \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| //============================================================================== | |||
| #elif JUCE_USE_ARM_NEON | |||
| @@ -193,6 +205,12 @@ namespace FloatVectorHelpers | |||
| JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| JUCE_BEGIN_VEC_OP \ | |||
| setupOp \ | |||
| JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ | |||
| JUCE_FINISH_VEC_OP (normalOp) | |||
| //============================================================================== | |||
| #else | |||
| #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ | |||
| @@ -201,6 +219,8 @@ namespace FloatVectorHelpers | |||
| #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ | |||
| for (int i = 0; i < num; ++i) normalOp; | |||
| #endif | |||
| //============================================================================== | |||
| @@ -212,10 +232,19 @@ namespace FloatVectorHelpers | |||
| increment; \ | |||
| } | |||
| #define JUCE_VEC_LOOP_TWO_SOURCES(vecOp, src1Load, src2Load, dstStore, locals, increment) \ | |||
| for (int i = 0; i < numLongOps; ++i) \ | |||
| { \ | |||
| locals (src1Load, src2Load); \ | |||
| dstStore (dest, vecOp); \ | |||
| increment; \ | |||
| } | |||
| #define JUCE_LOAD_NONE(srcLoad, dstLoad) | |||
| #define JUCE_LOAD_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest); | |||
| #define JUCE_LOAD_SRC(srcLoad, dstLoad) const Mode::ParallelType s = srcLoad (src); | |||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | |||
| #define JUCE_LOAD_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest); | |||
| #define JUCE_LOAD_SRC(srcLoad, dstLoad) const Mode::ParallelType s = srcLoad (src); | |||
| #define JUCE_LOAD_SRC1_SRC2(src1Load, src2Load) const Mode::ParallelType s1 = src1Load (src1), s2 = src2Load (src2); | |||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | |||
| @@ -443,6 +472,28 @@ 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 | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsadd (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, | |||
| const Mode::ParallelType am = Mode::load1 (amount);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsaddD (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, | |||
| const Mode::ParallelType am = Mode::load1 (amount);) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| @@ -461,6 +512,24 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vadd (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vaddD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| @@ -479,6 +548,24 @@ void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsub (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsubD (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), | |||
| @@ -511,6 +598,24 @@ void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src1, const float* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vmul (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src1, const double* src2, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vmulD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplier, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| @@ -531,6 +636,20 @@ void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, double multipl | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, float multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, double multiplier, int num) noexcept | |||
| { | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| const Mode::ParallelType mult = Mode::load1 (multiplier);) | |||
| } | |||
| void FloatVectorOperations::negate (float* dest, const float* src, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| @@ -65,18 +65,36 @@ public: | |||
| /** Adds a fixed value to the destination values. */ | |||
| 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; | |||
| /** 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; | |||
| /** Adds the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
| /** Adds the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; | |||
| /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept; | |||
| /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; | |||
| /** Subtracts the source values from the destination values. */ | |||
| static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; | |||
| /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept; | |||
| /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) noexcept; | |||
| /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ | |||
| static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; | |||
| @@ -89,12 +107,24 @@ public: | |||
| /** Multiplies the destination values by the source values. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; | |||
| /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept; | |||
| /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, int numValues) noexcept; | |||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; | |||
| /** Multiplies each of the destination values by a fixed multiplier. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; | |||
| /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept; | |||
| /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ | |||
| static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept; | |||
| /** Copies a source vector to a destination, negating each value. */ | |||
| static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; | |||
| @@ -57,7 +57,9 @@ | |||
| #endif | |||
| #if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #include <Accelerate/Accelerate.h> | |||
| #undef Point | |||
| #else | |||
| #undef JUCE_USE_VDSP_FRAMEWORK | |||
| #endif | |||
| @@ -186,9 +186,10 @@ public: | |||
| /** Retrieves a copy of the next event from the buffer. | |||
| @param result on return, this will be the message (the MidiMessage's timestamp | |||
| is not set) | |||
| @param samplePosition on return, this will be the position of the event | |||
| @param result on return, this will be the message. The MidiMessage's timestamp | |||
| is set to the same value as samplePosition. | |||
| @param samplePosition on return, this will be the position of the event, as a | |||
| sample index in the buffer | |||
| @returns true if an event was found, or false if the iterator has reached | |||
| the end of the buffer | |||
| */ | |||
| @@ -203,7 +204,8 @@ public: | |||
| temporarily until the MidiBuffer is altered. | |||
| @param numBytesOfMidiData on return, this is the number of bytes of data used by the | |||
| midi message | |||
| @param samplePosition on return, this will be the position of the event | |||
| @param samplePosition on return, this will be the position of the event, as a | |||
| sample index in the buffer | |||
| @returns true if an event was found, or false if the iterator has reached | |||
| the end of the buffer | |||
| */ | |||
| @@ -661,7 +661,7 @@ String MidiMessage::getTextFromTextMetaEvent() const | |||
| MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||
| { | |||
| jassert (type > 0 && type < 16) | |||
| jassert (type > 0 && type < 16); | |||
| MidiMessage result; | |||
| @@ -230,7 +230,8 @@ void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double | |||
| void ResamplingAudioSource::resetFilters() | |||
| { | |||
| filterStates.clear ((size_t) numChannels); | |||
| if (filterStates != nullptr) | |||
| filterStates.clear ((size_t) numChannels); | |||
| } | |||
| void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) | |||
| @@ -163,10 +163,7 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
| : numSamples; | |||
| if (numThisTime > 0) | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| voices.getUnchecked (i)->renderNextBlock (outputBuffer, startSample, numThisTime); | |||
| } | |||
| renderVoices (outputBuffer, startSample, numThisTime); | |||
| if (useEvent) | |||
| handleMidiEvent (m); | |||
| @@ -176,6 +173,12 @@ void Synthesiser::renderNextBlock (AudioSampleBuffer& outputBuffer, const MidiBu | |||
| } | |||
| } | |||
| void Synthesiser::renderVoices (AudioSampleBuffer& buffer, int startSample, int numSamples) | |||
| { | |||
| for (int i = voices.size(); --i >= 0;) | |||
| voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples); | |||
| } | |||
| void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||
| { | |||
| if (m.isNoteOn()) | |||
| @@ -184,7 +187,7 @@ void Synthesiser::handleMidiEvent (const MidiMessage& m) | |||
| } | |||
| else if (m.isNoteOff()) | |||
| { | |||
| noteOff (m.getChannel(), m.getNoteNumber(), true); | |||
| noteOff (m.getChannel(), m.getNoteNumber(), m.getFloatVelocity(), true); | |||
| } | |||
| else if (m.isAllNotesOff() || m.isAllSoundOff()) | |||
| { | |||
| @@ -230,10 +233,10 @@ void Synthesiser::noteOn (const int midiChannel, | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber | |||
| && voice->isPlayingChannel (midiChannel)) | |||
| stopVoice (voice, true); | |||
| stopVoice (voice, 1.0f, true); | |||
| } | |||
| startVoice (findFreeVoice (sound, shouldStealNotes), | |||
| startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), | |||
| sound, midiChannel, midiNoteNumber, velocity); | |||
| } | |||
| } | |||
| @@ -248,7 +251,7 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
| if (voice != nullptr && sound != nullptr) | |||
| { | |||
| if (voice->currentlyPlayingSound != nullptr) | |||
| voice->stopNote (false); | |||
| voice->stopNote (0.0f, false); | |||
| voice->startNote (midiNoteNumber, velocity, sound, | |||
| lastPitchWheelValues [midiChannel - 1]); | |||
| @@ -261,11 +264,11 @@ void Synthesiser::startVoice (SynthesiserVoice* const voice, | |||
| } | |||
| } | |||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||
| void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff) | |||
| { | |||
| jassert (voice != nullptr); | |||
| voice->stopNote (allowTailOff); | |||
| voice->stopNote (velocity, allowTailOff); | |||
| // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! | |||
| jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == 0)); | |||
| @@ -273,6 +276,7 @@ void Synthesiser::stopVoice (SynthesiserVoice* voice, const bool allowTailOff) | |||
| void Synthesiser::noteOff (const int midiChannel, | |||
| const int midiNoteNumber, | |||
| const float velocity, | |||
| const bool allowTailOff) | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -291,7 +295,7 @@ void Synthesiser::noteOff (const int midiChannel, | |||
| voice->keyIsDown = false; | |||
| if (! (sustainPedalsDown [midiChannel] || voice->sostenutoPedalDown)) | |||
| stopVoice (voice, allowTailOff); | |||
| stopVoice (voice, velocity, allowTailOff); | |||
| } | |||
| } | |||
| } | |||
| @@ -307,7 +311,7 @@ void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) | |||
| voice->stopNote (allowTailOff); | |||
| voice->stopNote (1.0f, allowTailOff); | |||
| } | |||
| sustainPedalsDown.clear(); | |||
| @@ -379,7 +383,7 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->isPlayingChannel (midiChannel) && ! voice->keyIsDown) | |||
| stopVoice (voice, true); | |||
| stopVoice (voice, 1.0f, true); | |||
| } | |||
| sustainPedalsDown.clearBit (midiChannel); | |||
| @@ -400,7 +404,7 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
| if (isDown) | |||
| voice->sostenutoPedalDown = true; | |||
| else if (voice->sostenutoPedalDown) | |||
| stopVoice (voice, true); | |||
| stopVoice (voice, 1.0f, true); | |||
| } | |||
| } | |||
| } | |||
| @@ -412,7 +416,9 @@ void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
| } | |||
| //============================================================================== | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, const bool stealIfNoneAvailable) const | |||
| SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, | |||
| int midiChannel, int midiNoteNumber, | |||
| const bool stealIfNoneAvailable) const | |||
| { | |||
| const ScopedLock sl (lock); | |||
| @@ -425,25 +431,68 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, con | |||
| } | |||
| if (stealIfNoneAvailable) | |||
| return findVoiceToSteal (soundToPlay); | |||
| return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); | |||
| return nullptr; | |||
| } | |||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay) const | |||
| struct VoiceAgeSorter | |||
| { | |||
| static int compareElements (SynthesiserVoice* v1, SynthesiserVoice* v2) noexcept | |||
| { | |||
| return v1->wasStartedBefore (*v2) ? 1 : (v2->wasStartedBefore (*v1) ? -1 : 0); | |||
| } | |||
| }; | |||
| SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| int /*midiChannel*/, int midiNoteNumber) const | |||
| { | |||
| // currently this just steals the one that's been playing the longest, but could be made a bit smarter.. | |||
| SynthesiserVoice* oldest = nullptr; | |||
| SynthesiserVoice* bottom = nullptr; | |||
| SynthesiserVoice* top = nullptr; | |||
| // this is a list of voices we can steal, sorted by how long they've been running | |||
| Array<SynthesiserVoice*> usableVoices; | |||
| usableVoices.ensureStorageAllocated (voices.size()); | |||
| for (int i = 0; i < voices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = voices.getUnchecked (i); | |||
| if (voice->canPlaySound (soundToPlay) | |||
| && (oldest == nullptr || voice->wasStartedBefore (*oldest))) | |||
| oldest = voice; | |||
| if (voice->canPlaySound (soundToPlay)) | |||
| { | |||
| VoiceAgeSorter sorter; | |||
| usableVoices.addSorted (sorter, voice); | |||
| const int note = voice->getCurrentlyPlayingNote(); | |||
| if (bottom == nullptr || note < bottom->getCurrentlyPlayingNote()) | |||
| bottom = voice; | |||
| if (top == nullptr || note > top->getCurrentlyPlayingNote()) | |||
| top = voice; | |||
| } | |||
| } | |||
| jassert (bottom != nullptr && top != nullptr); | |||
| // The oldest note that's playing with the target pitch playing is ideal.. | |||
| for (int i = 0; i < usableVoices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||
| return voice; | |||
| } | |||
| // ..otherwise, look for the oldest note that isn't the top or bottom note.. | |||
| for (int i = 0; i < usableVoices.size(); ++i) | |||
| { | |||
| SynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
| if (voice != bottom && voice != top) | |||
| return voice; | |||
| } | |||
| jassert (oldest != nullptr); | |||
| return oldest; | |||
| // ..otherwise, there's only one or two voices to choose from - we'll return the top one.. | |||
| return top; | |||
| } | |||
| @@ -55,14 +55,14 @@ public: | |||
| The Synthesiser will use this information when deciding which sounds to trigger | |||
| for a given note. | |||
| */ | |||
| virtual bool appliesToNote (const int midiNoteNumber) = 0; | |||
| virtual bool appliesToNote (int midiNoteNumber) = 0; | |||
| /** Returns true if the sound should be triggered by midi events on a given channel. | |||
| The Synthesiser will use this information when deciding which sounds to trigger | |||
| for a given note. | |||
| */ | |||
| virtual bool appliesToChannel (const int midiChannel) = 0; | |||
| virtual bool appliesToChannel (int midiChannel) = 0; | |||
| /** The class is reference-counted, so this is a handy pointer class for it. */ | |||
| typedef ReferenceCountedObjectPtr<SynthesiserSound> Ptr; | |||
| @@ -127,6 +127,8 @@ public: | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. | |||
| If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all | |||
| sound immediately, and must call clearCurrentNote() to reset the state of this voice | |||
| and allow the synth to reassign it another sound. | |||
| @@ -136,7 +138,7 @@ public: | |||
| finishes playing (during the rendering callback), it must make sure that it calls | |||
| clearCurrentNote(). | |||
| */ | |||
| virtual void stopNote (bool allowTailOff) = 0; | |||
| virtual void stopNote (float velocity, bool allowTailOff) = 0; | |||
| /** Called to let the voice know that the pitch wheel has been moved. | |||
| This will be called during the rendering callback, so must be fast and thread-safe. | |||
| @@ -173,13 +175,6 @@ public: | |||
| int startSample, | |||
| int numSamples) = 0; | |||
| /** Returns true if the voice is currently playing a sound which is mapped to the given | |||
| midi channel. | |||
| If it's not currently playing, this will return false. | |||
| */ | |||
| bool isPlayingChannel (int midiChannel) const; | |||
| /** Changes the voice's reference sample rate. | |||
| The rate is set so that subclasses know the output rate and can set their pitch | |||
| @@ -188,7 +183,19 @@ public: | |||
| This method is called by the synth, and subclasses can access the current rate with | |||
| the currentSampleRate member. | |||
| */ | |||
| void setCurrentPlaybackSampleRate (double newRate); | |||
| virtual void setCurrentPlaybackSampleRate (double newRate); | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return currentSampleRate; } | |||
| /** Returns true if the voice is currently playing a sound which is mapped to the given | |||
| midi channel. | |||
| If it's not currently playing, this will return false. | |||
| */ | |||
| bool isPlayingChannel (int midiChannel) const; | |||
| /** Returns true if the key that triggered this voice is still held down. | |||
| Note that the voice may still be playing after the key was released (e.g because the | |||
| @@ -203,13 +210,6 @@ public: | |||
| bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; | |||
| protected: | |||
| //============================================================================== | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| This is available for subclasses so they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const { return currentSampleRate; } | |||
| /** Resets the state of this voice after a sound has finished playing. | |||
| The subclass must call this when it finishes playing a note and becomes available | |||
| @@ -235,6 +235,11 @@ private: | |||
| SynthesiserSound::Ptr currentlyPlayingSound; | |||
| bool keyIsDown, sostenutoPedalDown; | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| virtual int stopNote (bool) { return 0; } | |||
| #endif | |||
| JUCE_LEAK_DETECTOR (SynthesiserVoice) | |||
| }; | |||
| @@ -268,8 +273,7 @@ class JUCE_API Synthesiser | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a new synthesiser. | |||
| You'll need to add some sounds and voices before it'll make any sound.. | |||
| You'll need to add some sounds and voices before it'll make any sound. | |||
| */ | |||
| Synthesiser(); | |||
| @@ -365,6 +369,7 @@ public: | |||
| */ | |||
| virtual void noteOff (int midiChannel, | |||
| int midiNoteNumber, | |||
| float velocity, | |||
| bool allowTailOff); | |||
| /** Turns off all notes. | |||
| @@ -444,7 +449,7 @@ public: | |||
| This value is propagated to the voices so that they can use it to render the correct | |||
| pitches. | |||
| */ | |||
| void setCurrentPlaybackSampleRate (double sampleRate); | |||
| virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||
| /** Creates the next block of audio output. | |||
| @@ -463,6 +468,11 @@ public: | |||
| int startSample, | |||
| int numSamples); | |||
| /** Returns the current target sample rate at which rendering is being done. | |||
| Subclasses may need to know this so that they can pitch things correctly. | |||
| */ | |||
| double getSampleRate() const noexcept { return sampleRate; } | |||
| protected: | |||
| //============================================================================== | |||
| /** This is used to control access to the rendering callback and the note trigger methods. */ | |||
| @@ -474,21 +484,34 @@ protected: | |||
| /** The last pitch-wheel values for each midi channel. */ | |||
| int lastPitchWheelValues [16]; | |||
| /** Searches through the voices to find one that's not currently playing, and which | |||
| can play the given sound. | |||
| /** Renders the voices for the given range. | |||
| By default this just calls renderNextBlock() on each voice, but you may need | |||
| to override it to handle custom cases. | |||
| */ | |||
| virtual void renderVoices (AudioSampleBuffer& outputAudio, | |||
| int startSample, int numSamples); | |||
| /** Searches through the voices to find one that's not currently playing, and | |||
| which can play the given sound. | |||
| Returns nullptr if all voices are busy and stealing isn't enabled. | |||
| This can be overridden to implement custom voice-stealing algorithms. | |||
| To implement a custom note-stealing algorithm, you can either override this | |||
| method, or (preferably) override findVoiceToSteal(). | |||
| */ | |||
| virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, | |||
| const bool stealIfNoneAvailable) const; | |||
| int midiChannel, | |||
| int midiNoteNumber, | |||
| bool stealIfNoneAvailable) const; | |||
| /** Chooses a voice that is most suitable for being re-used. | |||
| The default method returns the one that has been playing for the longest, but | |||
| you may want to override this and do something more cunning instead. | |||
| The default method will attempt to find the oldest voice that isn't the | |||
| bottom or top note being played. If that's not suitable for your synth, | |||
| you can override this method and do something more cunning instead. | |||
| */ | |||
| virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay) const; | |||
| virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
| int midiChannel, | |||
| int midiNoteNumber) const; | |||
| /** Starts a specified voice playing a particular sound. | |||
| @@ -511,11 +534,14 @@ private: | |||
| bool shouldStealNotes; | |||
| BigInteger sustainPedalsDown; | |||
| void stopVoice (SynthesiserVoice*, bool allowTailOff); | |||
| void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff); | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // Note the new parameters for this method. | |||
| // Note the new parameters for these methods. | |||
| virtual int findFreeVoice (const bool) const { return 0; } | |||
| virtual int noteOff (int, int, int) { return 0; } | |||
| virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; } | |||
| virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; } | |||
| #endif | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) | |||
| @@ -41,7 +41,7 @@ namespace | |||
| return err; | |||
| } | |||
| #else | |||
| #define JUCE_ALSA_LOG(x) | |||
| #define JUCE_ALSA_LOG(x) {} | |||
| #define JUCE_CHECKED_RESULT(x) (x) | |||
| #endif | |||
| @@ -65,6 +65,7 @@ const char* const WavAudioFormat::acidNumerator = "acid numerator"; | |||
| const char* const WavAudioFormat::acidTempo = "acid tempo"; | |||
| const char* const WavAudioFormat::ISRC = "ISRC"; | |||
| const char* const WavAudioFormat::tracktionLoopInfo = "tracktion loop info"; | |||
| //============================================================================== | |||
| namespace WavFileHelpers | |||
| @@ -471,6 +472,46 @@ namespace WavFileHelpers | |||
| } | |||
| } | |||
| //============================================================================== | |||
| namespace ListInfoChunk | |||
| { | |||
| static bool writeValue (const StringPairArray& values, MemoryOutputStream& out, const char* paramName) | |||
| { | |||
| const String value (values.getValue (paramName, String())); | |||
| if (value.isEmpty()) | |||
| return false; | |||
| const int valueLength = (int) value.getNumBytesAsUTF8() + 1; | |||
| const int chunkLength = valueLength + (valueLength & 1); | |||
| out.writeInt (chunkName (paramName)); | |||
| out.writeInt (chunkLength); | |||
| out.write (value.toUTF8(), (size_t) valueLength); | |||
| if ((out.getDataSize() & 1) != 0) | |||
| out.writeByte (0); | |||
| return true; | |||
| } | |||
| static MemoryBlock createFrom (const StringPairArray& values) | |||
| { | |||
| static const char* params[] = { "INAM", "IART", "IPRD", "IPRT", "ISFT", | |||
| "ISRC", "IGNR", "ICMT", "ICOP", "ICRD" }; | |||
| MemoryOutputStream out; | |||
| out.writeInt (chunkName ("INFO")); | |||
| bool anyParamsDefined = false; | |||
| for (int i = 0; i < numElementsInArray (params); ++i) | |||
| if (writeValue (values, out, params[i])) | |||
| anyParamsDefined = true; | |||
| return anyParamsDefined ? out.getMemoryBlock() : MemoryBlock(); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| struct AcidChunk | |||
| { | |||
| @@ -481,6 +522,38 @@ namespace WavFileHelpers | |||
| input.read (this, (int) jmin (sizeof (*this), length)); | |||
| } | |||
| AcidChunk (const StringPairArray& values) | |||
| { | |||
| zerostruct (*this); | |||
| flags = getFlagIfPresent (values, WavAudioFormat::acidOneShot, 0x01) | |||
| | getFlagIfPresent (values, WavAudioFormat::acidRootSet, 0x02) | |||
| | getFlagIfPresent (values, WavAudioFormat::acidStretch, 0x04) | |||
| | getFlagIfPresent (values, WavAudioFormat::acidDiskBased, 0x08) | |||
| | getFlagIfPresent (values, WavAudioFormat::acidizerFlag, 0x10); | |||
| if (values[WavAudioFormat::acidRootSet].getIntValue() != 0) | |||
| rootNote = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidRootNote].getIntValue()); | |||
| numBeats = ByteOrder::swapIfBigEndian ((uint32) values[WavAudioFormat::acidBeats].getIntValue()); | |||
| meterDenominator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidDenominator].getIntValue()); | |||
| meterNumerator = ByteOrder::swapIfBigEndian ((uint16) values[WavAudioFormat::acidNumerator].getIntValue()); | |||
| if (values.containsKey (WavAudioFormat::acidTempo)) | |||
| tempo = swapFloatByteOrder (values[WavAudioFormat::acidTempo].getFloatValue()); | |||
| } | |||
| static MemoryBlock createFrom (const StringPairArray& values) | |||
| { | |||
| return AcidChunk (values).toMemoryBlock(); | |||
| } | |||
| MemoryBlock toMemoryBlock() const | |||
| { | |||
| return (flags != 0 || rootNote != 0 || numBeats != 0 || meterDenominator != 0 || meterNumerator != 0) | |||
| ? MemoryBlock (this, sizeof (*this)) : MemoryBlock(); | |||
| } | |||
| void addToMetadata (StringPairArray& values) const | |||
| { | |||
| setBoolFlag (values, WavAudioFormat::acidOneShot, 0x01); | |||
| @@ -490,30 +563,65 @@ namespace WavFileHelpers | |||
| setBoolFlag (values, WavAudioFormat::acidizerFlag, 0x10); | |||
| if (flags & 0x02) // root note set | |||
| values.set (WavAudioFormat::acidRootNote, String (rootNote)); | |||
| values.set (WavAudioFormat::acidRootNote, String (ByteOrder::swapIfBigEndian (rootNote))); | |||
| values.set (WavAudioFormat::acidBeats, String (numBeats)); | |||
| values.set (WavAudioFormat::acidDenominator, String (meterDenominator)); | |||
| values.set (WavAudioFormat::acidNumerator, String (meterNumerator)); | |||
| values.set (WavAudioFormat::acidTempo, String (tempo)); | |||
| values.set (WavAudioFormat::acidBeats, String (ByteOrder::swapIfBigEndian (numBeats))); | |||
| values.set (WavAudioFormat::acidDenominator, String (ByteOrder::swapIfBigEndian (meterDenominator))); | |||
| values.set (WavAudioFormat::acidNumerator, String (ByteOrder::swapIfBigEndian (meterNumerator))); | |||
| values.set (WavAudioFormat::acidTempo, String (swapFloatByteOrder (tempo))); | |||
| } | |||
| void setBoolFlag (StringPairArray& values, const char* name, int32 mask) const | |||
| void setBoolFlag (StringPairArray& values, const char* name, uint32 mask) const | |||
| { | |||
| values.set (name, (flags & mask) ? "1" : "0"); | |||
| values.set (name, (flags & ByteOrder::swapIfBigEndian (mask)) ? "1" : "0"); | |||
| } | |||
| static uint32 getFlagIfPresent (const StringPairArray& values, const char* name, uint32 flag) | |||
| { | |||
| return values[name].getIntValue() != 0 ? ByteOrder::swapIfBigEndian (flag) : 0; | |||
| } | |||
| static float swapFloatByteOrder (const float x) noexcept | |||
| { | |||
| #ifdef JUCE_BIG_ENDIAN | |||
| union { uint32 asInt; float asFloat; } n; | |||
| n.asFloat = x; | |||
| n.asInt = ByteOrder::swap (n.asInt); | |||
| return n.asFloat; | |||
| #else | |||
| return x; | |||
| #endif | |||
| } | |||
| int32 flags; | |||
| int16 rootNote; | |||
| int16 reserved1; | |||
| uint32 flags; | |||
| uint16 rootNote; | |||
| uint16 reserved1; | |||
| float reserved2; | |||
| int32 numBeats; | |||
| int16 meterDenominator; | |||
| int16 meterNumerator; | |||
| uint32 numBeats; | |||
| uint16 meterDenominator; | |||
| uint16 meterNumerator; | |||
| float tempo; | |||
| } JUCE_PACKED; | |||
| //============================================================================== | |||
| struct TracktionChunk | |||
| { | |||
| static MemoryBlock createFrom (const StringPairArray& values) | |||
| { | |||
| const String s = values[WavAudioFormat::tracktionLoopInfo]; | |||
| MemoryBlock data; | |||
| if (s.isNotEmpty()) | |||
| { | |||
| MemoryOutputStream os (data, false); | |||
| os.writeString (s); | |||
| } | |||
| return data; | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| namespace AXMLChunk | |||
| { | |||
| @@ -816,6 +924,12 @@ public: | |||
| { | |||
| AcidChunk (*input, length).addToMetadata (metadataValues); | |||
| } | |||
| else if (chunkType == chunkName ("Trkn")) | |||
| { | |||
| MemoryBlock tracktion; | |||
| input->readIntoMemoryBlock (tracktion, (ssize_t) length); | |||
| metadataValues.set (WavAudioFormat::tracktionLoopInfo, tracktion.toString()); | |||
| } | |||
| else if (chunkEnd <= input->getPosition()) | |||
| { | |||
| break; | |||
| @@ -913,12 +1027,15 @@ public: | |||
| // key should be removed (or set to "WAV") once this has been done | |||
| jassert (metadataValues.getValue ("MetaDataSource", "None") != "AIFF"); | |||
| bwavChunk = BWAVChunk::createFrom (metadataValues); | |||
| axmlChunk = AXMLChunk::createFrom (metadataValues); | |||
| smplChunk = SMPLChunk::createFrom (metadataValues); | |||
| instChunk = InstChunk::createFrom (metadataValues); | |||
| cueChunk = CueChunk ::createFrom (metadataValues); | |||
| listChunk = ListChunk::createFrom (metadataValues); | |||
| bwavChunk = BWAVChunk::createFrom (metadataValues); | |||
| axmlChunk = AXMLChunk::createFrom (metadataValues); | |||
| smplChunk = SMPLChunk::createFrom (metadataValues); | |||
| instChunk = InstChunk::createFrom (metadataValues); | |||
| cueChunk = CueChunk ::createFrom (metadataValues); | |||
| listChunk = ListChunk::createFrom (metadataValues); | |||
| listInfoChunk = ListInfoChunk::createFrom (metadataValues); | |||
| acidChunk = AcidChunk::createFrom (metadataValues); | |||
| trckChunk = TracktionChunk::createFrom (metadataValues); | |||
| } | |||
| headerPosition = out->getPosition(); | |||
| @@ -981,7 +1098,7 @@ public: | |||
| } | |||
| private: | |||
| MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk; | |||
| MemoryBlock tempBlock, bwavChunk, axmlChunk, smplChunk, instChunk, cueChunk, listChunk, listInfoChunk, acidChunk, trckChunk; | |||
| uint64 lengthInSamples, bytesWritten; | |||
| int64 headerPosition; | |||
| bool writeFailed; | |||
| @@ -1033,6 +1150,9 @@ private: | |||
| + chunkSize (instChunk) | |||
| + chunkSize (cueChunk) | |||
| + chunkSize (listChunk) | |||
| + chunkSize (listInfoChunk) | |||
| + chunkSize (acidChunk) | |||
| + chunkSize (trckChunk) | |||
| + (8 + 28)); // (ds64 chunk) | |||
| riffChunkSize += (riffChunkSize & 1); | |||
| @@ -1106,12 +1226,15 @@ private: | |||
| output->write (subFormat.data4, sizeof (subFormat.data4)); | |||
| } | |||
| writeChunk (bwavChunk, chunkName ("bext")); | |||
| writeChunk (axmlChunk, chunkName ("axml")); | |||
| writeChunk (smplChunk, chunkName ("smpl")); | |||
| writeChunk (instChunk, chunkName ("inst"), 7); | |||
| writeChunk (cueChunk, chunkName ("cue ")); | |||
| writeChunk (listChunk, chunkName ("LIST")); | |||
| writeChunk (bwavChunk, chunkName ("bext")); | |||
| writeChunk (axmlChunk, chunkName ("axml")); | |||
| writeChunk (smplChunk, chunkName ("smpl")); | |||
| writeChunk (instChunk, chunkName ("inst"), 7); | |||
| writeChunk (cueChunk, chunkName ("cue ")); | |||
| writeChunk (listChunk, chunkName ("LIST")); | |||
| writeChunk (listInfoChunk, chunkName ("LIST")); | |||
| writeChunk (acidChunk, chunkName ("acid")); | |||
| writeChunk (trckChunk, chunkName ("Trkn")); | |||
| writeChunkHeader (chunkName ("data"), isRF64 ? -1 : (int) (lengthInSamples * bytesPerFrame)); | |||
| @@ -135,6 +135,9 @@ public: | |||
| /** Metadata property name used when reading an ISRC code from an AXML chunk. */ | |||
| static const char* const ISRC; | |||
| /** Metadata property name used when reading a WAV file with a Tracktion chunk. */ | |||
| static const char* const tracktionLoopInfo; | |||
| //============================================================================== | |||
| Array<int> getPossibleSampleRates() override; | |||
| Array<int> getPossibleBitDepths() override; | |||
| @@ -173,102 +173,75 @@ void AudioFormatReader::read (AudioSampleBuffer* buffer, | |||
| } | |||
| } | |||
| template <typename SampleType> | |||
| static Range<SampleType> getChannelMinAndMax (SampleType* channel, int numSamples) noexcept | |||
| { | |||
| return Range<SampleType>::findMinAndMax (channel, numSamples); | |||
| } | |||
| static Range<float> getChannelMinAndMax (float* channel, int numSamples) noexcept | |||
| { | |||
| return FloatVectorOperations::findMinAndMax (channel, numSamples); | |||
| } | |||
| template <typename SampleType> | |||
| static void getStereoMinAndMax (SampleType* const* channels, const int numChannels, const int numSamples, | |||
| SampleType& lmin, SampleType& lmax, SampleType& rmin, SampleType& rmax) | |||
| { | |||
| Range<SampleType> range (getChannelMinAndMax (channels[0], numSamples)); | |||
| lmax = jmax (lmax, range.getEnd()); | |||
| lmin = jmin (lmin, range.getStart()); | |||
| if (numChannels > 1) | |||
| { | |||
| range = getChannelMinAndMax (channels[1], numSamples); | |||
| rmax = jmax (rmax, range.getEnd()); | |||
| rmin = jmin (rmin, range.getStart()); | |||
| } | |||
| else | |||
| { | |||
| rmax = lmax; | |||
| rmin = lmin; | |||
| } | |||
| } | |||
| void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, | |||
| float& lowestLeft, float& highestLeft, | |||
| float& lowestRight, float& highestRight) | |||
| Range<float>* const results, const int channelsToRead) | |||
| { | |||
| jassert (channelsToRead > 0 && channelsToRead <= (int) numChannels); | |||
| if (numSamples <= 0) | |||
| { | |||
| lowestLeft = 0; | |||
| lowestRight = 0; | |||
| highestLeft = 0; | |||
| highestRight = 0; | |||
| for (int i = 0; i < channelsToRead; ++i) | |||
| results[i] = Range<float>(); | |||
| return; | |||
| } | |||
| const int bufferSize = (int) jmin (numSamples, (int64) 4096); | |||
| AudioSampleBuffer tempSampleBuffer ((int) numChannels, bufferSize); | |||
| AudioSampleBuffer tempSampleBuffer ((int) channelsToRead, bufferSize); | |||
| float* const* const floatBuffer = tempSampleBuffer.getArrayOfWritePointers(); | |||
| int* const* intBuffer = reinterpret_cast<int* const*> (floatBuffer); | |||
| bool isFirstBlock = true; | |||
| if (usesFloatingPointData) | |||
| while (numSamples > 0) | |||
| { | |||
| float lmin = 1.0e6f; | |||
| float lmax = -lmin; | |||
| float rmin = lmin; | |||
| float rmax = lmax; | |||
| const int numToDo = (int) jmin (numSamples, (int64) bufferSize); | |||
| if (! read (intBuffer, channelsToRead, startSampleInFile, numToDo, false)) | |||
| break; | |||
| while (numSamples > 0) | |||
| for (int i = 0; i < channelsToRead; ++i) | |||
| { | |||
| const int numToDo = (int) jmin (numSamples, (int64) bufferSize); | |||
| if (! read (intBuffer, 2, startSampleInFile, numToDo, false)) | |||
| break; | |||
| Range<float> r; | |||
| if (usesFloatingPointData) | |||
| { | |||
| r = FloatVectorOperations::findMinAndMax (floatBuffer[i], numToDo); | |||
| } | |||
| else | |||
| { | |||
| Range<int> intRange (Range<int>::findMinAndMax (intBuffer[i], numToDo)); | |||
| numSamples -= numToDo; | |||
| startSampleInFile += numToDo; | |||
| getStereoMinAndMax (floatBuffer, (int) numChannels, numToDo, lmin, lmax, rmin, rmax); | |||
| r = Range<float> (intRange.getStart() / (float) std::numeric_limits<int>::max(), | |||
| intRange.getEnd() / (float) std::numeric_limits<int>::max()); | |||
| } | |||
| results[i] = isFirstBlock ? r : results[i].getUnionWith (r); | |||
| } | |||
| lowestLeft = lmin; | |||
| highestLeft = lmax; | |||
| lowestRight = rmin; | |||
| highestRight = rmax; | |||
| isFirstBlock = false; | |||
| numSamples -= numToDo; | |||
| startSampleInFile += numToDo; | |||
| } | |||
| else | |||
| { | |||
| int lmax = std::numeric_limits<int>::min(); | |||
| int lmin = std::numeric_limits<int>::max(); | |||
| int rmax = std::numeric_limits<int>::min(); | |||
| int rmin = std::numeric_limits<int>::max(); | |||
| while (numSamples > 0) | |||
| { | |||
| const int numToDo = (int) jmin (numSamples, (int64) bufferSize); | |||
| if (! read (intBuffer, 2, startSampleInFile, numToDo, false)) | |||
| break; | |||
| } | |||
| numSamples -= numToDo; | |||
| startSampleInFile += numToDo; | |||
| getStereoMinAndMax (intBuffer, (int) numChannels, numToDo, lmin, lmax, rmin, rmax); | |||
| } | |||
| void AudioFormatReader::readMaxLevels (int64 startSampleInFile, int64 numSamples, | |||
| float& lowestLeft, float& highestLeft, | |||
| float& lowestRight, float& highestRight) | |||
| { | |||
| Range<float> levels[2]; | |||
| readMaxLevels (startSampleInFile, numSamples, levels, jmin (2, (int) numChannels)); | |||
| lowestLeft = levels[0].getStart(); | |||
| highestLeft = levels[0].getEnd(); | |||
| lowestLeft = lmin / (float) std::numeric_limits<int>::max(); | |||
| highestLeft = lmax / (float) std::numeric_limits<int>::max(); | |||
| lowestRight = rmin / (float) std::numeric_limits<int>::max(); | |||
| highestRight = rmax / (float) std::numeric_limits<int>::max(); | |||
| if (numChannels > 1) | |||
| { | |||
| lowestRight = levels[1].getStart(); | |||
| highestRight = levels[1].getEnd(); | |||
| } | |||
| else | |||
| { | |||
| lowestRight = lowestLeft; | |||
| highestRight = highestLeft; | |||
| } | |||
| } | |||
| @@ -121,6 +121,25 @@ public: | |||
| bool useReaderLeftChan, | |||
| bool useReaderRightChan); | |||
| /** Finds the highest and lowest sample levels from a section of the audio stream. | |||
| This will read a block of samples from the stream, and measure the | |||
| highest and lowest sample levels from the channels in that section, returning | |||
| these as normalised floating-point levels. | |||
| @param startSample the offset into the audio stream to start reading from. It's | |||
| ok for this to be beyond the start or end of the stream. | |||
| @param numSamples how many samples to read | |||
| @param results this array will be filled with Range values for each channel. | |||
| The array must contain numChannels elements. | |||
| @param numChannelsToRead the number of channels of data to scan. This must be | |||
| more than zero, but not more than the total number of channels | |||
| that the reader contains | |||
| @see read | |||
| */ | |||
| virtual void readMaxLevels (int64 startSample, int64 numSamples, | |||
| Range<float>* results, int numChannelsToRead); | |||
| /** Finds the highest and lowest sample levels from a section of the audio stream. | |||
| This will read a block of samples from the stream, and measure the | |||
| @@ -138,12 +157,9 @@ public: | |||
| channel (if there is one) | |||
| @see read | |||
| */ | |||
| virtual void readMaxLevels (int64 startSample, | |||
| int64 numSamples, | |||
| float& lowestLeft, | |||
| float& highestLeft, | |||
| float& lowestRight, | |||
| float& highestRight); | |||
| virtual void readMaxLevels (int64 startSample, int64 numSamples, | |||
| float& lowestLeft, float& highestLeft, | |||
| float& lowestRight, float& highestRight); | |||
| /** Scans the source looking for a sample whose magnitude is in a specified range. | |||
| @@ -59,12 +59,12 @@ SamplerSound::~SamplerSound() | |||
| { | |||
| } | |||
| bool SamplerSound::appliesToNote (const int midiNoteNumber) | |||
| bool SamplerSound::appliesToNote (int midiNoteNumber) | |||
| { | |||
| return midiNotes [midiNoteNumber]; | |||
| } | |||
| bool SamplerSound::appliesToChannel (const int /*midiChannel*/) | |||
| bool SamplerSound::appliesToChannel (int /*midiChannel*/) | |||
| { | |||
| return true; | |||
| } | |||
| @@ -127,7 +127,7 @@ void SamplerVoice::startNote (const int midiNoteNumber, | |||
| } | |||
| } | |||
| void SamplerVoice::stopNote (const bool allowTailOff) | |||
| void SamplerVoice::stopNote (float /*velocity*/, bool allowTailOff) | |||
| { | |||
| if (allowTailOff) | |||
| { | |||
| @@ -197,7 +197,7 @@ void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSa | |||
| if (attackReleaseLevel <= 0.0f) | |||
| { | |||
| stopNote (false); | |||
| stopNote (0.0f, false); | |||
| break; | |||
| } | |||
| } | |||
| @@ -216,7 +216,7 @@ void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSa | |||
| if (sourceSamplePosition > playingSound->length) | |||
| { | |||
| stopNote (false); | |||
| stopNote (0.0f, false); | |||
| break; | |||
| } | |||
| } | |||
| @@ -82,8 +82,8 @@ public: | |||
| //============================================================================== | |||
| bool appliesToNote (const int midiNoteNumber) override; | |||
| bool appliesToChannel (const int midiChannel) override; | |||
| bool appliesToNote (int midiNoteNumber) override; | |||
| bool appliesToChannel (int midiChannel) override; | |||
| private: | |||
| @@ -124,7 +124,7 @@ public: | |||
| bool canPlaySound (SynthesiserSound*) override; | |||
| void startNote (int midiNoteNumber, float velocity, SynthesiserSound*, int pitchWheel) override; | |||
| void stopNote (bool allowTailOff) override; | |||
| void stopNote (float velocity, bool allowTailOff) override; | |||
| void pitchWheelMoved (int newValue); | |||
| void controllerMoved (int controllerNumber, int newValue) override; | |||
| @@ -345,11 +345,21 @@ public: | |||
| refreshParameterList(); | |||
| updateNumChannels(); | |||
| producesMidiMessages = canProduceMidiOutput(); | |||
| setPluginCallbacks(); | |||
| setPlayConfigDetails (numInputBusChannels * numInputBusses, | |||
| numOutputBusChannels * numOutputBusses, | |||
| rate, blockSize); | |||
| setLatencySamples (0); | |||
| if (parameters.size() == 0) | |||
| { | |||
| // some plugins crash if initialiseAudioUnit() is called too soon (sigh..), so we'll | |||
| // only call it here if it seems like they it's one of the awkward plugins that can | |||
| // only create their parameters after it has been initialised. | |||
| initialiseAudioUnit(); | |||
| refreshParameterList(); | |||
| } | |||
| setPluginCallbacks(); | |||
| } | |||
| //============================================================================== | |||
| @@ -26,7 +26,7 @@ | |||
| } // (juce namespace) | |||
| #include "ladspa.h" | |||
| #include <ladspa.h> | |||
| namespace juce | |||
| { | |||
| @@ -2112,7 +2112,11 @@ private: | |||
| MemoryBlock mem; | |||
| if (mem.fromBase64Encoding (state->getAllSubText())) | |||
| stream = new Steinberg::MemoryStream (mem.getData(), (TSize) mem.getSize()); | |||
| { | |||
| stream = new Steinberg::MemoryStream(); | |||
| stream->setSize ((TSize) mem.getSize()); | |||
| mem.copyTo (stream->getData(), 0, mem.getSize()); | |||
| } | |||
| } | |||
| return stream; | |||
| @@ -76,6 +76,7 @@ class AudioProcessor; | |||
| #include "processors/juce_AudioPlayHead.h" | |||
| #include "processors/juce_AudioProcessorEditor.h" | |||
| #include "processors/juce_AudioProcessorListener.h" | |||
| #include "processors/juce_AudioProcessorParameter.h" | |||
| #include "processors/juce_AudioProcessor.h" | |||
| #include "processors/juce_PluginDescription.h" | |||
| #include "processors/juce_AudioPluginInstance.h" | |||
| @@ -120,19 +120,6 @@ void AudioProcessor::setParameterNotifyingHost (const int parameterIndex, | |||
| sendParamChangeMessageToListeners (parameterIndex, newValue); | |||
| } | |||
| String AudioProcessor::getParameterName (int parameterIndex, int maximumStringLength) | |||
| { | |||
| return getParameterName (parameterIndex).substring (0, maximumStringLength); | |||
| } | |||
| String AudioProcessor::getParameterText (int parameterIndex, int maximumStringLength) | |||
| { | |||
| return getParameterText (parameterIndex).substring (0, maximumStringLength); | |||
| } | |||
| int AudioProcessor::getParameterNumSteps (int /*parameterIndex*/) { return 0x7fffffff; } | |||
| float AudioProcessor::getParameterDefaultValue (int /*parameterIndex*/) { return 0.0f; } | |||
| AudioProcessorListener* AudioProcessor::getListenerLocked (const int index) const noexcept | |||
| { | |||
| const ScopedLock sl (listenerLock); | |||
| @@ -203,10 +190,129 @@ void AudioProcessor::updateHostDisplay() | |||
| l->audioProcessorChanged (this); | |||
| } | |||
| String AudioProcessor::getParameterLabel (int) const { return String(); } | |||
| bool AudioProcessor::isParameterOrientationInverted (int) const { return false; } | |||
| bool AudioProcessor::isParameterAutomatable (int) const { return true; } | |||
| bool AudioProcessor::isMetaParameter (int) const { return false; } | |||
| const OwnedArray<AudioProcessorParameter>& AudioProcessor::getParameters() const noexcept | |||
| { | |||
| return managedParameters; | |||
| } | |||
| int AudioProcessor::getNumParameters() | |||
| { | |||
| return managedParameters.size(); | |||
| } | |||
| float AudioProcessor::getParameter (int index) | |||
| { | |||
| if (AudioProcessorParameter* p = getParamChecked (index)) | |||
| return p->getValue(); | |||
| return 0; | |||
| } | |||
| void AudioProcessor::setParameter (int index, float newValue) | |||
| { | |||
| if (AudioProcessorParameter* p = getParamChecked (index)) | |||
| p->setValue (newValue); | |||
| } | |||
| float AudioProcessor::getParameterDefaultValue (int index) | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->getDefaultValue(); | |||
| return 0; | |||
| } | |||
| const String AudioProcessor::getParameterName (int index) | |||
| { | |||
| if (AudioProcessorParameter* p = getParamChecked (index)) | |||
| return p->getName (512); | |||
| return String(); | |||
| } | |||
| String AudioProcessor::getParameterName (int index, int maximumStringLength) | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->getName (maximumStringLength); | |||
| return getParameterName (index).substring (0, maximumStringLength); | |||
| } | |||
| const String AudioProcessor::getParameterText (int index) | |||
| { | |||
| return getParameterText (index, 1024); | |||
| } | |||
| String AudioProcessor::getParameterText (int index, int maximumStringLength) | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->getText (p->getValue(), maximumStringLength); | |||
| return getParameterText (index).substring (0, maximumStringLength); | |||
| } | |||
| int AudioProcessor::getParameterNumSteps (int index) | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->getNumSteps(); | |||
| return AudioProcessor::getDefaultNumParameterSteps(); | |||
| } | |||
| int AudioProcessor::getDefaultNumParameterSteps() noexcept | |||
| { | |||
| return 0x7fffffff; | |||
| } | |||
| String AudioProcessor::getParameterLabel (int index) const | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->getLabel(); | |||
| return String(); | |||
| } | |||
| bool AudioProcessor::isParameterAutomatable (int index) const | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->isAutomatable(); | |||
| return true; | |||
| } | |||
| bool AudioProcessor::isParameterOrientationInverted (int index) const | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->isOrientationInverted(); | |||
| return false; | |||
| } | |||
| bool AudioProcessor::isMetaParameter (int index) const | |||
| { | |||
| if (AudioProcessorParameter* p = managedParameters[index]) | |||
| return p->isMetaParameter(); | |||
| return false; | |||
| } | |||
| AudioProcessorParameter* AudioProcessor::getParamChecked (int index) const noexcept | |||
| { | |||
| AudioProcessorParameter* p = managedParameters[index]; | |||
| // If you hit this, then you're either trying to access parameters that are out-of-range, | |||
| // or you're not using addParameter and the managed parameter list, but have failed | |||
| // to override some essential virtual methods and implement them appropriately. | |||
| jassert (p != nullptr); | |||
| return p; | |||
| } | |||
| void AudioProcessor::addParameter (AudioProcessorParameter* p) | |||
| { | |||
| p->processor = this; | |||
| p->parameterIndex = managedParameters.size(); | |||
| managedParameters.add (p); | |||
| } | |||
| void AudioProcessor::suspendProcessing (const bool shouldBeSuspended) | |||
| { | |||
| @@ -299,6 +405,47 @@ XmlElement* AudioProcessor::getXmlFromBinary (const void* data, const int sizeIn | |||
| void AudioProcessorListener::audioProcessorParameterChangeGestureBegin (AudioProcessor*, int) {} | |||
| void AudioProcessorListener::audioProcessorParameterChangeGestureEnd (AudioProcessor*, int) {} | |||
| //============================================================================== | |||
| AudioProcessorParameter::AudioProcessorParameter() noexcept | |||
| : processor (nullptr), parameterIndex (-1) | |||
| {} | |||
| AudioProcessorParameter::~AudioProcessorParameter() {} | |||
| void AudioProcessorParameter::setValueNotifyingHost (float newValue) | |||
| { | |||
| // This method can't be used until the parameter has been attached to a processor! | |||
| jassert (processor != nullptr && parameterIndex >= 0); | |||
| return processor->setParameterNotifyingHost (parameterIndex, newValue); | |||
| } | |||
| void AudioProcessorParameter::beginChangeGesture() | |||
| { | |||
| // This method can't be used until the parameter has been attached to a processor! | |||
| jassert (processor != nullptr && parameterIndex >= 0); | |||
| processor->beginParameterChangeGesture (parameterIndex); | |||
| } | |||
| void AudioProcessorParameter::endChangeGesture() | |||
| { | |||
| // This method can't be used until the parameter has been attached to a processor! | |||
| jassert (processor != nullptr && parameterIndex >= 0); | |||
| processor->endParameterChangeGesture (parameterIndex); | |||
| } | |||
| bool AudioProcessorParameter::isOrientationInverted() const { return false; } | |||
| bool AudioProcessorParameter::isAutomatable() const { return true; } | |||
| bool AudioProcessorParameter::isMetaParameter() const { return false; } | |||
| int AudioProcessorParameter::getNumSteps() const { return AudioProcessor::getDefaultNumParameterSteps(); } | |||
| String AudioProcessorParameter::getText (float value, int /*maximumStringLength*/) const | |||
| { | |||
| return String (value, 2); | |||
| } | |||
| //============================================================================== | |||
| bool AudioPlayHead::CurrentPositionInfo::operator== (const CurrentPositionInfo& other) const noexcept | |||
| { | |||
| @@ -379,10 +379,10 @@ public: | |||
| /** This must return the correct value immediately after the object has been | |||
| created, and mustn't change the number of parameters later. | |||
| */ | |||
| virtual int getNumParameters() = 0; | |||
| virtual int getNumParameters(); | |||
| /** Returns the name of a particular parameter. */ | |||
| virtual const String getParameterName (int parameterIndex) = 0; | |||
| virtual const String getParameterName (int parameterIndex); | |||
| /** Called by the host to find out the value of one of the filter's parameters. | |||
| @@ -392,10 +392,10 @@ public: | |||
| It's also likely to be called by non-UI threads, so the code in here should | |||
| be thread-aware. | |||
| */ | |||
| virtual float getParameter (int parameterIndex) = 0; | |||
| virtual float getParameter (int parameterIndex); | |||
| /** Returns the value of a parameter as a text string. */ | |||
| virtual const String getParameterText (int parameterIndex) = 0; | |||
| virtual const String getParameterText (int parameterIndex); | |||
| /** Returns the name of a parameter as a text string with a preferred maximum length. | |||
| If you want to provide customised short versions of your parameter names that | |||
| @@ -416,12 +416,18 @@ public: | |||
| virtual String getParameterText (int parameterIndex, int maximumStringLength); | |||
| /** Returns the number of discrete steps that this parameter can represent. | |||
| The default return value if you don't implement this method is 0x7fffffff. | |||
| The default return value if you don't implement this method is | |||
| AudioProcessor::getDefaultNumParameterSteps(). | |||
| If your parameter is boolean, then you may want to make this return 2. | |||
| The value that is returned may or may not be used, depending on the host. | |||
| */ | |||
| virtual int getParameterNumSteps (int parameterIndex); | |||
| /** Returns the default number of steps for a parameter. | |||
| @see getParameterNumSteps | |||
| */ | |||
| static int getDefaultNumParameterSteps() noexcept; | |||
| /** Returns the default value for the parameter. | |||
| By default, this just returns 0. | |||
| The value that is returned may or may not be used, depending on the host. | |||
| @@ -451,7 +457,7 @@ public: | |||
| The value passed will be between 0 and 1.0. | |||
| */ | |||
| virtual void setParameter (int parameterIndex, float newValue) = 0; | |||
| virtual void setParameter (int parameterIndex, float newValue); | |||
| /** Your filter can call this when it needs to change one of its parameters. | |||
| @@ -503,6 +509,16 @@ public: | |||
| */ | |||
| void updateHostDisplay(); | |||
| //============================================================================== | |||
| /** Adds a parameter to the list. | |||
| The parameter object will be managed and deleted automatically by the list | |||
| when no longer needed. | |||
| */ | |||
| void addParameter (AudioProcessorParameter*); | |||
| /** Returns the current list of parameters. */ | |||
| const OwnedArray<AudioProcessorParameter>& getParameters() const noexcept; | |||
| //============================================================================== | |||
| /** Returns the number of preset programs the filter supports. | |||
| @@ -582,7 +598,7 @@ public: | |||
| //============================================================================== | |||
| /** LV2 specific calls, saving/restore as string. */ | |||
| virtual String getStateInformationString () { return String::empty; } | |||
| virtual void setStateInformationString (const String& data) {} | |||
| virtual void setStateInformationString (const String&) {} | |||
| //============================================================================== | |||
| /** Adds a listener that will be called when an aspect of this processor changes. */ | |||
| @@ -668,6 +684,9 @@ private: | |||
| CriticalSection callbackLock, listenerLock; | |||
| String inputSpeakerArrangement, outputSpeakerArrangement; | |||
| OwnedArray<AudioProcessorParameter> managedParameters; | |||
| AudioProcessorParameter* getParamChecked (int) const noexcept; | |||
| #if JUCE_DEBUG | |||
| BigInteger changingParams; | |||
| #endif | |||
| @@ -0,0 +1,158 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_AUDIOPROCESSORPARAMETER_H_INCLUDED | |||
| #define JUCE_AUDIOPROCESSORPARAMETER_H_INCLUDED | |||
| //============================================================================== | |||
| /** An abstract base class for parameter objects that can be added to an | |||
| AudioProcessor. | |||
| @see AudioProcessor::addParameter | |||
| */ | |||
| class JUCE_API AudioProcessorParameter | |||
| { | |||
| public: | |||
| AudioProcessorParameter() noexcept; | |||
| /** Destructor. */ | |||
| virtual ~AudioProcessorParameter(); | |||
| /** Called by the host to find out the value of this parameter. | |||
| Hosts will expect the value returned to be between 0 and 1.0. | |||
| This could be called quite frequently, so try to make your code efficient. | |||
| It's also likely to be called by non-UI threads, so the code in here should | |||
| be thread-aware. | |||
| */ | |||
| virtual float getValue() const = 0; | |||
| /** The host will call this method to change the value of one of the filter's parameters. | |||
| The host may call this at any time, including during the audio processing | |||
| callback, so the filter has to process this very fast and avoid blocking. | |||
| If you want to set the value of a parameter internally, e.g. from your | |||
| editor component, then don't call this directly - instead, use the | |||
| setValueNotifyingHost() method, which will also send a message to | |||
| the host telling it about the change. If the message isn't sent, the host | |||
| won't be able to automate your parameters properly. | |||
| The value passed will be between 0 and 1.0. | |||
| */ | |||
| virtual void setValue (float newValue) = 0; | |||
| /** Your filter can call this when it needs to change one of its parameters. | |||
| This could happen when the editor or some other internal operation changes | |||
| a parameter. This method will call the setParameter() method to change the | |||
| value, and will then send a message to the host telling it about the change. | |||
| Note that to make sure the host correctly handles automation, you should call | |||
| the beginChangeGesture() and endChangeGesture() methods to tell the host when | |||
| the user has started and stopped changing the parameter. | |||
| */ | |||
| void setValueNotifyingHost (float newValue); | |||
| /** Sends a signal to the host to tell it that the user is about to start changing this | |||
| parameter. | |||
| This allows the host to know when a parameter is actively being held by the user, and | |||
| it may use this information to help it record automation. | |||
| If you call this, it must be matched by a later call to endChangeGesture(). | |||
| */ | |||
| void beginChangeGesture(); | |||
| /** Tells the host that the user has finished changing this parameter. | |||
| This allows the host to know when a parameter is actively being held by the user, | |||
| and it may use this information to help it record automation. | |||
| A call to this method must follow a call to beginChangeGesture(). | |||
| */ | |||
| void endChangeGesture(); | |||
| /** This should return the default value for this parameter. */ | |||
| virtual float getDefaultValue() const = 0; | |||
| /** Returns the name to display for this parameter, which should be made | |||
| to fit within the given string length. | |||
| */ | |||
| virtual String getName (int maximumStringLength) const = 0; | |||
| /** Some parameters may be able to return a label string for | |||
| their units. For example "Hz" or "%". | |||
| */ | |||
| virtual String getLabel() const = 0; | |||
| /** Returns the number of discrete interval steps that this parameter's range | |||
| should be quantised into. | |||
| If you want a continuous range of values, don't override this method, and allow | |||
| the default implementation to return AudioProcessor::getDefaultNumParameterSteps(). | |||
| If your parameter is boolean, then you may want to make this return 2. | |||
| The value that is returned may or may not be used, depending on the host. | |||
| */ | |||
| virtual int getNumSteps() const; | |||
| /** Returns a textual version of the supplied parameter value. | |||
| The default implementation just returns the floating point value | |||
| as a string, but this could do anything you need for a custom type | |||
| of value. | |||
| */ | |||
| virtual String getText (float value, int /*maximumStringLength*/) const; | |||
| /** Should parse a string and return the appropriate value for it. */ | |||
| virtual float getValueForText (const String& text) const = 0; | |||
| /** This can be overridden to tell the host that this parameter operates in the | |||
| reverse direction. | |||
| (Not all plugin formats or hosts will actually use this information). | |||
| */ | |||
| virtual bool isOrientationInverted() const; | |||
| /** Returns true if the host can automate this parameter. | |||
| By default, this returns true. | |||
| */ | |||
| virtual bool isAutomatable() const; | |||
| /** Should return true if this parameter is a "meta" parameter. | |||
| A meta-parameter is a parameter that changes other params. It is used | |||
| by some hosts (e.g. AudioUnit hosts). | |||
| By default this returns false. | |||
| */ | |||
| virtual bool isMetaParameter() const; | |||
| /** Returns the index of this parameter in its parent processor's parameter list. */ | |||
| int getParameterIndex() const noexcept { return parameterIndex; } | |||
| private: | |||
| friend class AudioProcessor; | |||
| AudioProcessor* processor; | |||
| int parameterIndex; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioProcessorParameter) | |||
| }; | |||
| #endif // JUCE_AUDIOPROCESSORPARAMETER_H_INCLUDED | |||
| @@ -830,7 +830,7 @@ public: | |||
| This will use a comparator object to sort the elements into order. The object | |||
| passed must have a method of the form: | |||
| @code | |||
| int compareElements (ElementType first, ElementType second); | |||
| int compareElements (ElementType* first, ElementType* second); | |||
| @endcode | |||
| ..and this method must return: | |||
| @@ -26,32 +26,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, | |||
| const String& directoryWildcardPatterns, | |||
| const String& desc) | |||
| : FileFilter (desc.isEmpty() ? fileWildcardPatterns | |||
| : (desc + " (" + fileWildcardPatterns + ")")) | |||
| { | |||
| parse (fileWildcardPatterns, fileWildcards); | |||
| parse (directoryWildcardPatterns, directoryWildcards); | |||
| } | |||
| WildcardFileFilter::~WildcardFileFilter() | |||
| { | |||
| } | |||
| bool WildcardFileFilter::isFileSuitable (const File& file) const | |||
| { | |||
| return match (file, fileWildcards); | |||
| } | |||
| bool WildcardFileFilter::isDirectorySuitable (const File& file) const | |||
| { | |||
| return match (file, directoryWildcards); | |||
| } | |||
| //============================================================================== | |||
| void WildcardFileFilter::parse (const String& pattern, StringArray& result) | |||
| static void parseWildcard (const String& pattern, StringArray& result) | |||
| { | |||
| result.addTokens (pattern.toLowerCase(), ";,", "\"'"); | |||
| @@ -65,7 +40,7 @@ void WildcardFileFilter::parse (const String& pattern, StringArray& result) | |||
| result.set (i, "*"); | |||
| } | |||
| bool WildcardFileFilter::match (const File& file, const StringArray& wildcards) | |||
| static bool matchWildcard (const File& file, const StringArray& wildcards) | |||
| { | |||
| const String filename (file.getFileName()); | |||
| @@ -75,3 +50,27 @@ bool WildcardFileFilter::match (const File& file, const StringArray& wildcards) | |||
| return false; | |||
| } | |||
| WildcardFileFilter::WildcardFileFilter (const String& fileWildcardPatterns, | |||
| const String& directoryWildcardPatterns, | |||
| const String& desc) | |||
| : FileFilter (desc.isEmpty() ? fileWildcardPatterns | |||
| : (desc + " (" + fileWildcardPatterns + ")")) | |||
| { | |||
| parseWildcard (fileWildcardPatterns, fileWildcards); | |||
| parseWildcard (directoryWildcardPatterns, directoryWildcards); | |||
| } | |||
| WildcardFileFilter::~WildcardFileFilter() | |||
| { | |||
| } | |||
| bool WildcardFileFilter::isFileSuitable (const File& file) const | |||
| { | |||
| return matchWildcard (file, fileWildcards); | |||
| } | |||
| bool WildcardFileFilter::isDirectorySuitable (const File& file) const | |||
| { | |||
| return matchWildcard (file, directoryWildcards); | |||
| } | |||
| @@ -75,12 +75,8 @@ private: | |||
| //============================================================================== | |||
| StringArray fileWildcards, directoryWildcards; | |||
| static void parse (const String& pattern, StringArray& result); | |||
| static bool match (const File& file, const StringArray& wildcards); | |||
| JUCE_LEAK_DETECTOR (WildcardFileFilter) | |||
| }; | |||
| #endif // JUCE_WILDCARDFILEFILTER_H_INCLUDED | |||
| @@ -102,6 +102,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| static bool isNumericOrUndefined (const var& v) { return v.isInt() || v.isDouble() || v.isInt64() || v.isBool() || v.isUndefined(); } | |||
| static int64 getOctalValue (const String& s) { BigInteger b; b.parseString (s, 8); return b.toInt64(); } | |||
| static Identifier getPrototypeIdentifier() { static const Identifier i ("prototype"); return i; } | |||
| static var* getPropertyPointer (DynamicObject* o, Identifier i) { return o->getProperties().getVarPointer (i); } | |||
| //============================================================================== | |||
| struct CodeLocation | |||
| @@ -139,13 +140,13 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| { | |||
| if (DynamicObject* o = targetObject.getDynamicObject()) | |||
| { | |||
| if (var* prop = o->getProperties().getVarPointer (functionName)) | |||
| if (const var* prop = getPropertyPointer (o, functionName)) | |||
| return *prop; | |||
| for (DynamicObject* p = o->getProperty (getPrototypeIdentifier()).getDynamicObject(); p != nullptr; | |||
| p = p->getProperty (getPrototypeIdentifier()).getDynamicObject()) | |||
| { | |||
| if (var* prop = p->getProperties().getVarPointer (functionName)) | |||
| if (const var* prop = getPropertyPointer (p, functionName)) | |||
| return *prop; | |||
| } | |||
| } | |||
| @@ -168,14 +169,14 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| var* findRootClassProperty (Identifier className, Identifier propName) const | |||
| { | |||
| if (DynamicObject* cls = root->getProperty (className).getDynamicObject()) | |||
| return cls->getProperties().getVarPointer (propName); | |||
| return getPropertyPointer (cls, propName); | |||
| return nullptr; | |||
| } | |||
| var findSymbolInParentScopes (Identifier name) const | |||
| { | |||
| if (var* v = scope->getProperties().getVarPointer (name)) | |||
| if (const var* v = getPropertyPointer (scope, name)) | |||
| return *v; | |||
| return parent != nullptr ? parent->findSymbolInParentScopes (name) | |||
| @@ -184,13 +185,11 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| bool findAndInvokeMethod (Identifier function, const var::NativeFunctionArgs& args, var& result) const | |||
| { | |||
| const NamedValueSet& props = scope->getProperties(); | |||
| DynamicObject* target = args.thisObject.getDynamicObject(); | |||
| if (target == nullptr || target == scope) | |||
| { | |||
| if (const var* m = props.getVarPointer (function)) | |||
| if (const var* m = getPropertyPointer (scope, function)) | |||
| { | |||
| if (FunctionObject* fo = dynamic_cast<FunctionObject*> (m->getObject())) | |||
| { | |||
| @@ -200,6 +199,8 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| } | |||
| } | |||
| const NamedValueSet& props = scope->getProperties(); | |||
| for (int i = 0; i < props.size(); ++i) | |||
| if (DynamicObject* o = props.getValueAt (i).getDynamicObject()) | |||
| if (Scope (this, root, o).findAndInvokeMethod (function, args, result)) | |||
| @@ -353,7 +354,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| void assign (const Scope& s, const var& newValue) const override | |||
| { | |||
| if (var* v = s.scope->getProperties().getVarPointer (name)) | |||
| if (var* v = getPropertyPointer (s.scope, name)) | |||
| *v = newValue; | |||
| else | |||
| s.root->setProperty (name, newValue); | |||
| @@ -378,7 +379,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| } | |||
| if (DynamicObject* o = p.getDynamicObject()) | |||
| if (var* v = o->getProperties().getVarPointer (child)) | |||
| if (const var* v = getPropertyPointer (o, child)) | |||
| return *v; | |||
| return var::undefined(); | |||
| @@ -544,7 +545,7 @@ struct JavascriptEngine::RootObject : public DynamicObject | |||
| { | |||
| DivideOp (const CodeLocation& l, ExpPtr& a, ExpPtr& b) noexcept : BinaryOperator (l, a, b, TokenTypes::divide) {} | |||
| var getWithDoubles (double a, double b) const override { return b != 0 ? a / b : std::numeric_limits<double>::infinity(); } | |||
| var getWithInts (int64 a, int64 b) const override { return b != 0 ? var (a / b) : var (std::numeric_limits<double>::infinity()); } | |||
| var getWithInts (int64 a, int64 b) const override { return b != 0 ? var (a / (double) b) : var (std::numeric_limits<double>::infinity()); } | |||
| }; | |||
| struct ModuloOp : public BinaryOperator | |||
| @@ -1658,7 +1659,7 @@ JavascriptEngine::JavascriptEngine() : maximumExecutionTime (15.0), root (new R | |||
| JavascriptEngine::~JavascriptEngine() {} | |||
| void JavascriptEngine::prepareTimeout() const { root->timeout = Time::getCurrentTime() + maximumExecutionTime; } | |||
| void JavascriptEngine::prepareTimeout() const noexcept { root->timeout = Time::getCurrentTime() + maximumExecutionTime; } | |||
| void JavascriptEngine::registerNativeObject (Identifier name, DynamicObject* object) | |||
| { | |||
| @@ -1714,6 +1715,11 @@ var JavascriptEngine::callFunction (Identifier function, const var::NativeFuncti | |||
| return returnVal; | |||
| } | |||
| const NamedValueSet& JavascriptEngine::getRootObjectProperties() const noexcept | |||
| { | |||
| return root->getProperties(); | |||
| } | |||
| #if JUCE_MSVC | |||
| #pragma warning (pop) | |||
| #endif | |||
| @@ -96,10 +96,13 @@ public: | |||
| */ | |||
| RelativeTime maximumExecutionTime; | |||
| /** Provides access to the set of properties of the root namespace object. */ | |||
| const NamedValueSet& getRootObjectProperties() const noexcept; | |||
| private: | |||
| JUCE_PUBLIC_IN_DLL_BUILD (struct RootObject) | |||
| ReferenceCountedObjectPtr<RootObject> root; | |||
| void prepareTimeout() const; | |||
| const ReferenceCountedObjectPtr<RootObject> root; | |||
| void prepareTimeout() const noexcept; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JavascriptEngine) | |||
| }; | |||
| @@ -71,16 +71,19 @@ | |||
| #endif | |||
| #else | |||
| #if JUCE_LINUX || JUCE_ANDROID || JUCE_HAIKU | |||
| #if JUCE_LINUX || JUCE_ANDROID | |||
| #include <sys/types.h> | |||
| #include <sys/socket.h> | |||
| #if ! JUCE_HAIKU | |||
| #include <sys/errno.h> | |||
| #endif | |||
| #include <sys/errno.h> | |||
| #include <unistd.h> | |||
| #include <netinet/in.h> | |||
| #endif | |||
| #if JUCE_LINUX | |||
| #include <langinfo.h> | |||
| #include <ifaddrs.h> | |||
| #endif | |||
| #include <pwd.h> | |||
| #include <fcntl.h> | |||
| #include <netdb.h> | |||
| @@ -90,14 +93,12 @@ | |||
| #include <net/if.h> | |||
| #include <sys/ioctl.h> | |||
| #if JUCE_LINUX | |||
| #if ! JUCE_ANDROID | |||
| #include <execinfo.h> | |||
| #include <langinfo.h> | |||
| #endif | |||
| #endif | |||
| #if JUCE_MAC || JUCE_IOS | |||
| #include <execinfo.h> | |||
| #include <xlocale.h> | |||
| #include <mach/mach.h> | |||
| #endif | |||
| @@ -216,10 +217,6 @@ namespace juce | |||
| #include "native/juce_android_SystemStats.cpp" | |||
| #include "native/juce_android_Threads.cpp" | |||
| //============================================================================== | |||
| #elif JUCE_HAIKU | |||
| // TODO | |||
| #endif | |||
| #include "threads/juce_ChildProcess.cpp" | |||
| @@ -129,10 +129,6 @@ | |||
| #define JUCE_STRING_UTF_TYPE 8 | |||
| #endif | |||
| #ifndef JUCE_USE_VFORK | |||
| #define JUCE_USE_VFORK 0 | |||
| #endif | |||
| //============================================================================= | |||
| //============================================================================= | |||
| #if JUCE_MSVC | |||
| @@ -198,6 +194,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe | |||
| #include "threads/juce_ScopedLock.h" | |||
| #include "threads/juce_CriticalSection.h" | |||
| #include "maths/juce_Range.h" | |||
| #include "maths/juce_NormalisableRange.h" | |||
| #include "containers/juce_ElementComparator.h" | |||
| #include "containers/juce_ArrayAllocationBase.h" | |||
| #include "containers/juce_Array.h" | |||
| @@ -0,0 +1,169 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the juce_core module of the JUCE library. | |||
| Copyright (c) 2013 - Raw Material Software Ltd. | |||
| Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| or without fee is hereby granted, provided that the above copyright notice and this | |||
| permission notice appear in all copies. | |||
| THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD | |||
| TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN | |||
| NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL | |||
| DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER | |||
| IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |||
| CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| ------------------------------------------------------------------------------ | |||
| NOTE! This permissive ISC license applies ONLY to files within the juce_core module! | |||
| All other JUCE modules are covered by a dual GPL/commercial license, so if you are | |||
| using any other modules, be sure to check that you also comply with their license. | |||
| For more details, visit www.juce.com | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_NORMALISABLERANGE_H_INCLUDED | |||
| #define JUCE_NORMALISABLERANGE_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Represents a mapping between an arbitrary range of values and a | |||
| normalised 0->1 range. | |||
| The properties of the mapping also include an optional snapping interval | |||
| and skew-factor. | |||
| @see Range | |||
| */ | |||
| template<typename ValueType> | |||
| class NormalisableRange | |||
| { | |||
| public: | |||
| /** Creates a continuous range that performs a dummy mapping. */ | |||
| NormalisableRange() noexcept : start(), end (1), interval(), skew (static_cast<ValueType> (1)) {} | |||
| /** Creates a copy of another range. */ | |||
| NormalisableRange (const NormalisableRange& other) noexcept | |||
| : start (other.start), end (other.end), | |||
| interval (other.interval), skew (other.skew) | |||
| { | |||
| checkInvariants(); | |||
| } | |||
| /** Creates a copy of another range. */ | |||
| NormalisableRange& operator= (const NormalisableRange& other) noexcept | |||
| { | |||
| start = other.start; | |||
| end = other.end; | |||
| interval = other.interval; | |||
| skew = other.skew; | |||
| checkInvariants(); | |||
| return *this; | |||
| } | |||
| /** Creates a NormalisableRange with a given range, interval and skew factor. */ | |||
| NormalisableRange (ValueType rangeStart, | |||
| ValueType rangeEnd, | |||
| ValueType intervalValue, | |||
| ValueType skewFactor) noexcept | |||
| : start (rangeStart), end (rangeEnd), | |||
| interval (intervalValue), skew (skewFactor) | |||
| { | |||
| checkInvariants(); | |||
| } | |||
| /** Creates a NormalisableRange with a given range and interval, but a dummy skew-factor. */ | |||
| NormalisableRange (ValueType rangeStart, | |||
| ValueType rangeEnd, | |||
| ValueType intervalValue) noexcept | |||
| : start (rangeStart), end (rangeEnd), | |||
| interval (intervalValue), skew (static_cast<ValueType> (1)) | |||
| { | |||
| checkInvariants(); | |||
| } | |||
| /** Creates a NormalisableRange with a given range, continuous interval, but a dummy skew-factor. */ | |||
| NormalisableRange (ValueType rangeStart, | |||
| ValueType rangeEnd) noexcept | |||
| : start (rangeStart), end (rangeEnd), | |||
| interval(), skew (static_cast<ValueType> (1)) | |||
| { | |||
| checkInvariants(); | |||
| } | |||
| /** Uses the properties of this mapping to convert a non-normalised value to | |||
| its 0->1 representation. | |||
| */ | |||
| ValueType convertTo0to1 (ValueType v) const noexcept | |||
| { | |||
| ValueType proportion = (v - start) / (end - start); | |||
| if (skew != static_cast<ValueType> (1)) | |||
| proportion = pow (proportion, skew); | |||
| return proportion; | |||
| } | |||
| /** Uses the properties of this mapping to convert a normalised 0->1 value to | |||
| its full-range representation. | |||
| */ | |||
| ValueType convertFrom0to1 (ValueType proportion) const noexcept | |||
| { | |||
| if (skew != static_cast<ValueType> (1) && proportion > ValueType()) | |||
| proportion = exp (log (proportion) / skew); | |||
| return start + (end - start) * proportion; | |||
| } | |||
| /** Takes a non-normalised value and snaps it based on the interval property of | |||
| this NormalisedRange. */ | |||
| ValueType snapToLegalValue (ValueType v) const noexcept | |||
| { | |||
| if (interval > ValueType()) | |||
| v = start + interval * std::floor ((v - start) / interval + static_cast<ValueType> (0.5)); | |||
| if (v <= start || end <= start) | |||
| return start; | |||
| if (v >= end) | |||
| return end; | |||
| return v; | |||
| } | |||
| /** The start of the non-normalised range. */ | |||
| ValueType start; | |||
| /** The end of the non-normalised range. */ | |||
| ValueType end; | |||
| /** The snapping interval that should be used (in non-normalised value). Use 0 for a continuous range. */ | |||
| ValueType interval; | |||
| /** An optional skew factor that alters the way values are distribute across the range. | |||
| The skew factor lets you skew the mapping logarithmically so that larger or smaller | |||
| values are given a larger proportion of the avilable space. | |||
| A factor of 1.0 has no skewing effect at all. If the factor is < 1.0, the lower end | |||
| of the range will fill more of the slider's length; if the factor is > 1.0, the upper | |||
| end of the range will be expanded. | |||
| */ | |||
| ValueType skew; | |||
| private: | |||
| void checkInvariants() const | |||
| { | |||
| jassert (end > start); | |||
| jassert (interval >= ValueType()); | |||
| jassert (skew > ValueType()); | |||
| } | |||
| }; | |||
| #endif // JUCE_NORMALISABLERANGE_H_INCLUDED | |||
| @@ -88,14 +88,14 @@ MemoryBlock& MemoryBlock::operator= (const MemoryBlock& other) | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| MemoryBlock::MemoryBlock (MemoryBlock&& other) noexcept | |||
| : data (static_cast <HeapBlock<char>&&> (other.data)), | |||
| : data (static_cast<HeapBlock<char>&&> (other.data)), | |||
| size (other.size) | |||
| { | |||
| } | |||
| MemoryBlock& MemoryBlock::operator= (MemoryBlock&& other) noexcept | |||
| { | |||
| data = static_cast <HeapBlock<char>&&> (other.data); | |||
| data = static_cast<HeapBlock<char>&&> (other.data); | |||
| size = other.size; | |||
| return *this; | |||
| } | |||
| @@ -346,7 +346,7 @@ void MemoryBlock::loadFromHexString (StringRef hex) | |||
| if (c == 0) | |||
| { | |||
| setSize (static_cast <size_t> (dest - data)); | |||
| setSize (static_cast<size_t> (dest - data)); | |||
| return; | |||
| } | |||
| } | |||
| @@ -182,11 +182,11 @@ public: | |||
| /** Swaps this object with that of another ScopedPointer. | |||
| The two objects simply exchange their pointers. | |||
| */ | |||
| void swapWith (ScopedPointer <ObjectType>& other) noexcept | |||
| void swapWith (ScopedPointer<ObjectType>& other) noexcept | |||
| { | |||
| // Two ScopedPointers should never be able to refer to the same object - if | |||
| // this happens, you must have done something dodgy! | |||
| jassert (object != other.object || this == other.getAddress()); | |||
| jassert (object != other.object || this == other.getAddress() || object == nullptr); | |||
| std::swap (object, other.object); | |||
| } | |||
| @@ -231,7 +231,7 @@ private: | |||
| template <class ObjectType> | |||
| bool operator== (const ScopedPointer<ObjectType>& pointer1, ObjectType* const pointer2) noexcept | |||
| { | |||
| return static_cast <ObjectType*> (pointer1) == pointer2; | |||
| return static_cast<ObjectType*> (pointer1) == pointer2; | |||
| } | |||
| /** Compares a ScopedPointer with another pointer. | |||
| @@ -240,7 +240,7 @@ bool operator== (const ScopedPointer<ObjectType>& pointer1, ObjectType* const po | |||
| template <class ObjectType> | |||
| bool operator!= (const ScopedPointer<ObjectType>& pointer1, ObjectType* const pointer2) noexcept | |||
| { | |||
| return static_cast <ObjectType*> (pointer1) != pointer2; | |||
| return static_cast<ObjectType*> (pointer1) != pointer2; | |||
| } | |||
| //============================================================================== | |||
| @@ -212,33 +212,6 @@ | |||
| #include <dirent.h> | |||
| #include <fnmatch.h> | |||
| #include <sys/wait.h> | |||
| //============================================================================== | |||
| #elif JUCE_HAIKU | |||
| #include <sched.h> | |||
| #include <pthread.h> | |||
| #include <sys/time.h> | |||
| #include <errno.h> | |||
| #include <sys/stat.h> | |||
| #include <sys/statvfs.h> | |||
| #include <sys/wait.h> | |||
| #include <sys/mman.h> | |||
| #include <fnmatch.h> | |||
| #include <utime.h> | |||
| #include <pwd.h> | |||
| #include <fcntl.h> | |||
| #include <dlfcn.h> | |||
| #include <netdb.h> | |||
| #include <arpa/inet.h> | |||
| #include <netinet/in.h> | |||
| #include <sys/types.h> | |||
| #include <sys/ioctl.h> | |||
| #include <sys/socket.h> | |||
| #include <sys/sockio.h> | |||
| #include <net/if.h> | |||
| #include <sys/file.h> | |||
| #include <signal.h> | |||
| #include <stddef.h> | |||
| #endif | |||
| // Need to clear various moronic redefinitions made by system headers.. | |||
| @@ -31,26 +31,26 @@ void MACAddress::findAllAddresses (Array<MACAddress>& result) | |||
| const int s = socket (AF_INET, SOCK_DGRAM, 0); | |||
| if (s != -1) | |||
| { | |||
| char buf [1024]; | |||
| struct ifconf ifc; | |||
| ifc.ifc_len = sizeof (buf); | |||
| ifc.ifc_buf = buf; | |||
| ioctl (s, SIOCGIFCONF, &ifc); | |||
| struct ifaddrs* addrs = nullptr; | |||
| for (unsigned int i = 0; i < ifc.ifc_len / sizeof (struct ifreq); ++i) | |||
| if (getifaddrs (&addrs) != -1) | |||
| { | |||
| struct ifreq ifr; | |||
| strcpy (ifr.ifr_name, ifc.ifc_req[i].ifr_name); | |||
| if (ioctl (s, SIOCGIFFLAGS, &ifr) == 0 | |||
| && (ifr.ifr_flags & IFF_LOOPBACK) == 0 | |||
| && ioctl (s, SIOCGIFHWADDR, &ifr) == 0) | |||
| for (struct ifaddrs* i = addrs; i != nullptr; i = i->ifa_next) | |||
| { | |||
| MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data); | |||
| struct ifreq ifr; | |||
| strcpy (ifr.ifr_name, i->ifa_name); | |||
| ifr.ifr_addr.sa_family = AF_INET; | |||
| if (ioctl (s, SIOCGIFHWADDR, &ifr) == 0) | |||
| { | |||
| MACAddress ma ((const uint8*) ifr.ifr_hwaddr.sa_data); | |||
| if (! ma.isNull()) | |||
| result.addIfNotAlreadyThere (ma); | |||
| if (! ma.isNull()) | |||
| result.addIfNotAlreadyThere (ma); | |||
| } | |||
| } | |||
| freeifaddrs (addrs); | |||
| } | |||
| close (s); | |||
| @@ -263,7 +263,7 @@ private: | |||
| } | |||
| } | |||
| String responseHeader (readResponse (socketHandle, timeOutTime)); | |||
| String responseHeader (readResponse (timeOutTime)); | |||
| position = 0; | |||
| if (responseHeader.isNotEmpty()) | |||
| @@ -302,7 +302,7 @@ private: | |||
| } | |||
| //============================================================================== | |||
| String readResponse (const int socketHandle, const uint32 timeOutTime) | |||
| String readResponse (const uint32 timeOutTime) | |||
| { | |||
| int numConsecutiveLFs = 0; | |||
| MemoryOutputStream buffer; | |||
| @@ -338,7 +338,8 @@ private: | |||
| dest << "\r\n" << key << ' ' << value; | |||
| } | |||
| static void writeHost (MemoryOutputStream& dest, const bool isPost, const String& path, const String& host, const int port) | |||
| static void writeHost (MemoryOutputStream& dest, const bool isPost, | |||
| const String& path, const String& host, int /*port*/) | |||
| { | |||
| dest << (isPost ? "POST " : "GET ") << path << " HTTP/1.0\r\nHost: " << host; | |||
| } | |||
| @@ -142,7 +142,7 @@ void JUCE_CALLTYPE Thread::sleep (int millisecs) | |||
| void JUCE_CALLTYPE Process::terminate() | |||
| { | |||
| #if JUCE_ANDROID || JUCE_HAIKU | |||
| #if JUCE_ANDROID | |||
| _exit (EXIT_FAILURE); | |||
| #else | |||
| std::_Exit (EXIT_FAILURE); | |||
| @@ -203,10 +203,6 @@ namespace | |||
| #define JUCE_STAT stat | |||
| #endif | |||
| #if JUCE_HAIKU | |||
| #define statfs statvfs | |||
| #endif | |||
| bool juce_stat (const String& fileName, juce_statStruct& info) | |||
| { | |||
| return fileName.isNotEmpty() | |||
| @@ -227,6 +223,12 @@ namespace | |||
| return statfs (f.getFullPathName().toUTF8(), &result) == 0; | |||
| } | |||
| #if (JUCE_MAC && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5) || JUCE_IOS | |||
| static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_birthtime; } | |||
| #else | |||
| static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_ctime; } | |||
| #endif | |||
| void updateStatInfoForFile (const String& path, bool* const isDir, int64* const fileSize, | |||
| Time* const modTime, Time* const creationTime, bool* const isReadOnly) | |||
| { | |||
| @@ -236,9 +238,9 @@ namespace | |||
| const bool statOk = juce_stat (path, info); | |||
| if (isDir != nullptr) *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); | |||
| if (fileSize != nullptr) *fileSize = statOk ? info.st_size : 0; | |||
| if (modTime != nullptr) *modTime = Time (statOk ? (int64) info.st_mtime * 1000 : 0); | |||
| if (creationTime != nullptr) *creationTime = Time (statOk ? (int64) info.st_ctime * 1000 : 0); | |||
| if (fileSize != nullptr) *fileSize = statOk ? (int64) info.st_size : 0; | |||
| if (modTime != nullptr) *modTime = Time (statOk ? (int64) info.st_mtime * 1000 : 0); | |||
| if (creationTime != nullptr) *creationTime = Time (statOk ? getCreationTime (info) * 1000 : 0); | |||
| } | |||
| if (isReadOnly != nullptr) | |||
| @@ -546,11 +548,7 @@ void MemoryMappedFile::openInternal (const File& file, AccessMode mode) | |||
| if (m != MAP_FAILED) | |||
| { | |||
| address = m; | |||
| #if JUCE_HAIKU | |||
| posix_madvise (m, (size_t) range.getLength(), POSIX_MADV_SEQUENTIAL); | |||
| #else | |||
| madvise (m, (size_t) range.getLength(), MADV_SEQUENTIAL); | |||
| #endif | |||
| madvise (m, (size_t) range.getLength(), MADV_SEQUENTIAL); | |||
| } | |||
| else | |||
| { | |||
| @@ -671,11 +669,8 @@ int File::getVolumeSerialNumber() const | |||
| return result; | |||
| } | |||
| #if JUCE_HAIKU | |||
| #undef statvfs | |||
| #endif | |||
| //============================================================================== | |||
| #if ! JUCE_IOS | |||
| void juce_runSystemCommand (const String&); | |||
| void juce_runSystemCommand (const String& command) | |||
| { | |||
| @@ -696,7 +691,7 @@ String juce_getOutputFromCommand (const String& command) | |||
| tempFile.deleteFile(); | |||
| return result; | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_IOS | |||
| @@ -1012,15 +1007,15 @@ public: | |||
| int pipeHandles[2] = { 0 }; | |||
| Array<char*> argv; | |||
| for (int i = 0; i < arguments.size(); ++i) | |||
| if (arguments[i].isNotEmpty()) | |||
| argv.add (const_cast<char*> (arguments[i].toUTF8().getAddress())); | |||
| argv.add (nullptr); | |||
| if (pipe (pipeHandles) == 0) | |||
| { | |||
| Array<char*> argv; | |||
| for (int i = 0; i < arguments.size(); ++i) | |||
| if (arguments[i].isNotEmpty()) | |||
| argv.add (const_cast<char*> (arguments[i].toUTF8().getAddress())); | |||
| argv.add (nullptr); | |||
| #if JUCE_USE_VFORK | |||
| const pid_t result = vfork(); | |||
| #else | |||
| @@ -1050,6 +1045,7 @@ public: | |||
| close (pipeHandles[1]); | |||
| #endif | |||
| execvp (argv[0], argv.getRawDataPointer()); | |||
| exit (-1); | |||
| } | |||
| @@ -1120,11 +1116,6 @@ public: | |||
| return 0; | |||
| } | |||
| int getPID() const noexcept | |||
| { | |||
| return childPID; | |||
| } | |||
| int childPID; | |||
| private: | |||
| @@ -45,13 +45,14 @@ public: | |||
| address (address_), headers (headers_), postData (postData_), position (0), | |||
| finished (false), isPost (isPost_), timeOutMs (timeOutMs_) | |||
| { | |||
| createConnection (progressCallback, progressCallbackContext); | |||
| if (! isError()) | |||
| for (int maxRedirects = 10; --maxRedirects >= 0;) | |||
| { | |||
| if (responseHeaders != nullptr) | |||
| createConnection (progressCallback, progressCallbackContext); | |||
| if (! isError()) | |||
| { | |||
| DWORD bufferSizeBytes = 4096; | |||
| StringPairArray headers (false); | |||
| for (;;) | |||
| { | |||
| @@ -65,11 +66,10 @@ public: | |||
| for (int i = 0; i < headersArray.size(); ++i) | |||
| { | |||
| const String& header = headersArray[i]; | |||
| const String key (header.upToFirstOccurrenceOf (": ", false, false)); | |||
| const String key (header.upToFirstOccurrenceOf (": ", false, false)); | |||
| const String value (header.fromFirstOccurrenceOf (": ", false, false)); | |||
| const String previousValue ((*responseHeaders) [key]); | |||
| responseHeaders->set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||
| const String previousValue (headers[key]); | |||
| headers.set (key, previousValue.isEmpty() ? value : (previousValue + "," + value)); | |||
| } | |||
| break; | |||
| @@ -77,14 +77,34 @@ public: | |||
| if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |||
| break; | |||
| bufferSizeBytes += 4096; | |||
| } | |||
| } | |||
| DWORD status = 0; | |||
| DWORD statusSize = sizeof (status); | |||
| DWORD status = 0; | |||
| DWORD statusSize = sizeof (status); | |||
| if (HttpQueryInfo (request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statusSize, 0)) | |||
| { | |||
| statusCode = (int) status; | |||
| if (status == 301 || status == 302 || status == 303 || status == 307) | |||
| { | |||
| const String newLocation (headers["Location"]); | |||
| if (newLocation.isNotEmpty() && newLocation != address) | |||
| { | |||
| address = newLocation; | |||
| continue; | |||
| } | |||
| } | |||
| } | |||
| if (responseHeaders != nullptr) | |||
| responseHeaders->addArray (headers); | |||
| } | |||
| if (HttpQueryInfo (request, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &statusSize, 0)) | |||
| statusCode = (int) status; | |||
| break; | |||
| } | |||
| } | |||
| @@ -534,11 +534,6 @@ public: | |||
| return (uint32) exitCode; | |||
| } | |||
| int getPID() const noexcept | |||
| { | |||
| return 0; | |||
| } | |||
| bool ok; | |||
| private: | |||
| @@ -263,6 +263,12 @@ namespace SocketHelpers | |||
| return true; | |||
| } | |||
| static void makeReusable (int handle) noexcept | |||
| { | |||
| const int reuse = 1; | |||
| setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); | |||
| } | |||
| } | |||
| //============================================================================== | |||
| @@ -418,8 +424,9 @@ bool StreamingSocket::createListener (const int newPortNumber, const String& loc | |||
| if (handle < 0) | |||
| return false; | |||
| const int reuse = 1; | |||
| setsockopt (handle, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuse, sizeof (reuse)); | |||
| #if ! JUCE_WINDOWS // on windows, adding this option produces behaviour different to posix | |||
| SocketHelpers::makeReusable (handle); | |||
| #endif | |||
| if (bind (handle, (struct sockaddr*) &servTmpAddr, sizeof (struct sockaddr_in)) < 0 | |||
| || listen (handle, SOMAXCONN) < 0) | |||
| @@ -470,6 +477,7 @@ DatagramSocket::DatagramSocket (const int localPortNumber, const bool canBroadca | |||
| SocketHelpers::initSockets(); | |||
| handle = (int) socket (AF_INET, SOCK_DGRAM, 0); | |||
| SocketHelpers::makeReusable (handle); | |||
| bindToPort (localPortNumber); | |||
| } | |||
| @@ -179,6 +179,11 @@ String URL::toString (const bool includeGetParameters) const | |||
| return url; | |||
| } | |||
| bool URL::isEmpty() const noexcept | |||
| { | |||
| return url.isEmpty(); | |||
| } | |||
| bool URL::isWellFormed() const | |||
| { | |||
| //xxx TODO | |||
| @@ -342,7 +347,6 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
| if (! headers.endsWithChar ('\n')) | |||
| headers << "\r\n"; | |||
| #if ! JUCE_HAIKU | |||
| ScopedPointer<WebInputStream> wi (new WebInputStream (toString (! usePostCommand), | |||
| usePostCommand, headersAndPostData, | |||
| progressCallback, progressCallbackContext, | |||
| @@ -352,9 +356,6 @@ InputStream* URL::createInputStream (const bool usePostCommand, | |||
| *statusCode = wi->statusCode; | |||
| return wi->isError() ? nullptr : wi.release(); | |||
| #else | |||
| return nullptr; // TODO | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| @@ -76,6 +76,9 @@ public: | |||
| */ | |||
| String toString (bool includeGetParameters) const; | |||
| /** Returns true if the URL is an empty string. */ | |||
| bool isEmpty() const noexcept; | |||
| /** True if it seems to be valid. */ | |||
| bool isWellFormed() const; | |||
| @@ -182,7 +182,7 @@ int64 MemoryOutputStream::writeFromInputStream (InputStream& source, int64 maxNu | |||
| if (availableData > 0) | |||
| { | |||
| if (maxNumBytesToWrite > availableData) | |||
| if (maxNumBytesToWrite > availableData || maxNumBytesToWrite < 0) | |||
| maxNumBytesToWrite = availableData; | |||
| if (blockToUse != nullptr) | |||
| @@ -94,20 +94,34 @@ | |||
| #define JUCE_ANALYZER_NORETURN | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_MSVC && ! DOXYGEN | |||
| #define MACRO_WITH_FORCED_SEMICOLON(x) \ | |||
| __pragma(warning(push)) \ | |||
| __pragma(warning(disable:4127)) \ | |||
| do { x } while (false) \ | |||
| __pragma(warning(pop)) | |||
| #else | |||
| /** This is the good old C++ trick for creating a macro that forces the user to put | |||
| a semicolon after it when they use it. | |||
| */ | |||
| #define MACRO_WITH_FORCED_SEMICOLON(x) do { x } while (false) | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_DEBUG || DOXYGEN | |||
| /** Writes a string to the standard error stream. | |||
| This is only compiled in a debug build. | |||
| @see Logger::outputDebugString | |||
| */ | |||
| #define DBG(dbgtext) { juce::String tempDbgBuf; tempDbgBuf << dbgtext; juce::Logger::outputDebugString (tempDbgBuf); } | |||
| #define DBG(dbgtext) MACRO_WITH_FORCED_SEMICOLON (juce::String tempDbgBuf; tempDbgBuf << dbgtext; juce::Logger::outputDebugString (tempDbgBuf);) | |||
| //============================================================================== | |||
| /** This will always cause an assertion failure. | |||
| It is only compiled in a debug build, (unless JUCE_LOG_ASSERTIONS is enabled for your build). | |||
| @see jassert | |||
| */ | |||
| #define jassertfalse { juce_LogCurrentAssertion; if (juce::juce_isRunningUnderDebugger()) juce_breakDebugger; JUCE_ANALYZER_NORETURN } | |||
| #define jassertfalse MACRO_WITH_FORCED_SEMICOLON (juce_LogCurrentAssertion; if (juce::juce_isRunningUnderDebugger()) juce_breakDebugger; JUCE_ANALYZER_NORETURN) | |||
| //============================================================================== | |||
| /** Platform-independent assertion macro. | |||
| @@ -117,19 +131,19 @@ | |||
| correct behaviour of your program! | |||
| @see jassertfalse | |||
| */ | |||
| #define jassert(expression) { if (! (expression)) jassertfalse; } | |||
| #define jassert(expression) MACRO_WITH_FORCED_SEMICOLON (if (! (expression)) jassertfalse;) | |||
| #else | |||
| //============================================================================== | |||
| // If debugging is disabled, these dummy debug and assertion macros are used.. | |||
| #define DBG(dbgtext) | |||
| #define jassertfalse { juce_LogCurrentAssertion } | |||
| #define jassertfalse MACRO_WITH_FORCED_SEMICOLON (juce_LogCurrentAssertion) | |||
| #if JUCE_LOG_ASSERTIONS | |||
| #define jassert(expression) { if (! (expression)) jassertfalse; } | |||
| #define jassert(expression) MACRO_WITH_FORCED_SEMICOLON (if (! (expression)) jassertfalse;) | |||
| #else | |||
| #define jassert(a) {} | |||
| #define jassert(a) MACRO_WITH_FORCED_SEMICOLON ( ; ) | |||
| #endif | |||
| #endif | |||
| @@ -139,7 +153,7 @@ | |||
| namespace juce | |||
| { | |||
| template <bool b> struct JuceStaticAssert; | |||
| template <> struct JuceStaticAssert <true> { static void dummy() {} }; | |||
| template <> struct JuceStaticAssert<true> { static void dummy() {} }; | |||
| } | |||
| #endif | |||
| @@ -211,6 +225,26 @@ namespace juce | |||
| #define JUCE_STRINGIFY(item) JUCE_STRINGIFY_MACRO_HELPER (item) | |||
| //============================================================================== | |||
| #if JUCE_MSVC && ! defined (DOXYGEN) | |||
| #define JUCE_WARNING_HELPER(file, line, mess) message(file "(" JUCE_STRINGIFY (line) ") : Warning: " #mess) | |||
| #define JUCE_COMPILER_WARNING(message) __pragma(JUCE_WARNING_HELPER (__FILE__, __LINE__, message)); | |||
| #else | |||
| #ifndef DOXYGEN | |||
| #define JUCE_WARNING_HELPER(mess) message(#mess) | |||
| #endif | |||
| /** This macro allows you to emit a custom compiler warning message. | |||
| Very handy for marking bits of code as "to-do" items, or for shaming | |||
| code written by your co-workers in a way that's hard to ignore. | |||
| GCC and Clang provide the \#warning directive, but MSVC doesn't, so this macro | |||
| is a cross-compiler way to get the same functionality as \#warning. | |||
| */ | |||
| #define JUCE_COMPILER_WARNING(message) _Pragma(JUCE_STRINGIFY (JUCE_WARNING_HELPER (message))); | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_CATCH_UNHANDLED_EXCEPTIONS | |||
| @@ -317,6 +351,10 @@ namespace juce | |||
| #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 407 && ! defined (JUCE_DELETED_FUNCTION) | |||
| #define JUCE_DELETED_FUNCTION = delete | |||
| #endif | |||
| #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 && ! defined (JUCE_COMPILER_SUPPORTS_LAMBDAS) | |||
| #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 | |||
| #endif | |||
| #endif | |||
| #if JUCE_CLANG && defined (__has_feature) | |||
| @@ -336,6 +374,13 @@ namespace juce | |||
| #define JUCE_DELETED_FUNCTION = delete | |||
| #endif | |||
| #if __has_feature (cxx_lambdas) \ | |||
| && ((JUCE_MAC && defined (MAC_OS_X_VERSION_10_8) && MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_8) \ | |||
| || (JUCE_IOS && defined (__IPHONE_7_0) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0) \ | |||
| || ! (JUCE_MAC || JUCE_IOS)) | |||
| #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 | |||
| #endif | |||
| #ifndef JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL | |||
| #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | |||
| #endif | |||
| @@ -352,6 +397,7 @@ namespace juce | |||
| #if defined (_MSC_VER) && _MSC_VER >= 1700 | |||
| #define JUCE_COMPILER_SUPPORTS_OVERRIDE_AND_FINAL 1 | |||
| #define JUCE_COMPILER_SUPPORTS_LAMBDAS 1 | |||
| #endif | |||
| #ifndef JUCE_DELETED_FUNCTION | |||
| @@ -72,6 +72,7 @@ | |||
| #include <iostream> | |||
| #include <vector> | |||
| #include <algorithm> | |||
| #include <functional> | |||
| #if JUCE_USE_MSVC_INTRINSICS | |||
| #include <intrin.h> | |||
| @@ -97,7 +97,7 @@ String SystemStats::getStackBacktrace() | |||
| { | |||
| String result; | |||
| #if JUCE_ANDROID || JUCE_MINGW || JUCE_HAIKU | |||
| #if JUCE_ANDROID || JUCE_MINGW | |||
| jassertfalse; // sorry, not implemented yet! | |||
| #elif JUCE_WINDOWS | |||
| @@ -51,8 +51,6 @@ | |||
| #define JUCE_ANDROID 1 | |||
| #elif defined (LINUX) || defined (__linux__) | |||
| #define JUCE_LINUX 1 | |||
| #elif defined (__HAIKU__) | |||
| #define JUCE_HAIKU 1 | |||
| #elif defined (__APPLE_CPP__) || defined(__APPLE_CC__) | |||
| #define Point CarbonDummyPointName // (workaround to avoid definition of "Point" by old Carbon headers) | |||
| #define Component CarbonDummyCompName | |||
| @@ -142,7 +140,7 @@ | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_LINUX || JUCE_ANDROID || JUCE_HAIKU | |||
| #if JUCE_LINUX || JUCE_ANDROID | |||
| #ifdef _DEBUG | |||
| #define JUCE_DEBUG 1 | |||
| @@ -157,7 +155,7 @@ | |||
| #define JUCE_BIG_ENDIAN 1 | |||
| #endif | |||
| #if defined (__LP64__) || defined (_LP64) | |||
| #if defined (__LP64__) || defined (_LP64) || defined (__arm64__) | |||
| #define JUCE_64BIT 1 | |||
| #else | |||
| #define JUCE_32BIT 1 | |||
| @@ -1204,16 +1204,16 @@ public: | |||
| //============================================================================== | |||
| #if JUCE_MAC || JUCE_IOS || DOXYGEN | |||
| /** MAC ONLY - Creates a String from an OSX CFString. */ | |||
| /** OSX ONLY - Creates a String from an OSX CFString. */ | |||
| static String fromCFString (CFStringRef cfString); | |||
| /** MAC ONLY - Converts this string to a CFString. | |||
| /** OSX ONLY - Converts this string to a CFString. | |||
| Remember that you must use CFRelease() to free the returned string when you're | |||
| finished with it. | |||
| */ | |||
| CFStringRef toCFString() const; | |||
| /** MAC ONLY - Returns a copy of this string in which any decomposed unicode characters have | |||
| /** OSX ONLY - Returns a copy of this string in which any decomposed unicode characters have | |||
| been converted to their precomposed equivalents. */ | |||
| String convertToPrecomposedUnicode() const; | |||
| #endif | |||
| @@ -81,11 +81,6 @@ String ChildProcess::readAllProcessOutput() | |||
| return result.toString(); | |||
| } | |||
| uint32 ChildProcess::getPID() const noexcept | |||
| { | |||
| return activeProcess != nullptr ? activeProcess->getPID() : 0; | |||
| } | |||
| //============================================================================== | |||
| #if JUCE_UNIT_TESTS | |||
| @@ -106,8 +106,6 @@ public: | |||
| */ | |||
| bool kill(); | |||
| uint32 getPID() const noexcept; | |||
| private: | |||
| //============================================================================== | |||
| class ActiveProcess; | |||
| @@ -53,7 +53,7 @@ static void sanityCheckTagName (const String& tag) | |||
| (void) tag; | |||
| // the tag name mustn't be empty, or it'll look like a text element! | |||
| jassert (tag.containsNonWhitespaceChars()) | |||
| jassert (tag.containsNonWhitespaceChars()); | |||
| // The tag can't contain spaces or other characters that would create invalid XML! | |||
| jassert (! tag.containsAnyOf (" <>/&(){}")); | |||
| @@ -82,7 +82,7 @@ File PropertiesFile::Options::getDefaultFile() const | |||
| if (folderName.isNotEmpty()) | |||
| dir = dir.getChildFile (folderName); | |||
| #elif JUCE_LINUX || JUCE_ANDROID || JUCE_HAIKU | |||
| #elif JUCE_LINUX || JUCE_ANDROID | |||
| const File dir (File (commonToAllUsers ? "/var" : "~") | |||
| .getChildFile (folderName.isNotEmpty() ? folderName | |||
| : ("." + applicationName))); | |||
| @@ -29,10 +29,6 @@ struct UndoManager::ActionSet | |||
| time (Time::getCurrentTime()) | |||
| {} | |||
| OwnedArray <UndoableAction> actions; | |||
| String name; | |||
| Time time; | |||
| bool perform() const | |||
| { | |||
| for (int i = 0; i < actions.size(); ++i) | |||
| @@ -60,6 +56,10 @@ struct UndoManager::ActionSet | |||
| return total; | |||
| } | |||
| OwnedArray<UndoableAction> actions; | |||
| String name; | |||
| Time time; | |||
| }; | |||
| //============================================================================== | |||
| @@ -101,6 +101,19 @@ void UndoManager::setMaxNumberOfStoredUnits (const int maxNumberOfUnitsToKeep, | |||
| //============================================================================== | |||
| bool UndoManager::perform (UndoableAction* const newAction, const String& actionName) | |||
| { | |||
| if (perform (newAction)) | |||
| { | |||
| if (actionName.isNotEmpty()) | |||
| setCurrentTransactionName (actionName); | |||
| return true; | |||
| } | |||
| return false; | |||
| } | |||
| bool UndoManager::perform (UndoableAction* const newAction) | |||
| { | |||
| if (newAction != nullptr) | |||
| { | |||
| @@ -113,9 +126,6 @@ bool UndoManager::perform (UndoableAction* const newAction, const String& action | |||
| return false; | |||
| } | |||
| if (actionName.isNotEmpty()) | |||
| currentTransactionName = actionName; | |||
| if (action->perform()) | |||
| { | |||
| ActionSet* actionSet = getCurrentSet(); | |||
| @@ -134,7 +144,7 @@ bool UndoManager::perform (UndoableAction* const newAction, const String& action | |||
| } | |||
| else | |||
| { | |||
| actionSet = new ActionSet (currentTransactionName); | |||
| actionSet = new ActionSet (newTransactionName); | |||
| transactions.insert (nextIndex, actionSet); | |||
| ++nextIndex; | |||
| } | |||
| @@ -174,23 +184,31 @@ void UndoManager::clearFutureTransactions() | |||
| } | |||
| } | |||
| void UndoManager::beginNewTransaction (const String& actionName) | |||
| void UndoManager::beginNewTransaction() noexcept | |||
| { | |||
| beginNewTransaction (String()); | |||
| } | |||
| void UndoManager::beginNewTransaction (const String& actionName) noexcept | |||
| { | |||
| newTransaction = true; | |||
| currentTransactionName = actionName; | |||
| newTransactionName = actionName; | |||
| } | |||
| void UndoManager::setCurrentTransactionName (const String& newName) | |||
| void UndoManager::setCurrentTransactionName (const String& newName) noexcept | |||
| { | |||
| currentTransactionName = newName; | |||
| if (newTransaction) | |||
| newTransactionName = newName; | |||
| else if (ActionSet* action = getCurrentSet()) | |||
| action->name = newName; | |||
| } | |||
| //============================================================================== | |||
| UndoManager::ActionSet* UndoManager::getCurrentSet() const noexcept { return transactions [nextIndex - 1]; } | |||
| UndoManager::ActionSet* UndoManager::getNextSet() const noexcept { return transactions [nextIndex]; } | |||
| bool UndoManager::canUndo() const { return getCurrentSet() != nullptr; } | |||
| bool UndoManager::canRedo() const { return getNextSet() != nullptr; } | |||
| bool UndoManager::canUndo() const noexcept { return getCurrentSet() != nullptr; } | |||
| bool UndoManager::canRedo() const noexcept { return getNextSet() != nullptr; } | |||
| bool UndoManager::undo() | |||
| { | |||
| @@ -267,7 +285,7 @@ bool UndoManager::undoCurrentTransactionOnly() | |||
| return newTransaction ? false : undo(); | |||
| } | |||
| void UndoManager::getActionsInCurrentTransaction (Array <const UndoableAction*>& actionsFound) const | |||
| void UndoManager::getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const | |||
| { | |||
| if (! newTransaction) | |||
| if (const ActionSet* const s = getCurrentSet()) | |||
| @@ -98,16 +98,32 @@ public: | |||
| //============================================================================== | |||
| /** Performs an action and adds it to the undo history list. | |||
| @param action the action to perform - this will be deleted by the UndoManager | |||
| when no longer needed | |||
| @param action the action to perform - this object will be deleted by | |||
| the UndoManager when no longer needed | |||
| @returns true if the command succeeds - see UndoableAction::perform | |||
| @see beginNewTransaction | |||
| */ | |||
| bool perform (UndoableAction* action); | |||
| /** Performs an action and also gives it a name. | |||
| @param action the action to perform - this object will be deleted by | |||
| the UndoManager when no longer needed | |||
| @param actionName if this string is non-empty, the current transaction will be | |||
| given this name; if it's empty, the current transaction name will | |||
| be left unchanged. See setCurrentTransactionName() | |||
| @returns true if the command succeeds - see UndoableAction::perform | |||
| @see beginNewTransaction | |||
| */ | |||
| bool perform (UndoableAction* action, | |||
| const String& actionName = String()); | |||
| bool perform (UndoableAction* action, const String& actionName); | |||
| /** Starts a new group of actions that together will be treated as a single transaction. | |||
| All actions that are passed to the perform() method between calls to this | |||
| method are grouped together and undone/redone together by a single call to | |||
| undo() or redo(). | |||
| */ | |||
| void beginNewTransaction() noexcept; | |||
| /** Starts a new group of actions that together will be treated as a single transaction. | |||
| @@ -118,7 +134,7 @@ public: | |||
| @param actionName a description of the transaction that is about to be | |||
| performed | |||
| */ | |||
| void beginNewTransaction (const String& actionName = String()); | |||
| void beginNewTransaction (const String& actionName) noexcept; | |||
| /** Changes the name stored for the current transaction. | |||
| @@ -126,19 +142,15 @@ public: | |||
| called, but this can be used to change that name without starting a new | |||
| transaction. | |||
| */ | |||
| void setCurrentTransactionName (const String& newName); | |||
| void setCurrentTransactionName (const String& newName) noexcept; | |||
| //============================================================================== | |||
| /** Returns true if there's at least one action in the list to undo. | |||
| @see getUndoDescription, undo, canRedo | |||
| */ | |||
| bool canUndo() const; | |||
| /** Returns the description of the transaction that would be next to get undone. | |||
| The description returned is the one that was passed into beginNewTransaction | |||
| before the set of actions was performed. | |||
| bool canUndo() const noexcept; | |||
| /** Returns the name of the transaction that will be rolled-back when undo() is called. | |||
| @see undo | |||
| */ | |||
| String getUndoDescription() const; | |||
| @@ -172,7 +184,7 @@ public: | |||
| The first item in the list is the earliest action performed. | |||
| */ | |||
| void getActionsInCurrentTransaction (Array <const UndoableAction*>& actionsFound) const; | |||
| void getActionsInCurrentTransaction (Array<const UndoableAction*>& actionsFound) const; | |||
| /** Returns the number of UndoableAction objects that have been performed during the | |||
| transaction that is currently open. | |||
| @@ -194,12 +206,9 @@ public: | |||
| /** Returns true if there's at least one action in the list to redo. | |||
| @see getRedoDescription, redo, canUndo | |||
| */ | |||
| bool canRedo() const; | |||
| /** Returns the description of the transaction that would be next to get redone. | |||
| The description returned is the one that was passed into beginNewTransaction | |||
| before the set of actions was performed. | |||
| bool canRedo() const noexcept; | |||
| /** Returns the name of the transaction that will be redone when redo() is called. | |||
| @see redo | |||
| */ | |||
| String getRedoDescription() const; | |||
| @@ -216,7 +225,7 @@ private: | |||
| struct ActionSet; | |||
| friend struct ContainerDeletePolicy<ActionSet>; | |||
| OwnedArray<ActionSet> transactions; | |||
| String currentTransactionName; | |||
| String newTransactionName; | |||
| int totalUnitsStored, maxNumUnitsToKeep, minimumTransactionsToKeep, nextIndex; | |||
| bool newTransaction, reentrancyCheck; | |||
| ActionSet* getCurrentSet() const noexcept; | |||
| @@ -318,7 +318,10 @@ public: | |||
| /** Creates an XmlElement that holds a complete image of this node and all its children. | |||
| If this node is invalid, this may return nullptr. Otherwise, the XML that is produced can | |||
| be used to recreate a similar node by calling fromXml() | |||
| be used to recreate a similar node by calling fromXml(). | |||
| The caller must delete the object that is returned. | |||
| @see fromXml | |||
| */ | |||
| XmlElement* createXml() const; | |||
| @@ -130,6 +130,25 @@ void MessageManager::stopDispatchLoop() | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| struct AsyncFunction : private MessageManager::MessageBase | |||
| { | |||
| AsyncFunction (std::function<void(void)> f) : fn (f) { post(); } | |||
| private: | |||
| std::function<void(void)> fn; | |||
| void messageCallback() override { fn(); } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AsyncFunction) | |||
| }; | |||
| void MessageManager::callAsync (std::function<void(void)> f) | |||
| { | |||
| new AsyncFunction (f); | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| class AsyncFunctionCallback : public MessageManager::MessageBase | |||
| { | |||
| @@ -91,6 +91,13 @@ public: | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| /** Asynchronously invokes a function or C++11 lambda on the message thread. | |||
| Internally this uses the CallbackMessage class to invoke the callback. | |||
| */ | |||
| static void callAsync (std::function<void(void)>); | |||
| #endif | |||
| /** Calls a function using the message-thread. | |||
| This can be used by any thread to cause this function to be called-back | |||
| @@ -360,7 +360,7 @@ bool MessageManager::postMessageToSystemQueue (MessageManager::MessageBase* cons | |||
| if (InternalMessageQueue* const queue = InternalMessageQueue::getInstanceWithoutCreating()) | |||
| { | |||
| InternalMessageQueue::getInstanceWithoutCreating()->postMessage (message); | |||
| queue->postMessage (message); | |||
| return true; | |||
| } | |||
| @@ -158,7 +158,7 @@ public: | |||
| void drawText (const String& text, | |||
| int x, int y, int width, int height, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig) const; | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Draws a line of text within a specified rectangle. | |||
| @@ -172,7 +172,7 @@ public: | |||
| void drawText (const String& text, | |||
| const Rectangle<int>& area, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig) const; | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Draws a line of text within a specified rectangle. | |||
| @@ -186,7 +186,7 @@ public: | |||
| void drawText (const String& text, | |||
| const Rectangle<float>& area, | |||
| Justification justificationType, | |||
| bool useEllipsesIfTooBig) const; | |||
| bool useEllipsesIfTooBig = true) const; | |||
| /** Tries to draw a text string inside a given space. | |||
| @@ -384,7 +384,7 @@ void LowLevelGraphicsPostScriptRenderer::fillPath (const Path& path, const Affin | |||
| { | |||
| // this doesn't work correctly yet - it could be improved to handle solid gradients, but | |||
| // postscript can't do semi-transparent ones. | |||
| notPossibleInPostscriptAssert // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file | |||
| notPossibleInPostscriptAssert; // you can disable this warning by setting the WARN_ABOUT_NON_POSTSCRIPT_OPERATIONS flag at the top of this file | |||
| writeClip(); | |||
| out << "gsave "; | |||
| @@ -488,12 +488,13 @@ bool PNGImageFormat::writeImageToStream (const Image& image, OutputStream& out) | |||
| PNG_COMPRESSION_TYPE_BASE, | |||
| PNG_FILTER_TYPE_BASE); | |||
| HeapBlock <uint8> rowData ((size_t) width * 4); | |||
| HeapBlock<uint8> rowData ((size_t) width * 4); | |||
| png_color_8 sig_bit; | |||
| sig_bit.red = 8; | |||
| sig_bit.red = 8; | |||
| sig_bit.green = 8; | |||
| sig_bit.blue = 8; | |||
| sig_bit.blue = 8; | |||
| sig_bit.gray = 0; | |||
| sig_bit.alpha = 8; | |||
| png_set_sBIT (pngWriteStruct, pngInfoStruct, &sig_bit); | |||
| @@ -1026,7 +1026,7 @@ png_read_png(png_structrp png_ptr, png_inforp info_ptr, | |||
| if ((transforms & PNG_TRANSFORM_SHIFT) | |||
| && png_get_valid(png_ptr, info_ptr, PNG_INFO_sBIT)) | |||
| { | |||
| png_color_8p sig_bit; | |||
| png_color_8p sig_bit = 0; | |||
| png_get_sBIT(png_ptr, info_ptr, &sig_bit); | |||
| png_set_shift(png_ptr, sig_bit); | |||
| @@ -197,11 +197,11 @@ Image Image::getClippedImage (const Rectangle<int>& area) const | |||
| //============================================================================== | |||
| Image::Image() | |||
| Image::Image() noexcept | |||
| { | |||
| } | |||
| Image::Image (ImagePixelData* const instance) | |||
| Image::Image (ImagePixelData* const instance) noexcept | |||
| : image (instance) | |||
| { | |||
| } | |||
| @@ -216,7 +216,7 @@ Image::Image (const PixelFormat format, int width, int height, bool clearImage, | |||
| { | |||
| } | |||
| Image::Image (const Image& other) | |||
| Image::Image (const Image& other) noexcept | |||
| : image (other.image) | |||
| { | |||
| } | |||
| @@ -67,7 +67,7 @@ public: | |||
| //============================================================================== | |||
| /** Creates a null image. */ | |||
| Image(); | |||
| Image() noexcept; | |||
| /** Creates an image with a specified size and format. | |||
| @@ -106,7 +106,7 @@ public: | |||
| point to the same shared image data. To make sure that an Image object has its own unique, | |||
| unshared internal data, call duplicateIfShared(). | |||
| */ | |||
| Image (const Image&); | |||
| Image (const Image&) noexcept; | |||
| /** Makes this image refer to the same underlying image as another object. | |||
| @@ -408,7 +408,7 @@ public: | |||
| ImagePixelData* getPixelData() const noexcept { return image; } | |||
| /** @internal */ | |||
| explicit Image (ImagePixelData*); | |||
| explicit Image (ImagePixelData*) noexcept; | |||
| private: | |||
| //============================================================================== | |||
| @@ -125,11 +125,12 @@ void Button::setTooltip (const String& newTooltip) | |||
| generateTooltip = false; | |||
| } | |||
| String Button::getTooltip() | |||
| void Button::updateAutomaticTooltip (const ApplicationCommandInfo& info) | |||
| { | |||
| if (generateTooltip && commandManagerToUse != nullptr && commandID != 0) | |||
| if (generateTooltip && commandManagerToUse != nullptr) | |||
| { | |||
| String tt (commandManagerToUse->getDescriptionOfCommand (commandID)); | |||
| String tt (info.description.isNotEmpty() ? info.description | |||
| : info.shortName); | |||
| Array<KeyPress> keyPresses (commandManagerToUse->getKeyMappings()->getKeyPressesAssignedToCommand (commandID)); | |||
| @@ -145,10 +146,8 @@ String Button::getTooltip() | |||
| tt << key << ']'; | |||
| } | |||
| return tt; | |||
| SettableTooltipClient::setTooltip (tt); | |||
| } | |||
| return SettableTooltipClient::getTooltip(); | |||
| } | |||
| void Button::setConnectedEdges (const int newFlags) | |||
| @@ -542,6 +541,7 @@ void Button::applicationCommandListChangeCallback() | |||
| if (commandManagerToUse->getTargetForCommand (commandID, info) != nullptr) | |||
| { | |||
| updateAutomaticTooltip (info); | |||
| setEnabled ((info.flags & ApplicationCommandInfo::isDisabled) == 0); | |||
| setToggleState ((info.flags & ApplicationCommandInfo::isTicked) != 0, dontSendNotification); | |||
| } | |||
| @@ -277,12 +277,6 @@ public: | |||
| */ | |||
| void setTooltip (const String& newTooltip) override; | |||
| /** Returns the tooltip set by setTooltip(), or the description corresponding to | |||
| the currently mapped command if one is enabled (see setCommandToTrigger). | |||
| */ | |||
| String getTooltip() override; | |||
| //============================================================================== | |||
| /** A combination of these flags are used by setConnectedEdges(). */ | |||
| enum ConnectedEdgeFlags | |||
| @@ -349,6 +343,9 @@ public: | |||
| */ | |||
| void setState (ButtonState newState); | |||
| /** Returns the button's current over/down/up state. */ | |||
| ButtonState getState() const noexcept { return buttonState; } | |||
| // This method's parameters have changed - see the new version. | |||
| JUCE_DEPRECATED (void setToggleState (bool, bool)); | |||
| @@ -495,6 +492,7 @@ private: | |||
| void repeatTimerCallback(); | |||
| bool keyStateChangedCallback(); | |||
| void applicationCommandListChangeCallback(); | |||
| void updateAutomaticTooltip (const ApplicationCommandInfo&); | |||
| ButtonState updateState(); | |||
| ButtonState updateState (bool isOver, bool isDown); | |||
| @@ -51,7 +51,20 @@ void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& n | |||
| // the name isn't optional! | |||
| jassert (newCommand.shortName.isNotEmpty()); | |||
| if (getCommandForID (newCommand.commandID) == 0) | |||
| if (ApplicationCommandInfo* command = getMutableCommandForID (newCommand.commandID)) | |||
| { | |||
| // Trying to re-register the same command ID with different parameters can often indicate a typo. | |||
| // This assertion is here because I've found it useful catching some mistakes, but it may also cause | |||
| // false alarms if you're deliberately updating some flags for a command. | |||
| jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName | |||
| && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName | |||
| && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses | |||
| && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)) | |||
| == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))); | |||
| *command = newCommand; | |||
| } | |||
| else | |||
| { | |||
| ApplicationCommandInfo* const newInfo = new ApplicationCommandInfo (newCommand); | |||
| newInfo->flags &= ~ApplicationCommandInfo::isTicked; | |||
| @@ -61,16 +74,6 @@ void ApplicationCommandManager::registerCommand (const ApplicationCommandInfo& n | |||
| triggerAsyncUpdate(); | |||
| } | |||
| else | |||
| { | |||
| // trying to re-register the same command ID with different parameters? | |||
| jassert (newCommand.shortName == getCommandForID (newCommand.commandID)->shortName | |||
| && (newCommand.description == getCommandForID (newCommand.commandID)->description || newCommand.description.isEmpty()) | |||
| && newCommand.categoryName == getCommandForID (newCommand.commandID)->categoryName | |||
| && newCommand.defaultKeypresses == getCommandForID (newCommand.commandID)->defaultKeypresses | |||
| && (newCommand.flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor)) | |||
| == (getCommandForID (newCommand.commandID)->flags & (ApplicationCommandInfo::wantsKeyUpDownCallbacks | ApplicationCommandInfo::hiddenFromKeyEditor | ApplicationCommandInfo::readOnlyInKeyEditor))); | |||
| } | |||
| } | |||
| void ApplicationCommandManager::registerAllCommandsForTarget (ApplicationCommandTarget* target) | |||
| @@ -113,7 +116,7 @@ void ApplicationCommandManager::commandStatusChanged() | |||
| } | |||
| //============================================================================== | |||
| const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const noexcept | |||
| ApplicationCommandInfo* ApplicationCommandManager::getMutableCommandForID (CommandID commandID) const noexcept | |||
| { | |||
| for (int i = commands.size(); --i >= 0;) | |||
| if (commands.getUnchecked(i)->commandID == commandID) | |||
| @@ -122,6 +125,11 @@ const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const | |||
| return nullptr; | |||
| } | |||
| const ApplicationCommandInfo* ApplicationCommandManager::getCommandForID (const CommandID commandID) const noexcept | |||
| { | |||
| return getMutableCommandForID (commandID); | |||
| } | |||
| String ApplicationCommandManager::getNameOfCommand (const CommandID commandID) const noexcept | |||
| { | |||
| if (const ApplicationCommandInfo* const ci = getCommandForID (commandID)) | |||
| @@ -215,7 +223,10 @@ ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const | |||
| target = target->getTargetForCommand (commandID); | |||
| if (target != nullptr) | |||
| { | |||
| upToDateInfo.commandID = commandID; | |||
| target->getCommandInfo (commandID, upToDateInfo); | |||
| } | |||
| return target; | |||
| } | |||
| @@ -223,7 +234,7 @@ ApplicationCommandTarget* ApplicationCommandManager::getTargetForCommand (const | |||
| //============================================================================== | |||
| ApplicationCommandTarget* ApplicationCommandManager::findTargetForComponent (Component* c) | |||
| { | |||
| ApplicationCommandTarget* target = dynamic_cast <ApplicationCommandTarget*> (c); | |||
| ApplicationCommandTarget* target = dynamic_cast<ApplicationCommandTarget*> (c); | |||
| if (target == nullptr && c != nullptr) | |||
| target = c->findParentComponentOfClass<ApplicationCommandTarget>(); | |||
| @@ -263,7 +274,7 @@ ApplicationCommandTarget* ApplicationCommandManager::findDefaultComponentTarget( | |||
| // component that really should get the event. And if not, the event will | |||
| // still be passed up to the top level window anyway, so let's send it to the | |||
| // content comp. | |||
| if (ResizableWindow* const resizableWindow = dynamic_cast <ResizableWindow*> (c)) | |||
| if (ResizableWindow* const resizableWindow = dynamic_cast<ResizableWindow*> (c)) | |||
| if (Component* const content = resizableWindow->getContentComponent()) | |||
| c = content; | |||
| @@ -308,6 +308,7 @@ private: | |||
| void sendListenerInvokeCallback (const ApplicationCommandTarget::InvocationInfo&); | |||
| void handleAsyncUpdate() override; | |||
| void globalFocusChanged (Component*) override; | |||
| ApplicationCommandInfo* getMutableCommandForID (CommandID) const noexcept; | |||
| #if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||
| // This is just here to cause a compile error in old code that hasn't been changed to use the new | |||
| @@ -252,7 +252,7 @@ bool KeyPressMappingSet::restoreFromXml (const XmlElement& xmlVersion) | |||
| XmlElement* KeyPressMappingSet::createXml (const bool saveDifferencesFromDefaultSet) const | |||
| { | |||
| ScopedPointer <KeyPressMappingSet> defaultSet; | |||
| ScopedPointer<KeyPressMappingSet> defaultSet; | |||
| if (saveDifferencesFromDefaultSet) | |||
| { | |||
| @@ -1747,11 +1747,14 @@ public: | |||
| */ | |||
| virtual void focusLost (FocusChangeType cause); | |||
| /** Called to indicate that one of this component's children has been focused or unfocused. | |||
| /** Called to indicate a change in whether or not this component is the parent of the | |||
| currently-focused component. | |||
| Essentially this means that the return value of a call to hasKeyboardFocus (true) has | |||
| Essentially this is called when the return value of a call to hasKeyboardFocus (true) has | |||
| changed. It happens when focus moves from one of this component's children (at any depth) | |||
| to a component that isn't contained in this one, (or vice-versa). | |||
| Note that this method does NOT get called to when focus simply moves from one of its | |||
| child components to another. | |||
| @see focusGained, setWantsKeyboardFocus, getCurrentlyFocusedComponent, hasKeyboardFocus | |||
| */ | |||
| @@ -1786,9 +1789,7 @@ public: | |||
| bool isMouseButtonDown() const; | |||
| /** True if the mouse is over this component, or if it's being dragged in this component. | |||
| This is a handy equivalent to (isMouseOver() || isMouseButtonDown()). | |||
| @see isMouseOver, isMouseButtonDown, isMouseButtonDownAnywhere | |||
| */ | |||
| bool isMouseOverOrDragging() const; | |||
| @@ -85,31 +85,13 @@ public: | |||
| newState.viewBoxW = vwh.x; | |||
| newState.viewBoxH = vwh.y; | |||
| int placementFlags = 0; | |||
| const int placementFlags = parsePlacementFlags (xml->getStringAttribute ("preserveAspectRatio").trim()); | |||
| const String aspect (xml->getStringAttribute ("preserveAspectRatio")); | |||
| if (aspect.containsIgnoreCase ("none")) | |||
| { | |||
| placementFlags = RectanglePlacement::stretchToFit; | |||
| } | |||
| else | |||
| { | |||
| if (aspect.containsIgnoreCase ("slice")) placementFlags |= RectanglePlacement::fillDestination; | |||
| if (aspect.containsIgnoreCase ("xMin")) placementFlags |= RectanglePlacement::xLeft; | |||
| else if (aspect.containsIgnoreCase ("xMax")) placementFlags |= RectanglePlacement::xRight; | |||
| else placementFlags |= RectanglePlacement::xMid; | |||
| if (aspect.containsIgnoreCase ("yMin")) placementFlags |= RectanglePlacement::yTop; | |||
| else if (aspect.containsIgnoreCase ("yMax")) placementFlags |= RectanglePlacement::yBottom; | |||
| else placementFlags |= RectanglePlacement::yMid; | |||
| } | |||
| newState.transform = RectanglePlacement (placementFlags) | |||
| .getTransformToFit (Rectangle<float> (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y), | |||
| Rectangle<float> (newState.width, newState.height)) | |||
| .followedBy (newState.transform); | |||
| if (placementFlags != 0) | |||
| newState.transform = RectanglePlacement (placementFlags) | |||
| .getTransformToFit (Rectangle<float> (viewboxXY.x, viewboxXY.y, vwh.x, vwh.y), | |||
| Rectangle<float> (newState.width, newState.height)) | |||
| .followedBy (newState.transform); | |||
| } | |||
| } | |||
| else | |||
| @@ -560,24 +542,12 @@ private: | |||
| path.applyTransform (transform); | |||
| dp->setPath (path); | |||
| Path::Iterator iter (path); | |||
| bool containsClosedSubPath = false; | |||
| while (iter.next()) | |||
| { | |||
| if (iter.elementType == Path::Iterator::closePath) | |||
| { | |||
| containsClosedSubPath = true; | |||
| break; | |||
| } | |||
| } | |||
| dp->setFill (getPathFillType (path, | |||
| getStyleAttribute (xml, "fill"), | |||
| getStyleAttribute (xml, "fill-opacity"), | |||
| getStyleAttribute (xml, "opacity"), | |||
| containsClosedSubPath ? Colours::black | |||
| : Colours::transparentBlack)); | |||
| pathContainsClosedSubPath (path) ? Colours::black | |||
| : Colours::transparentBlack)); | |||
| const String strokeType (getStyleAttribute (xml, "stroke")); | |||
| @@ -594,6 +564,15 @@ private: | |||
| return dp; | |||
| } | |||
| static bool pathContainsClosedSubPath (const Path& path) noexcept | |||
| { | |||
| for (Path::Iterator iter (path); iter.next();) | |||
| if (iter.elementType == Path::Iterator::closePath) | |||
| return true; | |||
| return false; | |||
| } | |||
| struct SetGradientStopsOp | |||
| { | |||
| const SVGState* state; | |||
| @@ -796,44 +775,41 @@ private: | |||
| return parseColour (fill, i, defaultColour).withMultipliedAlpha (opacity); | |||
| } | |||
| PathStrokeType getStrokeFor (const XmlPath& xml) const | |||
| static PathStrokeType::JointStyle getJointStyle (const String& join) noexcept | |||
| { | |||
| const String strokeWidth (getStyleAttribute (xml, "stroke-width")); | |||
| const String cap (getStyleAttribute (xml, "stroke-linecap")); | |||
| const String join (getStyleAttribute (xml, "stroke-linejoin")); | |||
| if (join.equalsIgnoreCase ("round")) return PathStrokeType::curved; | |||
| if (join.equalsIgnoreCase ("bevel")) return PathStrokeType::beveled; | |||
| //const String mitreLimit (getStyleAttribute (xml, "stroke-miterlimit")); | |||
| //const String dashArray (getStyleAttribute (xml, "stroke-dasharray")); | |||
| //const String dashOffset (getStyleAttribute (xml, "stroke-dashoffset")); | |||
| PathStrokeType::JointStyle joinStyle = PathStrokeType::mitered; | |||
| PathStrokeType::EndCapStyle capStyle = PathStrokeType::butt; | |||
| return PathStrokeType::mitered; | |||
| } | |||
| if (join.equalsIgnoreCase ("round")) | |||
| joinStyle = PathStrokeType::curved; | |||
| else if (join.equalsIgnoreCase ("bevel")) | |||
| joinStyle = PathStrokeType::beveled; | |||
| static PathStrokeType::EndCapStyle getEndCapStyle (const String& cap) noexcept | |||
| { | |||
| if (cap.equalsIgnoreCase ("round")) return PathStrokeType::rounded; | |||
| if (cap.equalsIgnoreCase ("square")) return PathStrokeType::square; | |||
| if (cap.equalsIgnoreCase ("round")) | |||
| capStyle = PathStrokeType::rounded; | |||
| else if (cap.equalsIgnoreCase ("square")) | |||
| capStyle = PathStrokeType::square; | |||
| return PathStrokeType::butt; | |||
| } | |||
| float ox = 0.0f, oy = 0.0f; | |||
| float x = getCoordLength (strokeWidth, viewBoxW), y = 0.0f; | |||
| transform.transformPoints (ox, oy, x, y); | |||
| float getStrokeWidth (const String& strokeWidth) const noexcept | |||
| { | |||
| return transform.getScaleFactor() * getCoordLength (strokeWidth, viewBoxW); | |||
| } | |||
| return PathStrokeType (strokeWidth.isNotEmpty() ? juce_hypot (x - ox, y - oy) : 1.0f, | |||
| joinStyle, capStyle); | |||
| PathStrokeType getStrokeFor (const XmlPath& xml) const | |||
| { | |||
| return PathStrokeType (getStrokeWidth (getStyleAttribute (xml, "stroke-width", "1")), | |||
| getJointStyle (getStyleAttribute (xml, "stroke-linejoin")), | |||
| getEndCapStyle (getStyleAttribute (xml, "stroke-linecap"))); | |||
| } | |||
| //============================================================================== | |||
| Drawable* parseText (const XmlPath& xml) | |||
| { | |||
| Array <float> xCoords, yCoords, dxCoords, dyCoords; | |||
| Array<float> xCoords, yCoords, dxCoords, dyCoords; | |||
| getCoordList (xCoords, getInheritedAttribute (xml, "x"), true, true); | |||
| getCoordList (yCoords, getInheritedAttribute (xml, "y"), true, false); | |||
| getCoordList (xCoords, getInheritedAttribute (xml, "x"), true, true); | |||
| getCoordList (yCoords, getInheritedAttribute (xml, "y"), true, false); | |||
| getCoordList (dxCoords, getInheritedAttribute (xml, "dx"), true, true); | |||
| getCoordList (dyCoords, getInheritedAttribute (xml, "dy"), true, false); | |||
| @@ -898,7 +874,7 @@ private: | |||
| return false; | |||
| } | |||
| float getCoordLength (const String& s, const float sizeForProportions) const | |||
| float getCoordLength (const String& s, const float sizeForProportions) const noexcept | |||
| { | |||
| float n = s.getFloatValue(); | |||
| const int len = s.length(); | |||
| @@ -920,13 +896,12 @@ private: | |||
| return n; | |||
| } | |||
| float getCoordLength (const XmlPath& xml, const char* attName, const float sizeForProportions) const | |||
| float getCoordLength (const XmlPath& xml, const char* attName, const float sizeForProportions) const noexcept | |||
| { | |||
| return getCoordLength (xml->getStringAttribute (attName), sizeForProportions); | |||
| } | |||
| void getCoordList (Array <float>& coords, const String& list, | |||
| const bool allowUnits, const bool isX) const | |||
| void getCoordList (Array<float>& coords, const String& list, bool allowUnits, const bool isX) const | |||
| { | |||
| String::CharPointerType text (list.getCharPointer()); | |||
| float value; | |||
| @@ -960,7 +935,7 @@ private: | |||
| return source; | |||
| } | |||
| String getStyleAttribute (const XmlPath& xml, const String& attributeName, | |||
| String getStyleAttribute (const XmlPath& xml, StringRef attributeName, | |||
| const String& defaultValue = String()) const | |||
| { | |||
| if (xml->hasAttribute (attributeName)) | |||
| @@ -1000,7 +975,7 @@ private: | |||
| return defaultValue; | |||
| } | |||
| String getInheritedAttribute (const XmlPath& xml, const String& attributeName) const | |||
| String getInheritedAttribute (const XmlPath& xml, StringRef attributeName) const | |||
| { | |||
| if (xml->hasAttribute (attributeName)) | |||
| return xml->getStringAttribute (attributeName); | |||
| @@ -1011,13 +986,30 @@ private: | |||
| return String(); | |||
| } | |||
| static int parsePlacementFlags (const String& align) noexcept | |||
| { | |||
| if (align.isEmpty()) | |||
| return 0; | |||
| if (align.containsIgnoreCase ("none")) | |||
| return RectanglePlacement::stretchToFit; | |||
| return (align.containsIgnoreCase ("slice") ? RectanglePlacement::fillDestination : 0) | |||
| | (align.containsIgnoreCase ("xMin") ? RectanglePlacement::xLeft | |||
| : (align.containsIgnoreCase ("xMax") ? RectanglePlacement::xRight | |||
| : RectanglePlacement::xMid)) | |||
| | (align.containsIgnoreCase ("yMin") ? RectanglePlacement::yTop | |||
| : (align.containsIgnoreCase ("yMax") ? RectanglePlacement::yBottom | |||
| : RectanglePlacement::yMid)); | |||
| } | |||
| //============================================================================== | |||
| static bool isIdentifierChar (const juce_wchar c) | |||
| { | |||
| return CharacterFunctions::isLetter (c) || c == '-'; | |||
| } | |||
| static String getAttributeFromStyleList (const String& list, const String& attributeName, const String& defaultValue) | |||
| static String getAttributeFromStyleList (const String& list, StringRef attributeName, const String& defaultValue) | |||
| { | |||
| int i = 0; | |||
| @@ -1221,7 +1213,7 @@ private: | |||
| const bool largeArc, const bool sweep, | |||
| double& rx, double& ry, | |||
| double& centreX, double& centreY, | |||
| double& startAngle, double& deltaAngle) | |||
| double& startAngle, double& deltaAngle) noexcept | |||
| { | |||
| const double midX = (x1 - x2) * 0.5; | |||
| const double midY = (y1 - y2) * 0.5; | |||
| @@ -217,7 +217,7 @@ void ComponentAnimator::animateComponent (Component* const component, | |||
| const double endSpeed) | |||
| { | |||
| // the speeds must be 0 or greater! | |||
| jassert (startSpeed >= 0 && endSpeed >= 0) | |||
| jassert (startSpeed >= 0 && endSpeed >= 0); | |||
| if (component != nullptr) | |||
| { | |||
| @@ -111,6 +111,9 @@ LookAndFeel_V2::LookAndFeel_V2() | |||
| TextPropertyComponent::textColourId, 0xff000000, | |||
| TextPropertyComponent::outlineColourId, standardOutlineColour, | |||
| BooleanPropertyComponent::backgroundColourId, 0xffffffff, | |||
| BooleanPropertyComponent::outlineColourId, standardOutlineColour, | |||
| ListBox::backgroundColourId, 0xffffffff, | |||
| ListBox::outlineColourId, standardOutlineColour, | |||
| ListBox::textColourId, 0xff000000, | |||
| @@ -1001,6 +1004,16 @@ void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle<int>& area, | |||
| } | |||
| } | |||
| void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle<int>& area, const String& sectionName) | |||
| { | |||
| g.setFont (getPopupMenuFont().boldened()); | |||
| g.setColour (findColour (PopupMenu::headerTextColourId)); | |||
| g.drawFittedText (sectionName, | |||
| area.getX() + 12, area.getY(), area.getWidth() - 16, (int) (area.getHeight() * 0.8f), | |||
| Justification::bottomLeft, 1); | |||
| } | |||
| //============================================================================== | |||
| int LookAndFeel_V2::getMenuWindowFlags() | |||
| { | |||
| @@ -137,6 +137,9 @@ public: | |||
| const String& text, const String& shortcutKeyText, | |||
| const Drawable* icon, const Colour* textColour) override; | |||
| void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>& area, | |||
| const String& sectionName) override; | |||
| Font getPopupMenuFont() override; | |||
| void drawPopupMenuUpDownArrow (Graphics&, int width, int height, bool isScrollUpArrow) override; | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| MenuBarComponent::MenuBarComponent (MenuBarModel* model_) | |||
| MenuBarComponent::MenuBarComponent (MenuBarModel* m) | |||
| : model (nullptr), | |||
| itemUnderMouse (-1), | |||
| currentPopupIndex (-1), | |||
| @@ -32,7 +32,7 @@ MenuBarComponent::MenuBarComponent (MenuBarModel* model_) | |||
| setWantsKeyboardFocus (false); | |||
| setMouseClickGrabsKeyboardFocus (false); | |||
| setModel (model_); | |||
| setModel (m); | |||
| } | |||
| MenuBarComponent::~MenuBarComponent() | |||
| @@ -284,22 +284,26 @@ void MenuBarComponent::mouseMove (const MouseEvent& e) | |||
| bool MenuBarComponent::keyPressed (const KeyPress& key) | |||
| { | |||
| bool used = false; | |||
| const int numMenus = menuNames.size(); | |||
| const int currentIndex = jlimit (0, menuNames.size() - 1, currentPopupIndex); | |||
| if (key.isKeyCode (KeyPress::leftKey)) | |||
| if (numMenus > 0) | |||
| { | |||
| showMenu ((currentIndex + numMenus - 1) % numMenus); | |||
| used = true; | |||
| } | |||
| else if (key.isKeyCode (KeyPress::rightKey)) | |||
| { | |||
| showMenu ((currentIndex + 1) % numMenus); | |||
| used = true; | |||
| const int currentIndex = jlimit (0, numMenus - 1, currentPopupIndex); | |||
| if (key.isKeyCode (KeyPress::leftKey)) | |||
| { | |||
| showMenu ((currentIndex + numMenus - 1) % numMenus); | |||
| return true; | |||
| } | |||
| if (key.isKeyCode (KeyPress::rightKey)) | |||
| { | |||
| showMenu ((currentIndex + 1) % numMenus); | |||
| return true; | |||
| } | |||
| } | |||
| return used; | |||
| return false; | |||
| } | |||
| void MenuBarComponent::menuBarItemsChanged (MenuBarModel* /*menuBarModel*/) | |||
| @@ -40,9 +40,9 @@ public: | |||
| //============================================================================== | |||
| /** Creates a menu bar. | |||
| @param model the model object to use to control this bar. You can | |||
| pass 0 into this if you like, and set the model later | |||
| using the setModel() method | |||
| @param model the model object to use to control this bar. You can | |||
| pass nullptr into this if you like, and set the model | |||
| later using the setModel() method | |||
| */ | |||
| MenuBarComponent (MenuBarModel* model); | |||
| @@ -57,8 +57,7 @@ public: | |||
| */ | |||
| void setModel (MenuBarModel* newModel); | |||
| /** Returns the current menu bar model being used. | |||
| */ | |||
| /** Returns the current menu bar model being used. */ | |||
| MenuBarModel* getModel() const noexcept; | |||
| //============================================================================== | |||
| @@ -80,8 +80,7 @@ public: | |||
| virtual ~Listener() {} | |||
| //============================================================================== | |||
| /** This callback is made when items are changed in the menu bar model. | |||
| */ | |||
| /** This callback is made when items are changed in the menu bar model. */ | |||
| virtual void menuBarItemsChanged (MenuBarModel* menuBarModel) = 0; | |||
| /** This callback is made when an application command is invoked that | |||
| @@ -101,7 +100,6 @@ public: | |||
| void addListener (Listener* listenerToAdd) noexcept; | |||
| /** Removes a listener. | |||
| @see addListener | |||
| */ | |||
| void removeListener (Listener* listenerToRemove) noexcept; | |||
| @@ -130,7 +128,7 @@ public: | |||
| //============================================================================== | |||
| #if JUCE_MAC || DOXYGEN | |||
| /** MAC ONLY - Sets the model that is currently being shown as the main | |||
| /** OSX ONLY - Sets the model that is currently being shown as the main | |||
| menu bar at the top of the screen on the Mac. | |||
| You can pass 0 to stop the current model being displayed. Be careful | |||
| @@ -151,12 +149,12 @@ public: | |||
| const PopupMenu* extraAppleMenuItems = nullptr, | |||
| const String& recentItemsMenuName = String::empty); | |||
| /** MAC ONLY - Returns the menu model that is currently being shown as | |||
| /** OSX ONLY - Returns the menu model that is currently being shown as | |||
| the main menu bar. | |||
| */ | |||
| static MenuBarModel* getMacMainMenu(); | |||
| /** MAC ONLY - Returns the menu that was last passed as the extraAppleMenuItems | |||
| /** OSX ONLY - Returns the menu that was last passed as the extraAppleMenuItems | |||
| argument to setMacMainMenu(), or nullptr if none was specified. | |||
| */ | |||
| static const PopupMenu* getMacExtraAppleItemsMenu(); | |||
| @@ -164,7 +162,7 @@ public: | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo& info) override; | |||
| void applicationCommandInvoked (const ApplicationCommandTarget::InvocationInfo&) override; | |||
| /** @internal */ | |||
| void applicationCommandListChanged() override; | |||
| /** @internal */ | |||
| @@ -172,7 +170,7 @@ public: | |||
| private: | |||
| ApplicationCommandManager* manager; | |||
| ListenerList <Listener> listeners; | |||
| ListenerList<Listener> listeners; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MenuBarModel) | |||
| }; | |||
| @@ -740,7 +740,7 @@ public: | |||
| void ensureItemIsVisible (const int itemID, int wantedY) | |||
| { | |||
| jassert (itemID != 0) | |||
| jassert (itemID != 0); | |||
| for (int i = items.size(); --i >= 0;) | |||
| { | |||
| @@ -1216,12 +1216,7 @@ public: | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.setFont (getLookAndFeel().getPopupMenuFont().boldened()); | |||
| g.setColour (findColour (PopupMenu::headerTextColourId)); | |||
| g.drawFittedText (getName(), | |||
| 12, 0, getWidth() - 16, proportionOfHeight (0.8f), | |||
| Justification::bottomLeft, 1); | |||
| getLookAndFeel().drawPopupMenuSectionHeader (g, getLocalBounds(), getName()); | |||
| } | |||
| void getIdealSize (int& idealWidth, int& idealHeight) | |||
| @@ -562,6 +562,9 @@ public: | |||
| const Drawable* icon, | |||
| const Colour* textColour) = 0; | |||
| virtual void drawPopupMenuSectionHeader (Graphics&, const Rectangle<int>& area, | |||
| const String& sectionName) = 0; | |||
| /** Returns the size and style of font to use in popup menus. */ | |||
| virtual Font getPopupMenuFont() = 0; | |||
| @@ -69,6 +69,8 @@ public: | |||
| if (current->isInterestedInDragSource (sourceDetails)) | |||
| current->itemDragExit (sourceDetails); | |||
| } | |||
| owner.dragOperationEnded(); | |||
| } | |||
| void paint (Graphics& g) override | |||
| @@ -164,14 +166,14 @@ public: | |||
| if (sourceDetails.sourceComponent == nullptr) | |||
| { | |||
| delete this; | |||
| deleteSelf(); | |||
| } | |||
| else if (! isMouseButtonDownAnywhere()) | |||
| { | |||
| if (mouseDragSource != nullptr) | |||
| mouseDragSource->removeMouseListener (this); | |||
| delete this; | |||
| deleteSelf(); | |||
| } | |||
| } | |||
| @@ -180,7 +182,7 @@ public: | |||
| if (key == KeyPress::escapeKey) | |||
| { | |||
| dismissWithAnimation (true); | |||
| delete this; | |||
| deleteSelf(); | |||
| return true; | |||
| } | |||
| @@ -215,13 +217,28 @@ private: | |||
| return dynamic_cast<DragAndDropTarget*> (currentlyOverComp.get()); | |||
| } | |||
| static Component* findDesktopComponentBelow (Point<int> screenPos) | |||
| { | |||
| Desktop& desktop = Desktop::getInstance(); | |||
| for (int i = desktop.getNumComponents(); --i >= 0;) | |||
| { | |||
| Component* c = desktop.getComponent(i); | |||
| if (Component* hit = c->getComponentAt (c->getLocalPoint (nullptr, screenPos))) | |||
| return hit; | |||
| } | |||
| return nullptr; | |||
| } | |||
| DragAndDropTarget* findTarget (Point<int> screenPos, Point<int>& relativePos, | |||
| Component*& resultComponent) const | |||
| { | |||
| Component* hit = getParentComponent(); | |||
| if (hit == nullptr) | |||
| hit = Desktop::getInstance().findComponentAt (screenPos); | |||
| hit = findDesktopComponentBelow (screenPos); | |||
| else | |||
| hit = hit->getComponentAt (hit->getLocalPoint (nullptr, screenPos)); | |||
| @@ -296,12 +313,17 @@ private: | |||
| && ModifierKeys::getCurrentModifiersRealtime().isAnyMouseButtonDown()) | |||
| { | |||
| (new ExternalDragAndDropMessage (files, canMoveFiles))->post(); | |||
| delete this; | |||
| deleteSelf(); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| void deleteSelf() | |||
| { | |||
| delete this; | |||
| } | |||
| void dismissWithAnimation (const bool shouldSnapBack) | |||
| { | |||
| setVisible (true); | |||
| @@ -434,6 +456,8 @@ void DragAndDropContainer::startDragging (const var& sourceDescription, | |||
| if (ComponentPeer* const peer = dragImageComponent->getPeer()) | |||
| peer->performAnyPendingRepaintsNow(); | |||
| #endif | |||
| dragOperationStarted(); | |||
| } | |||
| } | |||
| @@ -458,6 +482,9 @@ bool DragAndDropContainer::shouldDropFilesWhenDraggedExternally (const DragAndDr | |||
| return false; | |||
| } | |||
| void DragAndDropContainer::dragOperationStarted() {} | |||
| void DragAndDropContainer::dragOperationEnded() {} | |||
| //============================================================================== | |||
| DragAndDropTarget::SourceDetails::SourceDetails (const var& desc, Component* comp, Point<int> pos) noexcept | |||
| : description (desc), | |||
| @@ -165,6 +165,12 @@ protected: | |||
| virtual bool shouldDropFilesWhenDraggedExternally (const DragAndDropTarget::SourceDetails& sourceDetails, | |||
| StringArray& files, bool& canMoveFiles); | |||
| /** Subclasses can override this to be told when a drag starts. */ | |||
| virtual void dragOperationStarted(); | |||
| /** Subclasses can override this to be told when a drag finishes. */ | |||
| virtual void dragOperationEnded(); | |||
| private: | |||
| //============================================================================== | |||
| class DragImageComponent; | |||
| @@ -24,6 +24,14 @@ | |||
| class UIViewComponentPeer; | |||
| // The way rotation works changed in iOS8.. | |||
| static bool isUsingOldRotationMethod() noexcept | |||
| { | |||
| static bool isPreV8 = ([[[UIDevice currentDevice] systemVersion] compare: @"8.0" | |||
| options: NSNumericSearch] == NSOrderedAscending); | |||
| return isPreV8; | |||
| } | |||
| namespace Orientations | |||
| { | |||
| static Desktop::DisplayOrientation convertToJuce (UIInterfaceOrientation orientation) | |||
| @@ -42,12 +50,15 @@ namespace Orientations | |||
| static CGAffineTransform getCGTransformFor (const Desktop::DisplayOrientation orientation) noexcept | |||
| { | |||
| switch (orientation) | |||
| if (isUsingOldRotationMethod()) | |||
| { | |||
| case Desktop::upsideDown: return CGAffineTransformMake (-1, 0, 0, -1, 0, 0); | |||
| case Desktop::rotatedClockwise: return CGAffineTransformMake (0, -1, 1, 0, 0, 0); | |||
| case Desktop::rotatedAntiClockwise: return CGAffineTransformMake (0, 1, -1, 0, 0, 0); | |||
| default: break; | |||
| switch (orientation) | |||
| { | |||
| case Desktop::upsideDown: return CGAffineTransformMake (-1, 0, 0, -1, 0, 0); | |||
| case Desktop::rotatedClockwise: return CGAffineTransformMake (0, -1, 1, 0, 0, 0); | |||
| case Desktop::rotatedAntiClockwise: return CGAffineTransformMake (0, 1, -1, 0, 0, 0); | |||
| default: break; | |||
| } | |||
| } | |||
| return CGAffineTransformIdentity; | |||
| @@ -85,10 +96,10 @@ using namespace juce; | |||
| - (void) drawRect: (CGRect) r; | |||
| - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (void) touchesCancelled: (NSSet*) touches withEvent: (UIEvent*) event; | |||
| - (BOOL) becomeFirstResponder; | |||
| - (BOOL) resignFirstResponder; | |||
| @@ -106,6 +117,7 @@ using namespace juce; | |||
| - (BOOL) shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation) interfaceOrientation; | |||
| - (void) willRotateToInterfaceOrientation: (UIInterfaceOrientation) toInterfaceOrientation duration: (NSTimeInterval) duration; | |||
| - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation; | |||
| - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator; | |||
| - (void) viewDidLoad; | |||
| - (void) viewWillAppear: (BOOL) animated; | |||
| @@ -189,7 +201,7 @@ public: | |||
| bool isSharedWindow, fullScreen, insideDrawRect; | |||
| static ModifierKeys currentModifiers; | |||
| static int64 getMouseTime (UIEvent* e) | |||
| static int64 getMouseTime (UIEvent* e) noexcept | |||
| { | |||
| return (Time::currentTimeMillis() - Time::getMillisecondCounter()) | |||
| + (int64) ([e timestamp] * 1000.0); | |||
| @@ -197,26 +209,29 @@ public: | |||
| static Rectangle<int> rotatedScreenPosToReal (const Rectangle<int>& r) | |||
| { | |||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||
| switch ([[UIApplication sharedApplication] statusBarOrientation]) | |||
| if (isUsingOldRotationMethod()) | |||
| { | |||
| case UIInterfaceOrientationPortrait: | |||
| return r; | |||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||
| switch ([[UIApplication sharedApplication] statusBarOrientation]) | |||
| { | |||
| case UIInterfaceOrientationPortrait: | |||
| return r; | |||
| case UIInterfaceOrientationPortraitUpsideDown: | |||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||
| r.getWidth(), r.getHeight()); | |||
| case UIInterfaceOrientationPortraitUpsideDown: | |||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||
| r.getWidth(), r.getHeight()); | |||
| case UIInterfaceOrientationLandscapeLeft: | |||
| return Rectangle<int> (r.getY(), screen.getHeight() - r.getRight(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeLeft: | |||
| return Rectangle<int> (r.getY(), screen.getHeight() - r.getRight(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeRight: | |||
| return Rectangle<int> (screen.getWidth() - r.getBottom(), r.getX(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeRight: | |||
| return Rectangle<int> (screen.getWidth() - r.getBottom(), r.getX(), | |||
| r.getHeight(), r.getWidth()); | |||
| default: jassertfalse; // unknown orientation! | |||
| default: jassertfalse; // unknown orientation! | |||
| } | |||
| } | |||
| return r; | |||
| @@ -224,26 +239,29 @@ public: | |||
| static Rectangle<int> realScreenPosToRotated (const Rectangle<int>& r) | |||
| { | |||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||
| switch ([[UIApplication sharedApplication] statusBarOrientation]) | |||
| if (isUsingOldRotationMethod()) | |||
| { | |||
| case UIInterfaceOrientationPortrait: | |||
| return r; | |||
| const Rectangle<int> screen (convertToRectInt ([UIScreen mainScreen].bounds)); | |||
| switch ([[UIApplication sharedApplication] statusBarOrientation]) | |||
| { | |||
| case UIInterfaceOrientationPortrait: | |||
| return r; | |||
| case UIInterfaceOrientationPortraitUpsideDown: | |||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||
| r.getWidth(), r.getHeight()); | |||
| case UIInterfaceOrientationPortraitUpsideDown: | |||
| return Rectangle<int> (screen.getWidth() - r.getRight(), screen.getHeight() - r.getBottom(), | |||
| r.getWidth(), r.getHeight()); | |||
| case UIInterfaceOrientationLandscapeLeft: | |||
| return Rectangle<int> (screen.getHeight() - r.getBottom(), r.getX(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeLeft: | |||
| return Rectangle<int> (screen.getHeight() - r.getBottom(), r.getX(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeRight: | |||
| return Rectangle<int> (r.getY(), screen.getWidth() - r.getRight(), | |||
| r.getHeight(), r.getWidth()); | |||
| case UIInterfaceOrientationLandscapeRight: | |||
| return Rectangle<int> (r.getY(), screen.getWidth() - r.getRight(), | |||
| r.getHeight(), r.getWidth()); | |||
| default: jassertfalse; // unknown orientation! | |||
| default: jassertfalse; // unknown orientation! | |||
| } | |||
| } | |||
| return r; | |||
| @@ -273,6 +291,13 @@ private: | |||
| }; | |||
| }; | |||
| static void sendScreenBoundsUpdate (JuceUIViewController* c) | |||
| { | |||
| JuceUIView* juceView = (JuceUIView*) [c view]; | |||
| jassert (juceView != nil && juceView->owner != nullptr); | |||
| juceView->owner->updateTransformAndScreenBounds(); | |||
| } | |||
| } // (juce namespace) | |||
| //============================================================================== | |||
| @@ -301,19 +326,23 @@ private: | |||
| - (void) didRotateFromInterfaceOrientation: (UIInterfaceOrientation) fromInterfaceOrientation | |||
| { | |||
| (void) fromInterfaceOrientation; | |||
| sendScreenBoundsUpdate (self); | |||
| [UIView setAnimationsEnabled: YES]; | |||
| } | |||
| JuceUIView* juceView = (JuceUIView*) [self view]; | |||
| jassert (juceView != nil && juceView->owner != nullptr); | |||
| juceView->owner->updateTransformAndScreenBounds(); | |||
| - (void) viewWillTransitionToSize: (CGSize) size withTransitionCoordinator: (id<UIViewControllerTransitionCoordinator>) coordinator | |||
| { | |||
| [super viewWillTransitionToSize: size withTransitionCoordinator: coordinator]; | |||
| sendScreenBoundsUpdate (self); | |||
| [UIView setAnimationsEnabled: YES]; | |||
| // On some devices the screen-size isn't yet updated at this point, so also trigger another | |||
| // async update to double-check.. | |||
| MessageManager::callAsync ([=]() { sendScreenBoundsUpdate (self); }); | |||
| } | |||
| - (void) viewDidLoad | |||
| { | |||
| JuceUIView* juceView = (JuceUIView*) [self view]; | |||
| jassert (juceView != nil && juceView->owner != nullptr); | |||
| juceView->owner->updateTransformAndScreenBounds(); | |||
| sendScreenBoundsUpdate (self); | |||
| } | |||
| - (void) viewWillAppear: (BOOL) animated | |||
| @@ -65,6 +65,12 @@ static void addKDialogArgs (StringArray& args, String& separator, | |||
| if (title.isNotEmpty()) | |||
| args.add ("--title=" + title); | |||
| if (uint64 topWindowID = getTopWindowID()) | |||
| { | |||
| args.add ("--attach"); | |||
| args.add (String (topWindowID)); | |||
| } | |||
| if (selectMultipleFiles) | |||
| { | |||
| separator = "\n"; | |||
| @@ -79,12 +85,6 @@ static void addKDialogArgs (StringArray& args, String& separator, | |||
| else args.add ("--getopenfilename"); | |||
| } | |||
| if (uint64 topWindowID = getTopWindowID()) | |||
| { | |||
| args.add ("--attach"); | |||
| args.add (String (topWindowID)); | |||
| } | |||
| File startPath; | |||
| if (file.exists()) | |||
| @@ -979,11 +979,13 @@ public: | |||
| Point<float> localToGlobal (Point<float> relativePosition) override | |||
| { | |||
| updateWindowBounds(); | |||
| return relativePosition + bounds.getPosition().toFloat(); | |||
| } | |||
| Point<float> globalToLocal (Point<float> screenPosition) override | |||
| { | |||
| updateWindowBounds(); | |||
| return screenPosition - bounds.getPosition().toFloat(); | |||
| } | |||
| @@ -1087,9 +1089,7 @@ public: | |||
| { | |||
| for (int i = windowListSize; --i >= 0;) | |||
| { | |||
| LinuxComponentPeer* const peer = LinuxComponentPeer::getPeerFor (windowList[i]); | |||
| if (peer != 0) | |||
| if (LinuxComponentPeer* const peer = LinuxComponentPeer::getPeerFor (windowList[i])) | |||
| { | |||
| result = (peer == this); | |||
| break; | |||
| @@ -177,6 +177,7 @@ static void setWindowZOrder (HWND hwnd, HWND insertAfter) | |||
| //============================================================================== | |||
| static void setDPIAwareness() | |||
| { | |||
| #if ! JUCE_DISABLE_WIN32_DPI_AWARENESS | |||
| if (JUCEApplicationBase::isStandaloneApp()) | |||
| { | |||
| if (setProcessDPIAwareness == nullptr) | |||
| @@ -203,6 +204,7 @@ static void setDPIAwareness() | |||
| } | |||
| } | |||
| } | |||
| #endif | |||
| } | |||
| static double getGlobalDPI() | |||
| @@ -845,7 +847,7 @@ public: | |||
| void toBehind (ComponentPeer* other) override | |||
| { | |||
| if (HWNDComponentPeer* const otherPeer = dynamic_cast <HWNDComponentPeer*> (other)) | |||
| if (HWNDComponentPeer* const otherPeer = dynamic_cast<HWNDComponentPeer*> (other)) | |||
| { | |||
| setMinimised (false); | |||
| @@ -960,7 +962,7 @@ public: | |||
| static ModifierKeys modifiersAtLastCallback; | |||
| //============================================================================== | |||
| class JuceDropTarget : public ComBaseClassHelper <IDropTarget> | |||
| class JuceDropTarget : public ComBaseClassHelper<IDropTarget> | |||
| { | |||
| public: | |||
| JuceDropTarget (HWNDComponentPeer& p) : ownerInfo (new OwnerInfo (p)) {} | |||
| @@ -1099,13 +1101,13 @@ public: | |||
| if (SUCCEEDED (fileData.error)) | |||
| { | |||
| const LPDROPFILES dropFiles = static_cast <const LPDROPFILES> (fileData.data); | |||
| const LPDROPFILES dropFiles = static_cast<const LPDROPFILES> (fileData.data); | |||
| const void* const names = addBytesToPointer (dropFiles, sizeof (DROPFILES)); | |||
| if (dropFiles->fWide) | |||
| ownerInfo->parseFileList (static_cast <const WCHAR*> (names), fileData.dataSize); | |||
| ownerInfo->parseFileList (static_cast<const WCHAR*> (names), fileData.dataSize); | |||
| else | |||
| ownerInfo->parseFileList (static_cast <const char*> (names), fileData.dataSize); | |||
| ownerInfo->parseFileList (static_cast<const char*> (names), fileData.dataSize); | |||
| } | |||
| else | |||
| { | |||
| @@ -1300,7 +1302,7 @@ private: | |||
| //============================================================================== | |||
| static void* createWindowCallback (void* userData) | |||
| { | |||
| static_cast <HWNDComponentPeer*> (userData)->createWindow(); | |||
| static_cast<HWNDComponentPeer*> (userData)->createWindow(); | |||
| return nullptr; | |||
| } | |||
| @@ -1643,7 +1645,7 @@ private: | |||
| handlePaint (*context); | |||
| } | |||
| static_cast <WindowsBitmapImage*> (offscreenImage.getPixelData()) | |||
| static_cast<WindowsBitmapImage*> (offscreenImage.getPixelData()) | |||
| ->blitToWindow (hwnd, dc, transparent, x, y, updateLayeredWindowAlpha); | |||
| } | |||
| @@ -2261,6 +2263,24 @@ private: | |||
| } | |||
| } | |||
| void handlePowerBroadcast (WPARAM wParam) | |||
| { | |||
| if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| switch (wParam) | |||
| { | |||
| case PBT_APMSUSPEND: app->suspended(); break; | |||
| case PBT_APMQUERYSUSPENDFAILED: | |||
| case PBT_APMRESUMECRITICAL: | |||
| case PBT_APMRESUMESUSPEND: | |||
| case PBT_APMRESUMEAUTOMATIC: app->resumed(); break; | |||
| default: break; | |||
| } | |||
| } | |||
| } | |||
| void handleLeftClickInNCArea (WPARAM wParam) | |||
| { | |||
| if (! sendInputAttemptWhenModalMessage()) | |||
| @@ -2309,7 +2329,7 @@ private: | |||
| { | |||
| Desktop& desktop = Desktop::getInstance(); | |||
| const_cast <Desktop::Displays&> (desktop.getDisplays()).refresh(); | |||
| const_cast<Desktop::Displays&> (desktop.getDisplays()).refresh(); | |||
| if (fullScreen && ! isMinimised()) | |||
| { | |||
| @@ -2550,6 +2570,10 @@ private: | |||
| } | |||
| return TRUE; | |||
| case WM_POWERBROADCAST: | |||
| handlePowerBroadcast (wParam); | |||
| break; | |||
| case WM_SYNCPAINT: | |||
| return 0; | |||
| @@ -2879,7 +2903,7 @@ private: | |||
| void moveCandidateWindowToLeftAlignWithSelection (HIMC hImc, ComponentPeer& peer, TextInputTarget* target) const | |||
| { | |||
| if (Component* const targetComp = dynamic_cast <Component*> (target)) | |||
| if (Component* const targetComp = dynamic_cast<Component*> (target)) | |||
| { | |||
| const Rectangle<int> area (peer.getComponent().getLocalArea (targetComp, target->getCaretRectangle())); | |||
| @@ -2981,7 +3005,7 @@ bool JUCE_CALLTYPE Process::isForegroundProcess() | |||
| fg = GetAncestor (fg, GA_ROOT); | |||
| for (int i = ComponentPeer::getNumPeers(); --i >= 0;) | |||
| if (HWNDComponentPeer* const wp = dynamic_cast <HWNDComponentPeer*> (ComponentPeer::getPeer (i))) | |||
| if (HWNDComponentPeer* const wp = dynamic_cast<HWNDComponentPeer*> (ComponentPeer::getPeer (i))) | |||
| if (wp->isInside (fg)) | |||
| return true; | |||
| @@ -3007,7 +3031,7 @@ static BOOL CALLBACK enumAlwaysOnTopWindows (HWND hwnd, LPARAM lParam) | |||
| if (GetWindowInfo (hwnd, &info) | |||
| && (info.dwExStyle & WS_EX_TOPMOST) != 0) | |||
| { | |||
| *reinterpret_cast <bool*> (lParam) = true; | |||
| *reinterpret_cast<bool*> (lParam) = true; | |||
| return FALSE; | |||
| } | |||
| } | |||
| @@ -3212,7 +3236,7 @@ void SystemClipboard::copyTextToClipboard (const String& text) | |||
| { | |||
| if (HGLOBAL bufH = GlobalAlloc (GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, bytesNeeded + sizeof (WCHAR))) | |||
| { | |||
| if (WCHAR* const data = static_cast <WCHAR*> (GlobalLock (bufH))) | |||
| if (WCHAR* const data = static_cast<WCHAR*> (GlobalLock (bufH))) | |||
| { | |||
| text.copyToUTF16 (data, bytesNeeded); | |||
| GlobalUnlock (bufH); | |||
| @@ -66,7 +66,7 @@ void BooleanPropertyComponent::paint (Graphics& g) | |||
| { | |||
| PropertyComponent::paint (g); | |||
| g.setColour (Colours::white); | |||
| g.setColour (findColour (backgroundColourId)); | |||
| g.fillRect (button.getBounds()); | |||
| g.setColour (findColour (ComboBox::outlineColourId)); | |||
| @@ -56,6 +56,10 @@ protected: | |||
| public: | |||
| /** Creates a button component. | |||
| Note that if you call this constructor then you must use the Value to interact with the | |||
| button state, and you can't override the class with your own setState or getState methods. | |||
| If you want to use getState and setState, call the other constructor instead. | |||
| @param valueToControl a Value object that this property should refer to. | |||
| @param propertyName the property name to be passed to the PropertyComponent | |||
| @param buttonText the text shown in the ToggleButton component | |||
| @@ -74,6 +78,20 @@ public: | |||
| /** Must return the current value of the property. */ | |||
| virtual bool getState() const; | |||
| //============================================================================== | |||
| /** A set of colour IDs to use to change the colour of various aspects of the component. | |||
| These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||
| methods. | |||
| @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||
| */ | |||
| enum ColourIds | |||
| { | |||
| backgroundColourId = 0x100e801, /**< The colour to fill the background of the button area. */ | |||
| outlineColourId = 0x100e803, /**< The colour to use to draw an outline around the text area. */ | |||
| }; | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void paint (Graphics&) override; | |||
| @@ -50,15 +50,17 @@ class JUCE_API ChoicePropertyComponent : public PropertyComponent, | |||
| { | |||
| protected: | |||
| /** Creates the component. | |||
| Your subclass's constructor must add a list of options to the choices | |||
| member variable. | |||
| Your subclass's constructor must add a list of options to the choices member variable. | |||
| */ | |||
| ChoicePropertyComponent (const String& propertyName); | |||
| public: | |||
| /** Creates the component. | |||
| Note that if you call this constructor then you must use the Value to interact with the | |||
| index, and you can't override the class with your own setIndex or getIndex methods. | |||
| If you want to use those methods, call the other constructor instead. | |||
| @param valueToControl the value that the combo box will read and control | |||
| @param propertyName the name of the property | |||
| @param choices the list of possible values that the drop-down list will contain | |||
| @@ -58,6 +58,10 @@ public: | |||
| If you need to customise the slider in other ways, your constructor can | |||
| access the slider member variable and change it directly. | |||
| Note that if you call this constructor then you must use the Value to interact with | |||
| the value, and you can't override the class with your own setValue or getValue methods. | |||
| If you want to use those methods, call the other constructor instead. | |||
| */ | |||
| SliderPropertyComponent (const Value& valueToControl, | |||
| const String& propertyName, | |||
| @@ -460,5 +460,3 @@ void Label::textEditorFocusLost (TextEditor& ed) | |||
| { | |||
| textEditorTextChanged (ed); | |||
| } | |||
| void Label::Listener::editorShown (Label*, TextEditor&) {} | |||
| @@ -184,7 +184,7 @@ public: | |||
| virtual void labelTextChanged (Label* labelThatHasChanged) = 0; | |||
| /** Called when a Label goes into editing mode and displays a TextEditor. */ | |||
| virtual void editorShown (Label*, TextEditor& textEditorShown); | |||
| virtual void editorShown (Label*, TextEditor&) {} | |||
| }; | |||
| /** Registers a listener that will be called when the label's text changes. */ | |||
| @@ -367,6 +367,7 @@ ListBox::ListBox (const String& name, ListBoxModel* const m) | |||
| outlineThickness (0), | |||
| lastRowSelected (-1), | |||
| multipleSelection (false), | |||
| alwaysFlipSelection (false), | |||
| hasDoneInitialUpdate (false) | |||
| { | |||
| addAndMakeVisible (viewport = new ListViewport (*this)); | |||
| @@ -391,11 +392,16 @@ void ListBox::setModel (ListBoxModel* const newModel) | |||
| } | |||
| } | |||
| void ListBox::setMultipleSelectionEnabled (bool b) | |||
| void ListBox::setMultipleSelectionEnabled (bool b) noexcept | |||
| { | |||
| multipleSelection = b; | |||
| } | |||
| void ListBox::setClickingTogglesRowSelection (bool b) noexcept | |||
| { | |||
| alwaysFlipSelection = b; | |||
| } | |||
| void ListBox::setMouseMoveSelectsRows (bool b) | |||
| { | |||
| if (b) | |||
| @@ -470,9 +476,7 @@ void ListBox::updateContent() | |||
| } | |||
| //============================================================================== | |||
| void ListBox::selectRow (const int row, | |||
| bool dontScroll, | |||
| bool deselectOthersFirst) | |||
| void ListBox::selectRow (int row, bool dontScroll, bool deselectOthersFirst) | |||
| { | |||
| selectRowInternal (row, dontScroll, deselectOthersFirst, false); | |||
| } | |||
| @@ -589,7 +593,7 @@ void ListBox::selectRowsBasedOnModifierKeys (const int row, | |||
| ModifierKeys mods, | |||
| const bool isMouseUpEvent) | |||
| { | |||
| if (multipleSelection && mods.isCommandDown()) | |||
| if (multipleSelection && (mods.isCommandDown() || alwaysFlipSelection)) | |||
| { | |||
| flipRowSelection (row); | |||
| } | |||
| @@ -883,7 +887,7 @@ void ListBox::repaintRow (const int rowNumber) noexcept | |||
| Image ListBox::createSnapshotOfSelectedRows (int& imageX, int& imageY) | |||
| { | |||
| Rectangle<int> imageArea; | |||
| const int firstRow = getRowContainingPosition (0, 0); | |||
| const int firstRow = getRowContainingPosition (0, viewport->getY()); | |||
| for (int i = getNumRowsOnScreen() + 2; --i >= 0;) | |||
| { | |||
| @@ -45,7 +45,10 @@ public: | |||
| */ | |||
| virtual int getNumRows() = 0; | |||
| /** This method must be implemented to draw a row of the list. */ | |||
| /** This method must be implemented to draw a row of the list. | |||
| Note that the rowNumber value may be greater than the number of rows in your | |||
| list, so be careful that you don't assume it's less than getNumRows(). | |||
| */ | |||
| virtual void paintListBoxItem (int rowNumber, | |||
| Graphics& g, | |||
| int width, int height, | |||
| @@ -216,7 +219,16 @@ public: | |||
| clicked and to get it to do the appropriate selection based on whether | |||
| the ctrl/shift keys are held down. | |||
| */ | |||
| void setMultipleSelectionEnabled (bool shouldBeEnabled); | |||
| void setMultipleSelectionEnabled (bool shouldBeEnabled) noexcept; | |||
| /** If enabled, this makes the listbox flip the selection status of | |||
| each row that the user clicks, without affecting other selected rows. | |||
| (This only has an effect if multiple selection is also enabled). | |||
| If not enabled, you can still get the same row-flipping behaviour by holding | |||
| down CMD or CTRL when clicking. | |||
| */ | |||
| void setClickingTogglesRowSelection (bool flipRowSelection) noexcept; | |||
| /** Makes the list react to mouse moves by selecting the row that the mouse if over. | |||
| @@ -557,7 +569,7 @@ private: | |||
| int totalItems, rowHeight, minimumRowWidth; | |||
| int outlineThickness; | |||
| int lastRowSelected; | |||
| bool multipleSelection, hasDoneInitialUpdate; | |||
| bool multipleSelection, alwaysFlipSelection, hasDoneInitialUpdate; | |||
| SparseSet<int> selected; | |||
| void selectRowInternal (int rowNumber, bool dontScrollToShowThisRow, | |||
| @@ -464,9 +464,9 @@ public: | |||
| const bool stopAtEnd) | |||
| { | |||
| // make sure the values are sensible.. | |||
| jassert (rotaryStart >= 0 && rotaryEnd >= 0); | |||
| jassert (rotaryStart < float_Pi * 4.0f && rotaryEnd < float_Pi * 4.0f); | |||
| jassert (rotaryStart < rotaryEnd); | |||
| jassert (startAngleRadians >= 0 && endAngleRadians >= 0); | |||
| jassert (startAngleRadians < float_Pi * 4.0f && endAngleRadians < float_Pi * 4.0f); | |||
| jassert (startAngleRadians < endAngleRadians); | |||
| rotaryStart = startAngleRadians; | |||
| rotaryEnd = endAngleRadians; | |||
| @@ -1006,9 +1006,9 @@ public: | |||
| valueBox->hideEditor (false); | |||
| const double value = (double) currentValue.getValue(); | |||
| const double delta = getMouseWheelDelta (value, (wheel.deltaX != 0 ? -wheel.deltaX : wheel.deltaY) | |||
| * (wheel.isReversed ? -1.0f : 1.0f)); | |||
| const double delta = getMouseWheelDelta (value, (std::abs (wheel.deltaX) > std::abs (wheel.deltaY) | |||
| ? -wheel.deltaX : wheel.deltaY) | |||
| * (wheel.isReversed ? -1.0f : 1.0f)); | |||
| if (delta != 0) | |||
| { | |||
| const double newValue = value + jmax (interval, std::abs (delta)) * (delta < 0 ? -1.0 : 1.0); | |||
| @@ -1287,6 +1287,7 @@ public: | |||
| { | |||
| setAlwaysOnTop (true); | |||
| setAllowedPlacement (owner.getLookAndFeel().getSliderPopupPlacement (s)); | |||
| setLookAndFeel (&s.getLookAndFeel()); | |||
| } | |||
| void paintContent (Graphics& g, int w, int h) | |||
| @@ -26,7 +26,7 @@ class TableListBox::RowComp : public Component, | |||
| public TooltipClient | |||
| { | |||
| public: | |||
| RowComp (TableListBox& tlb) : owner (tlb), row (-1), isSelected (false) | |||
| RowComp (TableListBox& tlb) noexcept : owner (tlb), row (-1), isSelected (false) | |||
| { | |||
| } | |||
| @@ -192,7 +192,7 @@ public: | |||
| if (TableListBoxModel* m = owner.getModel()) | |||
| return m->getCellTooltip (row, columnId); | |||
| return String::empty; | |||
| return String(); | |||
| } | |||
| Component* findChildComponentForColumn (const int columnId) const | |||
| @@ -275,7 +275,7 @@ void TableListBox::setHeader (TableHeaderComponent* newHeader) | |||
| { | |||
| jassert (newHeader != nullptr); // you need to supply a real header for a table! | |||
| Rectangle<int> newBounds (0, 0, 100, 28); | |||
| Rectangle<int> newBounds (100, 28); | |||
| if (header != nullptr) | |||
| newBounds = header->getBounds(); | |||
| @@ -287,7 +287,7 @@ void TableListBox::setHeader (TableHeaderComponent* newHeader) | |||
| header->addListener (this); | |||
| } | |||
| int TableListBox::getHeaderHeight() const | |||
| int TableListBox::getHeaderHeight() const noexcept | |||
| { | |||
| return header->getHeight(); | |||
| } | |||
| @@ -312,16 +312,11 @@ void TableListBox::autoSizeAllColumns() | |||
| autoSizeColumn (header->getColumnIdOfIndex (i, true)); | |||
| } | |||
| void TableListBox::setAutoSizeMenuOptionShown (const bool shouldBeShown) | |||
| void TableListBox::setAutoSizeMenuOptionShown (const bool shouldBeShown) noexcept | |||
| { | |||
| autoSizeOptionsShown = shouldBeShown; | |||
| } | |||
| bool TableListBox::isAutoSizeMenuOptionShown() const | |||
| { | |||
| return autoSizeOptionsShown; | |||
| } | |||
| Rectangle<int> TableListBox::getCellPosition (const int columnId, const int rowNumber, | |||
| const bool relativeToComponentTopLeft) const | |||
| { | |||
| @@ -337,7 +332,7 @@ Rectangle<int> TableListBox::getCellPosition (const int columnId, const int rowN | |||
| Component* TableListBox::getCellComponent (int columnId, int rowNumber) const | |||
| { | |||
| if (RowComp* const rowComp = dynamic_cast <RowComp*> (getComponentForRowNumber (rowNumber))) | |||
| if (RowComp* const rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (rowNumber))) | |||
| return rowComp->findChildComponentForColumn (columnId); | |||
| return nullptr; | |||
| @@ -370,12 +365,12 @@ void TableListBox::paintListBoxItem (int, Graphics&, int, int, bool) | |||
| { | |||
| } | |||
| Component* TableListBox::refreshComponentForRow (int rowNumber, bool isRowSelected_, Component* existingComponentToUpdate) | |||
| Component* TableListBox::refreshComponentForRow (int rowNumber, bool rowSelected, Component* existingComponentToUpdate) | |||
| { | |||
| if (existingComponentToUpdate == nullptr) | |||
| existingComponentToUpdate = new RowComp (*this); | |||
| static_cast <RowComp*> (existingComponentToUpdate)->update (rowNumber, isRowSelected_); | |||
| static_cast<RowComp*> (existingComponentToUpdate)->update (rowNumber, rowSelected); | |||
| return existingComponentToUpdate; | |||
| } | |||
| @@ -450,7 +445,7 @@ void TableListBox::updateColumnComponents() const | |||
| const int firstRow = getRowContainingPosition (0, 0); | |||
| for (int i = firstRow + getNumRowsOnScreen() + 2; --i >= firstRow;) | |||
| if (RowComp* const rowComp = dynamic_cast <RowComp*> (getComponentForRowNumber (i))) | |||
| if (RowComp* const rowComp = dynamic_cast<RowComp*> (getComponentForRowNumber (i))) | |||
| rowComp->resized(); | |||
| } | |||
| @@ -465,7 +460,7 @@ void TableListBoxModel::deleteKeyPressed (int) {} | |||
| void TableListBoxModel::returnKeyPressed (int) {} | |||
| void TableListBoxModel::listWasScrolled() {} | |||
| String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return String::empty; } | |||
| String TableListBoxModel::getCellTooltip (int /*rowNumber*/, int /*columnId*/) { return String(); } | |||
| var TableListBoxModel::getDragSourceDescription (const SparseSet<int>&) { return var(); } | |||
| Component* TableListBoxModel::refreshComponentForCell (int, int, bool, Component* existingComponentToUpdate) | |||
| @@ -57,7 +57,7 @@ public: | |||
| The graphics context has its origin at the row's top-left, and your method | |||
| should fill the area specified by the width and height parameters. | |||
| */ | |||
| virtual void paintRowBackground (Graphics& g, | |||
| virtual void paintRowBackground (Graphics&, | |||
| int rowNumber, | |||
| int width, int height, | |||
| bool rowIsSelected) = 0; | |||
| @@ -66,8 +66,11 @@ public: | |||
| The graphics context's origin will already be set to the top-left of the cell, | |||
| whose size is specified by (width, height). | |||
| Note that the rowNumber value may be greater than the number of rows in your | |||
| list, so be careful that you don't assume it's less than getNumRows(). | |||
| */ | |||
| virtual void paintCell (Graphics& g, | |||
| virtual void paintCell (Graphics&, | |||
| int rowNumber, | |||
| int columnId, | |||
| int width, int height, | |||
| @@ -142,25 +145,21 @@ public: | |||
| */ | |||
| virtual int getColumnAutoSizeWidth (int columnId); | |||
| /** Returns a tooltip for a particular cell in the table. | |||
| */ | |||
| /** Returns a tooltip for a particular cell in the table. */ | |||
| virtual String getCellTooltip (int rowNumber, int columnId); | |||
| //============================================================================== | |||
| /** Override this to be informed when rows are selected or deselected. | |||
| @see ListBox::selectedRowsChanged() | |||
| */ | |||
| virtual void selectedRowsChanged (int lastRowSelected); | |||
| /** Override this to be informed when the delete key is pressed. | |||
| @see ListBox::deleteKeyPressed() | |||
| */ | |||
| virtual void deleteKeyPressed (int lastRowSelected); | |||
| /** Override this to be informed when the return key is pressed. | |||
| @see ListBox::returnKeyPressed() | |||
| */ | |||
| virtual void returnKeyPressed (int lastRowSelected); | |||
| @@ -210,29 +209,34 @@ public: | |||
| /** Creates a TableListBox. | |||
| The model pointer passed-in can be null, in which case you can set it later | |||
| with setModel(). | |||
| with setModel(). The TableListBox does not take ownership of the model - it's | |||
| the caller's responsibility to manage its lifetime and make sure it | |||
| doesn't get deleted while still being used. | |||
| */ | |||
| TableListBox (const String& componentName = String::empty, | |||
| TableListBoxModel* model = 0); | |||
| TableListBox (const String& componentName = String(), | |||
| TableListBoxModel* model = nullptr); | |||
| /** Destructor. */ | |||
| ~TableListBox(); | |||
| //============================================================================== | |||
| /** Changes the TableListBoxModel that is being used for this table. | |||
| The TableListBox does not take ownership of the model - it's the caller's responsibility | |||
| to manage its lifetime and make sure it doesn't get deleted while still being used. | |||
| */ | |||
| void setModel (TableListBoxModel* newModel); | |||
| /** Returns the model currently in use. */ | |||
| TableListBoxModel* getModel() const { return model; } | |||
| TableListBoxModel* getModel() const noexcept { return model; } | |||
| //============================================================================== | |||
| /** Returns the header component being used in this table. */ | |||
| TableHeaderComponent& getHeader() const { return *header; } | |||
| TableHeaderComponent& getHeader() const noexcept { return *header; } | |||
| /** Sets the header component to use for the table. | |||
| The table will take ownership of the component that you pass in, and will delete it | |||
| when it's no longer needed. | |||
| The pointer passed in may not be null. | |||
| */ | |||
| void setHeader (TableHeaderComponent* newHeader); | |||
| @@ -244,7 +248,7 @@ public: | |||
| /** Returns the height of the table header. | |||
| @see setHeaderHeight | |||
| */ | |||
| int getHeaderHeight() const; | |||
| int getHeaderHeight() const noexcept; | |||
| //============================================================================== | |||
| /** Resizes a column to fit its contents. | |||
| @@ -260,15 +264,14 @@ public: | |||
| void autoSizeAllColumns(); | |||
| /** Enables or disables the auto size options on the popup menu. | |||
| By default, these are enabled. | |||
| */ | |||
| void setAutoSizeMenuOptionShown (bool shouldBeShown); | |||
| void setAutoSizeMenuOptionShown (bool shouldBeShown) noexcept; | |||
| /** True if the auto-size options should be shown on the menu. | |||
| @see setAutoSizeMenuOptionsShown | |||
| @see setAutoSizeMenuOptionShown | |||
| */ | |||
| bool isAutoSizeMenuOptionShown() const; | |||
| bool isAutoSizeMenuOptionShown() const noexcept { return autoSizeOptionsShown; } | |||
| /** Returns the position of one of the cells in the table. | |||
| @@ -591,8 +591,8 @@ void Toolbar::itemDragMove (const SourceDetails& dragSourceDetails) | |||
| { | |||
| const Rectangle<int> previousPos (animator.getComponentDestination (prev)); | |||
| if (abs (dragObjectLeft - (vertical ? previousPos.getY() : previousPos.getX()) | |||
| < abs (dragObjectRight - (vertical ? current.getBottom() : current.getRight())))) | |||
| if (std::abs (dragObjectLeft - (vertical ? previousPos.getY() : previousPos.getX())) | |||
| < std::abs (dragObjectRight - (vertical ? current.getBottom() : current.getRight()))) | |||
| { | |||
| newIndex = getIndexOfChildComponent (prev); | |||
| } | |||
| @@ -602,8 +602,8 @@ void Toolbar::itemDragMove (const SourceDetails& dragSourceDetails) | |||
| { | |||
| const Rectangle<int> nextPos (animator.getComponentDestination (next)); | |||
| if (abs (dragObjectLeft - (vertical ? current.getY() : current.getX()) | |||
| > abs (dragObjectRight - (vertical ? nextPos.getBottom() : nextPos.getRight())))) | |||
| if (std::abs (dragObjectLeft - (vertical ? current.getY() : current.getX())) | |||
| > std::abs (dragObjectRight - (vertical ? nextPos.getBottom() : nextPos.getRight()))) | |||
| { | |||
| newIndex = getIndexOfChildComponent (next) + 1; | |||
| } | |||
| @@ -1773,6 +1773,11 @@ TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const noexce | |||
| return nullptr; | |||
| } | |||
| static String escapeSlashesInTreeViewItemName (const String& s) | |||
| { | |||
| return s.replaceCharacter ('/', '\\'); | |||
| } | |||
| String TreeViewItem::getItemIdentifierString() const | |||
| { | |||
| String s; | |||
| @@ -1780,12 +1785,12 @@ String TreeViewItem::getItemIdentifierString() const | |||
| if (parentItem != nullptr) | |||
| s = parentItem->getItemIdentifierString(); | |||
| return s + "/" + getUniqueName().replaceCharacter ('/', '\\'); | |||
| return s + "/" + escapeSlashesInTreeViewItemName (getUniqueName()); | |||
| } | |||
| TreeViewItem* TreeViewItem::findItemFromIdentifierString (const String& identifierString) | |||
| { | |||
| const String thisId ("/" + getUniqueName()); | |||
| const String thisId ("/" + escapeSlashesInTreeViewItemName (getUniqueName())); | |||
| if (thisId == identifierString) | |||
| return this; | |||
| @@ -23,7 +23,7 @@ | |||
| */ | |||
| CallOutBox::CallOutBox (Component& c, const Rectangle<int>& area, Component* const parent) | |||
| : arrowSize (16.0f), content (c) | |||
| : arrowSize (16.0f), content (c), dismissalMouseClicksAreAlwaysConsumed (false) | |||
| { | |||
| addAndMakeVisible (content); | |||
| @@ -123,9 +123,8 @@ bool CallOutBox::hitTest (int x, int y) | |||
| void CallOutBox::inputAttemptWhenModal() | |||
| { | |||
| const Point<int> mousePos (getMouseXYRelative() + getBounds().getPosition()); | |||
| if (targetArea.contains (mousePos)) | |||
| if (dismissalMouseClicksAreAlwaysConsumed | |||
| || targetArea.contains (getMouseXYRelative() + getBounds().getPosition())) | |||
| { | |||
| // if you click on the area that originally popped-up the callout, you expect it | |||
| // to get rid of the box, but deleting the box here allows the click to pass through and | |||
| @@ -139,6 +138,11 @@ void CallOutBox::inputAttemptWhenModal() | |||
| } | |||
| } | |||
| void CallOutBox::setDismissalMouseClicksAreAlwaysConsumed (bool b) noexcept | |||
| { | |||
| dismissalMouseClicksAreAlwaysConsumed = b; | |||
| } | |||
| enum { callOutBoxDismissCommandId = 0x4f83a04b }; | |||
| void CallOutBox::handleCommandMessage (int commandId) | |||
| @@ -123,6 +123,16 @@ public: | |||
| */ | |||
| void dismiss(); | |||
| /** Determines whether the mouse events for clicks outside the calloutbox are | |||
| consumed, or allowed to arrive at the other component that they were aimed at. | |||
| By default this is false, so that when you click on something outside the calloutbox, | |||
| that event will also be sent to the component that was clicked on. If you set it to | |||
| true, then the first click will always just dismiss the box and not be sent to | |||
| anything else. | |||
| */ | |||
| void setDismissalMouseClicksAreAlwaysConsumed (bool shouldAlwaysBeConsumed) noexcept; | |||
| //============================================================================== | |||
| /** This abstract base class is implemented by LookAndFeel classes. */ | |||
| struct JUCE_API LookAndFeelMethods | |||
| @@ -161,6 +171,7 @@ private: | |||
| Point<float> targetPoint; | |||
| Rectangle<int> availableArea, targetArea; | |||
| Image background; | |||
| bool dismissalMouseClicksAreAlwaysConsumed; | |||
| void refreshPath(); | |||
| @@ -78,7 +78,7 @@ public: | |||
| initialise its fields with the appropriate details, and then call its launchAsync() | |||
| method to launch the dialog. | |||
| */ | |||
| struct LaunchOptions | |||
| struct JUCE_API LaunchOptions | |||
| { | |||
| LaunchOptions() noexcept; | |||
| @@ -127,7 +127,7 @@ public: | |||
| //============================================================================== | |||
| /** @internal */ | |||
| virtual void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr) override; | |||
| void addToDesktop (int windowStyleFlags, void* nativeWindowToAttachTo = nullptr) override; | |||
| protected: | |||
| //============================================================================== | |||